細品 javascript 設計模式(策略模式)

w39發表於2021-09-09

我儘量用最少的文字,最少的篇幅,講明白設計模式的方方面面。

理解策略模式

把 演算法 和 呼叫演算法 的部分做拆分開

我舉個例子吧:你想要去三亞旅遊,途徑有很多種:飛機,火車,自駕遊。這幾種方法都可以到達目的地,但是過程是有所不同的。

  • 飛機:適合緊急不差錢的情況
  • 火車:適合不緊急,並且目的明確(公司團建,中老年旅遊)
  • 自駕遊:這種比較隨性,和朋友家人一起出門,一起欣賞路過的風景。

每一種選擇,都是一種策略。
在程式中策略的意義在於,把處理不同任務但目的相同的程式碼整合在一起。再用一層函式委託他們來處理具體的演算法。這樣可以消除原本程式中大片的條件分支語句

上程式碼: 策略模式(計算不同的旅行方式到達時間)

// 定義策略
var strategies = {
    'plane': function(distance) {
        return distance * 1; // 假設飛機的速度最快
    },
    'train': function(distance) {
        return distance * 4; // 飛機的速度是火車的4倍
    },
    'roadTrip': function(distance) {
        return distance * 10; // 飛機的速度是自駕遊的10倍
    },
}

// Context
var calculateBonus = function(mode, distance) {
    if (typeof strategies[mode] === 'function') {
        return strategies[mode](distance);
    }
    return -1;
}

// 呼叫策略
console.log(calculateBonus('plane', 1000));
console.log(calculateBonus('train', 1000));
console.log(calculateBonus('roadTrip', 1000));
策略模式是比較好理解的,我們們先看一段即將被策略模式改造的程式碼

使用策略模式之前

var calculateBonus = function(mode, distance) {
    if (mode === 'plane') {
        return distance * 1;
    } else if (mode === 'train') {
        return distance * 4;
    } else if (mode === 'roadTrip') {
        return distance * 10;
    }
    return -1;
}

這段程式碼最大的問題是, 程式碼可複製性差, 不利於維護。每次有新的改動都必須扒開程式碼,找到具體的某個函式去修改。效率低,容易引發連貫性錯誤。

使用策略模式,實現緩懂動畫函式

為了更加明確策略模式的使用場景,我們一起來實現一個動畫函式。

js 動畫原理改變 dom 的 css 屬性,比如 left, top, background-position。所以至少要提供一下一些資訊。

圖片描述

  • dom 最初的位置
  • 目標位置
  • 開始時間
  • 結束時間

然後配合定時器 setInterval 在定時器中每個 19 毫秒改變一次 dom 的 css 屬性,每次修改 dom 時把上面的4個引數傳給演算法。演算法會計算出當前應該所在的位置。最後在更新 dom 的 css 屬性。這樣動畫就完成了。

演算法部分,這裡最初來自 Flash 但現在 css3 部分也是這樣實現的

// 先定義動畫緩動演算法
var tween = {
    linear: function(t, b, c, d) {
        return c * t / d + b;
    },
    easeIn: function(t, b, c, d) {
        return c * (t /= d) * t + b;
    },
    strongEaseIn: function(t, b, c, d) {
        return c * (t /= d) * t * t * t * t + b;
    },
    strongEaseOut: function(t, b, c, d) {
        return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
    },
    sineaseIn: function(t, b, c, d) {
        return c * (t /= d) * t * t + b;
    },
    sineaseOut: function(t, b, c, d) {
        return c * ((t = t / d - 1) * t * t + 1) + b;
    }
}

然後在 body 中新增入下節點

<div style="position: absolute; background:yellow;">im div</div>

接下來定義動畫類

let Animate = function(dom) {
    this.dom = dom;
    this.startTime = 0;
    this.startPos = 0;
    this.endPos = 0;
    this.propertyName = null;
    this.esing = null;
    this.duratin = null;
}
Animate.prototype.start = function(propertyName, endPos, duratin, esing) {
    this.startTime = Date.now();
    this.startPos = this.dom.getBoundingClientRect()[propertyName];
    this.propertyName = propertyName;
    this.endPos = endPos;
    this.duratin = duratin;
    this.esing = tween[esing];
    let self = this;
    let timeId = setInterval(function() {
        if (self.step() === false) {
            clearInterval(timeId)
        }
    }, 19)
}
Animate.prototype.step = function() {
    var t = Date.now();
    if (t >= this.startTime + this.duratin) {
        this.update(this.endPos)
        return false
    }
    var pos = this.esing(
        t - this.startTime, // 時間
        this.startPos, // 開始值
        this.endPos - this.startPos, // 運動距離
        this.duratin // 總耗時
    )
    this.update(pos);
}
Animate.prototype.update = function(pos) {
    this.dom.style[this.propertyName] = pos + 'px';
}

來測試一下!

var div = document.getElementsByTagName('div')[0];
var animate = new Animate(div);

animate.start('left', 500, 1000, 'linear')
// animate.start('left', 500, 1000, 'easeIn')
// animate.start('left', 500, 1000, 'strongEaseIn')
// animate.start('left', 500, 1000, 'strongEaseOut')
// animate.start('left', 500, 1000, 'sineaseIn')
// animate.start('left', 500, 1000, 'sineaseOut')

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/854/viewspace-2825695/,如需轉載,請註明出處,否則將追究法律責任。

相關文章