有時我們不希望某個類天生就非常龐大,一次性包含許多職責。那我們就可以使用裝飾著模式。
裝飾著模式可以動態地為某個物件添加一些額外的職責,從而不影響在這個類別中衍生的其他物件。
裝飾著模式將一個物件嵌入另一個物件之中,實際上相當於這個物件被另一個物件包裝起來,形成一條包裝鏈。
一、不改動原函數的情況下,請為該函數增加一些額外的功能
1. 保存原引用
window.onload = function() { console.log(1); }; var _onload = window.onload || function() {}; window.onload = function() { _onload(); console.log(2); }
問題:
(1)必須維護中間變數
(2)可能遇到this被劫持問題
在window.onload的例子中沒有這個煩惱,是因為呼叫普通函數_onload時,this也指向window,跟呼叫window.onload時一樣。
2. this被劫持:
var _getElementById = document.getElementById; document.getElementById = function(id) { console.log(1); return _getElementById(id); } return _getElementById(id); // 报错“Uncaught TypeError: Illegal invocation”
因為_getElementById是全域函數,當呼叫全域函數時,this是指向window的,而document.getElementById中this預期指向document。
3. 解決this被劫持:
var _getElementById = document.getElementById; document.getElementById = function(id) { console.log(1); return _getElementById.call(document, id); }
二、用AOP裝飾函數
/* 让新添加的函数在原函数之前执行(前置装饰)*/ Function.prototype.before = function(beforefn) { var _self = this; return function() { 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; }; };
document.getElementById = document.getElementById.before(function() { console.log(1); });
三、避免污染原型
var before = function(fn, beforefn) { return function() { beforefn.apply(this, arguments); return fn.apply(this, arguments); }; }; var after = function(fn, afterfn) { return function() { var ret = fn.apply(this, arguments); afterfn.apply(this, arguments); return ret; }; }; document.getElementById = before(document.getElementById, function(){ console.log(1); });
四、範例–外掛程式的表單驗證
結合《學習JavaScript設計模式之策略模式》中的【表單驗證】,運用到ajax提交資料驗證,效果很棒!
修改上述before方法
var before = function(fn, beforefn) { return function() { if(beforefn.apply(this, arguments) === false) { // beforefn返回false,直接return,不执行后面的原函数 return; } return fn.apply(this, arguments); }; };
/* 模拟数据验证*/ var validate = function() { if(username === "") { console.log("验证失败!"); return false; } return true; } /* 模拟ajax提交*/ var formSubmit = function() { console.log("提交!!!"); }
username = 1; formSubmit = before(formSubmit, validate); // 提交!!! formSubmit(); username = ""; formSubmit = before(formSubmit, validate); // 验证失败! formSubmit();
五、裝飾者模式與代理模式
相同點:這兩種模式都描述了怎麼為物件提供一定程度上的間接引用,它們的實作部分都保留了對另外一個物件的引用,並且向那個物件發送請求。
區別:
(1)代理模式:當直接存取本地不方便或不符合需求時,為這個本體提供一個替代者。本地定義關鍵功能,而代理提供或拒絕對它的訪問,或在訪問本體之前走一些額外的事情。 (其做的事情還是跟本體一樣)
(2)裝飾者模式:為物件動態加入行為。 (一開始無法確定物件的全部功能,實實在在的為物件增加新的職責與行為)
希望本文所述對大家學習javascript程式設計有所幫助。