本文是《JavaScript設計模式與開發實踐》的學習筆記,例子來源於書中,對於設計模式的見解,推薦看看本書做者的建議。html
給對象動態增長職責的方式成爲裝飾者模式。ajax
裝飾者模式可以在不改變對象自身的基礎上,在運行程序期間給對象動態地添加職責。這是一種輕便靈活的作法,裝飾者是一種「即付即用」的方式,好比天冷了就多穿一件外套。編程
想要爲函數添加一些功能,最簡單粗暴的方式就是直接改寫該函數,可是這是最差的辦法,直接違反了開放——封閉原則。設計模式
var a = function(){ alert(1) } // 改爲 var a = function(){ alert(1) alert(2) }
不少時候咱們不想碰原函數,也許原函數是其餘同事編寫的,甚至在一個古老的項目中,這個函數的源代碼被隱藏在一個咱們不肯觸碰的陰暗角落裏。如今須要不改變源代碼的狀況下,給函數增長功能。安全
咱們經過保存原引用的方式改寫某個函數。app
var a = function(){ alert(1) } var _a = a a = function(){ _a() alert(2) } a()
這是實際開發中很常見的一個作法,好比咱們想給 window 綁定 onload 事件,可是又不肯定這個事件是否是已經被其餘人綁定過,爲了不覆蓋掉以前的 window.onload 函數中的行爲,先保存 window.onload,把它放入新的 window.onload。函數
window.onload = function(){ alert(1) } var _onload = window.onload || function(){} window.onload = funtion(){ _onload() alert(2) }
這樣的代碼是符合封閉——開放原則,咱們在增長新功能的時候確實沒有修改原來的代碼,可是這種方式存在兩個問題:學習
AOP(Aspect Oriented Programming)面向切面編程的主要做用是:把一些跟核心業務邏輯無關的功能抽離出來,這些跟業務邏輯無關的功能一般包括日誌統計、安全控制、異常處理等。把這些功能抽離出來之後,再經過「動態織入」的方式摻入業務邏輯模塊中。這樣的好處首先是能夠保持業務邏輯模塊的純淨和高內聚性,其次是能夠很方便地複用日誌統計等功能模塊。優化
首先給出 Function.prototype.before 和 Function.prototype.after 方法:this
Function.prototype.before = function(beforefn){ // 保存原函數的引用 var _self = this // 返回包含原函數和新函數的「代理函數」 return function(){ // 執行新函數,保證this不被劫持, // 新函數接受的參數也會被原封不動地傳入原函數, // 新函數在原函數以前執行 beforefn.apply(this,arguments) return _self.apply(this.arguments) } } Function.prototype.after = function(afterfn){ var _self = this return function(){ var ret = _self.apply(this,arguments) afterfn.apply(this,arguments) return ret } }
「代理函數」只是結構上像代理而已,並不承擔代理的職責(好比控制對象的訪問),它的工做是把請求分別轉發給新添加的函數和原函數,且負責保證它們的執行順序。
再回到 window.onload 的例子中,用 Function.prototype.after 來增長新事件:
window.onload = function(){ alert(1) } window.onload = (window.onload || function(){}).after(function(){ alert(2) }).after(function(){ alert(3) })
(1)數據統計上報
<html> <button tag="login" id="button">點擊打開登陸浮層</button> <script> var showLogin = function(){ console.log( '打開登陸浮層' ) log( this.getAttribute( 'tag' ) ) } var log = function( tag ){ console.log( '上報標籤爲: ' + tag ) } document.getElementById( 'button' ).onclick = showLogin </script> </html>
showLogin 函數既要負責打開浮層,又要負責數據上報,兩個功能耦合在一個函數裏,使用 AOP 分離:
<html> <button tag="login" id="button">點擊打開登陸浮層</button> <script> Function.prototype.after = function( afterfn ){ var __self = this; return function(){ var ret = __self.apply( this, arguments ) afterfn.apply( this, arguments ) return ret } } var showLogin = function(){ console.log( '打開登陸浮層' ) } var log = function(){ console.log( '上報標籤爲: ' + this.getAttribute( 'tag' ) ) } showLogin = showLogin.after( log ); // 打開登陸浮層以後上報數據 document.getElementById( 'button' ).onclick = showLogin; </script> </html>
(2) 插件式表單驗證
<html> <body> 用戶名:<input id="username" type="text"/> 密碼: <input id="password" type="password"/> <input id="submitBtn" type="button" value="提交"></button> </body> <script> var username = document.getElementById( 'username' ), password = document.getElementById( 'password' ), submitBtn = document.getElementById( 'submitBtn' ); var formSubmit = function(){ if ( username.value === '' ){ return alert ( '用戶名不能爲空' ); } if ( password.value === '' ){ return alert ( '密碼不能爲空' ); } var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); // ajax 具體實現略 } submitBtn.onclick = function(){ formSubmit(); } </script> </html>
formatSubmit 函數承擔了兩個職責,除了提交ajax請求,還要驗證用戶輸入的合法性。咱們把校驗輸入的邏輯放到validata函數中,並約定當validata函數返回false的時候表示校驗未經過。
var validata = function(){ if ( username.value === '' ){ alert ( '用戶名不能爲空' ); return false; } if ( password.value === '' ){ alert ( '密碼不能爲空' ); return false; } } var formSubmit = function(){ if ( validata() === false ){ // 校驗未經過 return; } var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); } submitBtn.onclick = function(){ formSubmit(); }
使用AOP優化代碼
Function.prototype.before = function( beforefn ){ var __self = this; return function(){ if ( beforefn.apply( this, arguments ) === false ){ // beforefn 返回false 的狀況直接return,再也不執行後面的原函數 return; } return __self.apply( this, arguments ); } } var validata = function(){ if ( username.value === '' ){ alert ( '用戶名不能爲空' ); return false; } if ( password.value === '' ){ alert ( '密碼不能爲空' ); return false; } } var formSubmit = function(){ var param = { username: username.value, password: password.value } ajax( 'http:// xxx.com/login', param ); } formSubmit = formSubmit.before( validata ); submitBtn.onclick = function(){ formSubmit(); }