这篇继续说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--