本文是《JavaScript設計模式與開發實踐》的學習筆記,例子來源於書中,對於設計模式的見解,推薦看看本書做者的建議。html
給對象動態增長職責的方式成爲裝飾者模式。ajax
裝飾者模式可以在不改變對象自身的基礎上,在運行程序期間給對象動態地添加職責。這是一種輕便靈活的作法,裝飾者是一種「即付即用」的方式,好比天冷了就多穿一件外套。編程
想要爲函數添加一些功能,最簡單粗暴的方式就是直接改寫該函數,可是這是最差的辦法,直接違反了開放——封閉原則。設計模式
var a = function(){
alert(1)
}
// 改爲
var a = function(){
alert(1)
alert(2)
}
複製代碼
不少時候咱們不想碰原函數,也許原函數是其餘同事編寫的,甚至在一個古老的項目中,這個函數的源代碼被隱藏在一個咱們不肯觸碰的陰暗角落裏。如今須要不改變源代碼的狀況下,給函數增長功能。安全
咱們經過保存原引用的方式改寫某個函數。bash
var a = function(){
alert(1)
}
var _a = a
a = function(){
_a()
alert(2)
}
a()
複製代碼
這是實際開發中很常見的一個作法,好比咱們想給 window 綁定 onload 事件,可是又不肯定這個事件是否是已經被其餘人綁定過,爲了不覆蓋掉以前的 window.onload 函數中的行爲,先保存 window.onload,把它放入新的 window.onload。app
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 方法:學習
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)
})
複製代碼
<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>
複製代碼
<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();
}
複製代碼
做者提出,除了上面的兩個例子,在開發框架的時候也十分有用。框架裏的函數提供的是穩定而方便移植的功能,個性化的功能能夠在框架以外動態裝飾上去,能夠避免爲了讓框架有更多功能而去使用一些if、else語句預測用戶的實際須要。