這篇繼續說js的現代複用模式:混入、借用方法和綁定。javascript
混入java
能夠針對前面提到的經過屬性複製實現代碼複用的想法進行一個擴展,就是混入(mix-in)。混入並非複製一個完整的對象,而是從多個對象中複製出任意的成員並將這些成員組合成一個新的對象。數組
混入的實現並不難,只須要遍歷每一個參數,而且複製出傳遞給這個函數的每一個對象中的每一個屬性。閉包
function mix(){ var arg,prop,child={}; for(arg=0;arg<arguments.length;arg++){ for(prop in arguments[arg]){ if(arguments[arg].hasOwnProperty(prop)){ child[prop] = arguments[arg][prop]; } } } return child; }
如今,有了一個通用的mix-in函數,能夠向它傳遞任意數量的對象,其結果將得到一個具備全部源對象屬性的新對象,一個調用的例子:app
var cake = mix( {eggs : 2,large : true}, {butter : 1,sorted : true}, {flour : "3 cups"}, {suger : "sure!"} ); console.dir(cake);
下面是控制檯的輸出:函數
butter 1 eggs 2 flour "3 cups" large true sorted true sugar "sure!"
借用方法this
有時可能剛好僅須要現有對象其中的一個或兩個方法,在想要重用方法的同時,又不但願和源對象是父子的繼承關係,也就是隻想使用所須要的方法,而不須要那些永遠用不到的其餘方法。這種狀況下,可使用借用方法(borrowing method)來實現,即便用call()和apply(),區別就是傳參的區別。spa
下面是一個例子,借用了數組的方法:prototype
function f(){ var args = [].slice.call(arguments,1,3); return args; } f(1,2,3,4,5,6);//[2,3]
其中建立空數組是爲了使用數組的slice方法,也能夠從Array的原型中借用方法,即Array.prototype.slice.call,這個須要輸更長的字符,可是能夠節省建立一個空數組的工做。
借用方法,不是經過call()和apply()就是經過簡單的賦值,在借用方法的內部,this所指向的對象是基於調用表達式而肯定的,但更多時候,最好能夠鎖定this的值,或者把它綁定到特定對象並預先肯定該對象。指針
參考下面的例子,one對象有一個say()的方法:
var one = { name : "object", say : function(greet){ return greet+","+this.name; } }; one.say("hi");//"hi,object"
另外一個對象two中沒有say方法,可是能夠從one那裏借用:
var two = { name : "another object" }; one.say.apply(two,["hello"]);//"hello,another object"
上面借用的say()方法中的this指向了two,因此this.name是"another object".可是在什麼場景中,應該給函數指針賦值一個全局變量,或者將函數做爲回調函數傳遞?在程序中有這樣的應用,而且出現了問題。
var say = one.say; say("hoho");//"hoho,undefined" var yetanother = { name : "Yet another object", method : function(callback){ return callback("Hola"); } }; yetanother.method(one.say);//"Hola,undefined"
在上面兩種狀況下this都指向了全局對象,而且代碼都沒有按預期運行。爲了綁定對象與方法之間的關係,能夠用下面的一個簡單的函數:
function bind(o,m){ return function(){ return m.apply(o,arguments); }; }
bind()接受了一個對象o和一個方法m,並將它們綁定起來,而後返回另外一個函數。返回的函數能夠經過閉包來訪問o和m。因此在bind()返回後仍然能夠訪問o和m.可使用bind()建立一個新函數:
var twosay = bind(two,one.say); twosay("yo");//"yo,another object"
不管怎麼調用twosay(),這個方法老是綁定到對象two上。
ES5中的bind()
ES5將bind()添加到Function.prototype,使得bind()像call()apply()同樣易用。能夠執行下面的表達式:
var newFunc = obj.someFunc.bind(myobj,1,2,3);
就是將someFunc()與myobj綁定到一塊兒,並填充someFunc()的前3個參數。
在不支持ES5的環境下面運行的時候,看看怎麼實現Function.prototype.bind():
if (typeof Function.prototype.bind === "undefined"){ Function.prototype.bind = function(thisArg){ var fn = this, slice = Array.prototype.slice, args = slice.call(arguments,1); return function(){ return fn.apply(thisArg,args.concat(slice.call(arguments))); }; }; }
它拼接了參數列表,即傳給bind()的參數(第一個除外),以及那些傳給由bind()返回新函數的參數,新函數將在後面調用。一個調用例子:
var twosay2 = one.say.bind(two); twosay2("Bonjour");//"Bonjour,another object"
也能夠傳遞一個參數:
var twosay3 = one.say.bind(two,"Nihao"); twosay3();//"Nihao,another object"
小結
在javascript中可能並不會像C#或Java同樣常常面臨繼承的問題,一些緣由是js庫用一些方法解決了這個問題,另外一些緣由是在js中不多須要創建長並且複雜的繼承鏈。在靜態強類型語言中,繼承多是惟一複用代碼的方法,但在js中常常有更簡潔而且優雅的方法,包括借用方法,綁定,複製屬性,及從多個對象中混入屬性等方法。畢竟,代碼重用纔是最終目的,繼承只是實現這個目標的方法之一。
--end--