我们一步一步来,先来解决实例方法支持链式调用的问题,我们前面已经实现了将静态方法映射成实例方法,前面实现的实例方法的返回值就是静态方法的返回值。为了实现链式调用,我们还需要实例方法计算完后还能够返回当前实例(也就是this),所以我们需要一个依据来判断应该返回计算结果还是当前实例。这个依据在Underscore里面是要用户给的,也就是显式调用chain方法。依据我们的分析,chain应该很简单,给一个依据来判断实例方法应该返回啥,也就是给当前实例设置一个标志位:
_.chain = function() { this._chain = true; return this; }chain就是这么简单,两行代码,然后我们的实例方法里面根据_chain来判断返回计算结果还是当前实例:
_.mixin = function(obj) { _.each(_.functions(obj), function(item){ var func = obj[item]; _[item] = func; _.prototype[item] = function() { var value = this._wrapped; var args = [value]; Array.prototype.push.apply(args, arguments); var res = func.apply(this, args); // 检查链式调用标记,如果是链式调用 // 将数据挂载到实例上,返回实例 var isChain = this._chain; if(isChain) { // 注意如果方法是chain本身,不要更新_wrapped,不然_wrapped会被改为chain的返回值,也就是一个实例 // 这里有点丑,后面优化 if(item !== 'chain') { this._wrapped = res; } return this; } return res; } }); }我们再来写个unique方法来验证下链式调用:
_.unique = function(array){ var result = []; var length = array.length; for(var i = 0; i < length; i++) { if(result.indexOf(array[i]) === -1){ result.push(array[i]); } } return result; }试下链式调用:
我们发现结果是对的,但是输出的是一个实例,不是我们想要的,所以我们还要一个方法来输出真正的计算结果,这个方法只能挂在原型上,不能写成静态方法,不然还会走到我们的mixin,会返回实例:
_.prototype.value = function() { return this._wrapped; }再来试一下呢:
静态方法支持链式调用静态方法也要支持链式调用,我们必须要让他的返回值也能够访问到实例方法才行。一般情况下静态方法的返回值是不能返回实例的,但是我们现在已经有了chain方法,我们直接让这个方法构造一个_实例返回就行了,上面的实例方法支持链式调用是利用了现成的实例,返回的this,但是如果chain返回一个新实例,也是兼容上面的,于是chain改为:
_.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }这样我们的静态方法chain也可以链式调用了,数据跟其他静态方法一样作为参数传给chain:
优化代码到这里我们的功能基本实现了,但是mixin函数还有需要优化的地方:
var res = func.apply(this, args);这里的this指向的是当前实例,但是一个方法作为静态方法调用时,比如_.map(),方法里面的this指向的是_,所以这里应该改成_。之前这里传this是因为chain里面操作的是this,现在已经改成新建实例,就不用传this,所以改为正确的_。
对item进行了特异性判断,前面之所以这么做,也是因为chain里面操作的是this,所以在apply里面其实已经设置了this._chain为true,所以会走到if里面去,现在新建实例了,走到apply的时候,设置的其实是res._chain,所以不会进到if,要调下一个实例方法的时候,this._chain才会是true,所以这个if可以直接去掉了。
_.mixin = function(obj) { _.each(_.functions(obj), function(item){ var func = obj[item]; _[item] = func; _.prototype[item] = function() { var value = this._wrapped; var args = [value]; Array.prototype.push.apply(args, arguments); var res = func.apply(_, args); var isChain = this._chain; if(isChain) { // if(item !== 'chain') { this._wrapped = res; // } return this; } return res; } }); }Underscore里面还将isChain的判断单独提成了一个方法,我这里没这么做了,放在一起看着还直观点。
总结本文主要讲解了Underscore源码的架构,并自己实现了一个简单的架子,部分变量名字和方法的具体实现可能不一样,但是原理是一样的。通过搭建这个简单的架子,其实我们学会了:
不用new构造实例对象
mixin怎么扩展静态方法到原型上
通过显式的调用chain来支持静态方法和实例方法的链式调用