我尽量用最少的文字,最少的篇幅,讲明白设计模式的方方面面。
全文连接
把 算法 和 调用算法 的部分做拆分开
我举个例子吧:你想要去三亚旅游,途径有很多种:飞机,火车,自驾游。这几种方法都可以到达目的地,但是过程是有所不同的。
每一种选择,都是一种策略。
在程序中策略的意义在于,把处理不同任务但目的相同的代码整合在一起。再用一层函数委托他们来处理具体的算法。这样可以消除原本程序中大片的条件分支语句
上代码: 策略模式(计算不同的旅行方式到达时间)
// 定义策略 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。所以至少要提供一下一些信息。
然后配合定时器 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')