本系列文章主要根據《JavaScript設計模式與開發實踐》整理而來,其中會加入了一些本身的思考。但願對你們有所幫助。css
js設計模式--單例模式html
js設計模式--策略模式es6
策略模式的定義是:定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換。segmentfault
策略模式指的是定義一系列的算法,把它們一個個封裝起來。將不變的部分和變化的部分隔開是每一個設計模式的主題,策略模式也不例外,策略模式的目的就是將算法的使用與算法的實現分離開來。設計模式
一個基於策略模式的程序至少由兩部分組成。第一個部分是一組策略類,策略類封裝了具體 的算法,並負責具體的計算過程。 第二個部分是環境類Context,Context 接受客戶的請求,隨後 把請求委託給某一個策略類。要作到這點,說明 Context中要維持對某個策略對象的引用。app
策略模式的實現並不複雜,關鍵是如何從策略模式的實現背後,找到封裝變化、委託和多態性這些思想的價值。dom
從定義上看,策略模式就是用來封裝算法的。但若是把策略模式僅僅用來封裝算法,未免有一點大材小用。在實際開發中,咱們一般會把算法的含義擴散開來,使策略模式也能夠用來封裝 一系列的「業務規則」。只要這些業務規則指向的目標一致,而且能夠被替換使用,咱們就能夠 用策略模式來封裝它們。函數
但這些缺點並不嚴重post
var calculateBonus = function( performanceLevel, salary ){ if ( performanceLevel === 'S' ){ return salary * 4; } if ( performanceLevel === 'A' ){ return salary * 3; } if ( performanceLevel === 'B' ){ return salary * 2; } }; calculateBonus( 'B', 20000 ); // 輸出:40000 calculateBonus( 'S', 6000 ); // 輸出:24000
缺點:
var performanceS = function( salary ){ return salary * 4; }; var performanceA = function( salary ){ return salary * 3; }; var performanceB = function( salary ){ return salary * 2; }; var calculateBonus = function( performanceLevel, salary ){ if ( performanceLevel === 'S' ){ return performanceS( salary ); } if ( performanceLevel === 'A' ){ return performanceA( salary ); } if ( performanceLevel === 'B' ){ return performanceB( salary ); } }; calculateBonus( 'A' , 10000 ); // 輸出:30000
問題依然存在:calculateBonus 函數有可能愈來愈龐大,並且在系統變化的時候缺少彈性
var performanceS = function(){}; performanceS.prototype.calculate = function( salary ){ return salary * 4; }; var performanceA = function(){}; performanceA.prototype.calculate = function( salary ){ return salary * 3; }; var performanceB = function(){}; performanceB.prototype.calculate = function( salary ){ return salary * 2; }; //接下來定義獎金類Bonus: var Bonus = function(){ this.salary = null; // 原始工資 this.strategy = null; // 績效等級對應的策略對象 }; Bonus.prototype.setSalary = function( salary ){ this.salary = salary; // 設置員工的原始工資 }; Bonus.prototype.setStrategy = function( strategy ){ this.strategy = strategy; // 設置員工績效等級對應的策略對象 }; Bonus.prototype.getBonus = function(){ // 取得獎金數額 return this.strategy.calculate( this.salary ); // 把計算獎金的操做委託給對應的策略對象 }; var bonus = new Bonus(); bonus.setSalary( 10000 ); bonus.setStrategy( new performanceS() ); // 設置策略對象 console.log( bonus.getBonus() ); // 輸出:40000 bonus.setStrategy( new performanceA() ); // 設置策略對象 console.log( bonus.getBonus() ); // 輸出:30000
但這段代碼是基於傳統面嚮對象語言的模仿,下面咱們用JavaScript實現的策略模式。
在 JavaScript 語言中,函數也是對象,因此更簡單和直接的作法是把 strategy 直接定義爲函數
var strategies = { "S": function( salary ){ return salary * 4; }, "A": function( salary ){ return salary * 3; }, "B": function( salary ){ return salary * 2; } }; var calculateBonus = function( level, salary ){ return strategies[ level ]( salary ); }; console.log( calculateBonus( 'S', 20000 ) ); // 輸出:80000 console.log( calculateBonus( 'A', 10000 ) ); // 輸出:30000
var performanceS = function () {}; performanceS.prototype.calculate = function (salary) { return salary * 4; }; var performanceA = function () {}; performanceA.prototype.calculate = function (salary) { return salary * 3; }; var performanceB = function () {}; performanceB.prototype.calculate = function (salary) { return salary * 2; }; //接下來定義獎金類Bonus: class Bonus { constructor() { this.salary = null; // 原始工資 this.strategy = null; // 績效等級對應的策略對象 } setSalary(salary) { this.salary = salary; // 設置員工的原始工資 } setStrategy(strategy) { this.strategy = strategy; // 設置員工績效等級對應的策略對象 } getBonus() { // 取得獎金數額 return this.strategy.calculate(this.salary); // 把計算獎金的操做委託給對應的策略對象 } } var bonus = new Bonus(); bonus.setSalary(10000); bonus.setStrategy(new performanceS()); // 設置策略對象 console.log(bonus.getBonus()); // 輸出:40000 bonus.setStrategy(new performanceA()); // 設置策略對象 console.log(bonus.getBonus()); // 輸出:30000
首先緩動算法的職責是實現小球如何運動
而後動畫類(即context)的職責是負責:
初始化動畫對象
在運動開始以前,須要提早記錄一些有用的信息,至少包括如下信息:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div style="position:absolute;background:blue" id="div">我是div</div> </body> <script> 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; } }; var Animate = function (dom) { this.dom = dom; // 進行運動的dom 節點 this.startTime = 0; // 動畫開始時間 this.startPos = 0; // 動畫開始時,dom 節點的位置,即dom 的初始位置 this.endPos = 0; // 動畫結束時,dom 節點的位置,即dom 的目標位置 this.propertyName = null; // dom 節點須要被改變的css 屬性名 this.easing = null; // 緩動算法 this.duration = null; // 動畫持續時間 }; Animate.prototype.start = function (propertyName, endPos, duration, easing) { this.startTime = +new Date; // 動畫啓動時間 this.startPos = this.dom.getBoundingClientRect()[propertyName]; // dom 節點初始位置 this.propertyName = propertyName; // dom 節點須要被改變的CSS 屬性名 this.endPos = endPos; // dom 節點目標位置 this.duration = duration; // 動畫持續事件 this.easing = tween[easing]; // 緩動算法 var self = this; var timeId = setInterval(function () { // 啓動定時器,開始執行動畫 if (self.step() === false) { // 若是動畫已結束,則清除定時器 clearInterval(timeId); } }, 16); }; Animate.prototype.step = function () { var t = +new Date; // 取得當前時間 if (t >= this.startTime + this.duration) { // (1) this.update(this.endPos); // 更新小球的CSS 屬性值 return false; } var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration); // pos 爲小球當前位置 this.update(pos); // 更新小球的CSS 屬性值 }; Animate.prototype.update = function (pos) { this.dom.style[this.propertyName] = pos + 'px'; }; var div = document.getElementById('div'); var animate = new Animate(div); animate.start('left', 500, 1000, 'linear'); // animate.start( 'top', 1500, 500, 'strongEaseIn' ); </script> </html>
<html> <body> <form action="http:// xxx.com/register" id="registerForm" method="post"> 請輸入用戶名:<input type="text" name="userName" /> 請輸入密碼:<input type="text" name="password" /> 請輸入手機號碼:<input type="text" name="phoneNumber" /> <button>提交</button> </form> <script> var registerForm = document.getElementById('registerForm'); registerForm.onsubmit = function () { if (registerForm.userName.value === '') { alert('用戶名不能爲空'); return false; } if (registerForm.password.value.length < 6) { alert('密碼長度不能少於6 位'); return false; } if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) { alert('手機號碼格式不正確'); return false; } } </script> </body> </html>
<html> <body> <form action="http:// xxx.com/register" id="registerForm" method="post"> 請輸入用戶名:<input type="text" name="userName" /> 請輸入密碼:<input type="text" name="password" /> 請輸入手機號碼:<input type="text" name="phoneNumber" /> <button>提交</button> </form> <script> var strategies = { isNonEmpty: function (value, errorMsg) { // 不爲空 if (value === '') { return errorMsg; } }, minLength: function (value, length, errorMsg) { // 限制最小長度 if (value.length < length) { return errorMsg; } }, isMobile: function (value, errorMsg) { // 手機號碼格式 if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } }; var validataFunc = function () { var validator = new Validator(); // 建立一個validator 對象 /***************添加一些校驗規則****************/ validator.add(registerForm.userName, 'isNonEmpty', '用戶名不能爲空'); validator.add(registerForm.password, 'minLength:6', '密碼長度不能少於6 位'); validator.add(registerForm.phoneNumber, 'isMobile', '手機號碼格式不正確'); var errorMsg = validator.start(); // 得到校驗結果 return errorMsg; // 返回校驗結果 } var registerForm = document.getElementById('registerForm'); registerForm.onsubmit = function () { var errorMsg = validataFunc(); // 若是errorMsg 有確切的返回值,說明未經過校驗 if (errorMsg) { alert(errorMsg); return false; // 阻止表單提交 } }; var Validator = function () { this.cache = []; // 保存校驗規則 }; Validator.prototype.add = function (dom, rule, errorMsg) { var ary = rule.split(':'); // 把strategy 和參數分開 this.cache.push(function () { // 把校驗的步驟用空函數包裝起來,而且放入cache var strategy = ary.shift(); // 用戶挑選的strategy ary.unshift(dom.value); // 把input 的value 添加進參數列表 ary.push(errorMsg); // 把errorMsg 添加進參數列表 return strategies[strategy].apply(dom, ary); }); }; Validator.prototype.start = function () { for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) { var msg = validatorFunc(); // 開始校驗,並取得校驗後的返回信息 if (msg) { // 若是有確切的返回值,說明校驗沒有經過 return msg; } } }; </script> </body> </html>
缺點:一 個文本輸入框只能對應一種校驗規則
<html> <body> <form action="http:// xxx.com/register" id="registerForm" method="post"> 請輸入用戶名:<input type="text" name="userName" /> 請輸入密碼:<input type="text" name="password" /> 請輸入手機號碼:<input type="text" name="phoneNumber" /> <button>提交</button> </form> <script> /***********************策略對象**************************/ var strategies = { isNonEmpty: function (value, errorMsg) { if (value === '') { return errorMsg; } }, minLength: function (value, length, errorMsg) { if (value.length < length) { return errorMsg; } }, isMobile: function (value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } }; /***********************Validator 類**************************/ var Validator = function () { this.cache = []; }; Validator.prototype.add = function (dom, rules) { var self = this; for (var i = 0, rule; rule = rules[i++];) { (function (rule) { var strategyAry = rule.strategy.split(':'); var errorMsg = rule.errorMsg; self.cache.push(function () { var strategy = strategyAry.shift(); strategyAry.unshift(dom.value); strategyAry.push(errorMsg); return strategies[strategy].apply(dom, strategyAry); }); })(rule) } }; Validator.prototype.start = function () { for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) { var errorMsg = validatorFunc(); if (errorMsg) { return errorMsg; } } }; /***********************客戶調用代碼**************************/ var registerForm = document.getElementById('registerForm'); var validataFunc = function () { var validator = new Validator(); validator.add(registerForm.userName, [{ strategy: 'isNonEmpty', errorMsg: '用戶名不能爲空' }, { strategy: 'minLength:6', errorMsg: '用戶名長度不能小於10 位' }]); validator.add(registerForm.password, [{ strategy: 'minLength:6', errorMsg: '密碼長度不能小於6 位' }]); var errorMsg = validator.start(); return errorMsg; } registerForm.onsubmit = function () { var errorMsg = validataFunc(); if (errorMsg) { alert(errorMsg); return false; } }; </script> </body> </html>