能夠動態地給某個對象添加一些額外的職責,而不會影響從這個類中派生的其餘對象。在程序開發中,許多時候都並不但願某個類天生就很是龐大,一次性包含許多職責。那麼咱們就可使用裝飾者模式。app
1 var Plane = function(){} 2 Plane.prototype.fire = function(){ 3 console.log( '發射普通子彈' ); 4 } 5 var one = function( plane ){ //裝飾類 one
6 this.plane = plane; 7 } 8 one.prototype.fire = function(){ 9 this.plane.fire(); 10 console.log( '發射導彈' ); 11 } 12 var two = function( plane ){ //裝飾類 two
13 this.plane = plane; 14 } 15 two.prototype.fire = function(){ 16 this.plane.fire(); 17 console.log( '發射原子彈' ); 18 } 19
20 var plane = new Plane(); 21 plane = new one( plane ); //裝飾對象會覆蓋原來的對象,這麼作是由於裝飾者模式是爲了添加職能,可能這時候的代碼量已經很大了,原對象的方法可能在不少地方都調用過,因此咱們經過這種方式,追加了功能而不用修改太多代碼。
22 plane = new two( plane ); 23
24 /**裝飾對象調用**/
25 plane.fire(); /* 發射普通子彈 26 發射導彈 27 發射原子彈 28 */
從代碼咱們能夠看出,裝飾者模式這種給對象動態增長職責的方式,並無真正地改動對象自身。函數
從形式上來看,裝飾者模式就好像給原來的對象包裹了一層,產生一個新的對象,同時,保留了接口統一。裝飾類的做用就是接受一個對象,而後把它變成功能更多的對象。性能
裝飾類接收原來的對象,會把它保存下來,調用裝飾類時,會將原本的對象放入另外一個對象之中,這些對象以一條鏈的方式進行引用,造成一個聚合對象。這些對象都擁有相同的接口(fire方法),當請求達到鏈中的某個對象時,這個對象會執行自身的操做,隨後把請求轉發給鏈中的下一個對象。this
從傳遞請求這一點來看有點相似於職責鏈,可是職責鏈是傳遞請求給正確執行的對象,而這個不但自身會執行,經過裝飾類添加的方法也會執行,它這種相似於鏈子同樣的請求調用與其說是鏈子倒不如說是包裹更合適。裝飾類會把原來的對象包裹起來,調用時會一層一層的調用。spa
由於js是動態語言,修改類型,改變對象的屬性和方法對它來講太簡單了,因此還有一種思路,咱們拿到須要調用的方法的引用,而後直接對它進行改造便可。不過這種方式等於直接修改原對象的方法,和上面的方法哪一個更好,我說不上來。prototype
1 var plane = { //原對象 2 fire: function(){ 3 console.log( '發射普通子彈' ); 4 } 5 } 6 var add_one = function(){ //想要添加的功能 7 console.log( '發射導彈' ); 8 } 9 var add_two = function(){ 10 console.log( '發射原子彈' ); 11 } 12 13 var fire1 = plane.fire; //存儲對象方法的引用 14 plane.fire = function(){ //改寫對象方法 15 fire1(); //原對象的方法 16 add_one(); //添加的方法 17 } 18 19 var fire2 = plane.fire; //同上 20 plane.fire = function(){ 21 fire2(); 22 add_two(); 23 } 24 25 plane.fire(); /* 發射普通子彈 26 發射導彈 27 發射原子彈 28 */
另外我還有一個不成熟的想法,像第一種實現裝飾者的方法裏,咱們覆蓋了原來對象的變量。使得全部調用對應方法的位置都會執行添加的函數,可是若是咱們不是全都須要而是有的地方須要有的地方不須要呢?code
我有兩個思路,一個是在裝飾類裏重寫方法時進行判斷,另外一個是把原來的對象單獨給個變量,和裝飾類加工過的對象有所區分,不須要執行新功能的地方,就用原來的對象調用。對象
感受好像第一個思路好一點?blog
在js中,函數也是對象,只要是對象天然能夠實現裝飾者。接口
1 var a=function(){ 2 alert(1); 3 } 4
5 var _a=a; //保存函數引用
6
7 a=function(){ //重寫函數
8 _a(); //本來的函數引用
9 alert(2); //新添加的功能
10 }
又好比咱們想給window綁定onload事件,可是又不肯定這個事件是否是已經被其餘人綁定過,爲了不覆蓋掉以前的window.onload函數中的行爲,咱們通常都會先保存好原先的window.onload,把它放入新的window.onload 裏執行:
1 window.onload = function(){ 2 alert (1); 3 } 4 var _onload = window.onload || function(){}; //沒有綁過onload,讓變量爲空的函數 5
6 window.onload = function(){ //重寫 7 _onload(); 8 alert (2); //追加的代碼 9 }
可能的坑:this
保存引用再重寫,可能會帶來this綁定錯誤的問題,由於咱們保存的引用中的this和當前執行環境的this可能徹底不一樣了,重寫的函數有時候上下文其實已經改變了,若是上下文改變,this的指向也就改變了,因此它有可能沒有指向正確的對象。
1 var _getElementById = document.getElementById; //這個是document這個對象的方法,其內部this,指向document,咱們把它的引用保存到_getElementById這個全局變量裏
2 document.getElementById = function( id ){ //重寫函數
3 alert (1); 4 return _getElementById( id ); // 返回了保留的函數,可是返回的函數是全局函數,this指向window,沒有指向正確的document對象
5 } 6 var button = document.getElementById( 'button' ); //報錯,Uncaught TypeError: Illegal invocation
又好比:
1 var name="大白"; 2 var a={ 3 name:"如花", 4 getname:function(){ 5 console.log(this.name); 6 } 7 } 8
9 var get=a.getname; //保存引用
10
11 get(); //大白
12 a.getname(); //如花
能夠看到,調用函數時的環境改變,雖然保存了引用,卻沒獲得咱們想要的結果。(咱們想得到如花)
若是遇到這種狀況,也就只能手動更改(apply),或者控制在同一個上下文環境中添加職責。
好比上面的代碼,咱們改爲:
get.apply(a); //如花
就能夠得到正確的結果。
第一個例子也是同理
1 var _getElementById = document.getElementById; 2 document.getElementById = function(){ 3 alert (1); 4 return _getElementById.apply( document, arguments ); //apply大顯身手,指定上下文爲document
5 } 6 var button = document.getElementById( 'button' );
裝飾者模式能夠說是AOP思想的實例。優勢:解耦,追加功能不用修改本來函數,更爲靈活方便。缺點:會疊加函數的做用域,若是裝飾的鏈條過長,性能上也會受到一些影響。