其实最简单的方法就是写两个函数,一个是静态方法,一个是实例方法。但是如果我们这样做了,这两个函数内部处理的逻辑其实是高度相似的,可能只是参数稍微有点不同而已。这肯定不是一个优雅的程序员应该做的。Underscore给出的方法就是所有方法先写成静态方法,然后用一个统一的函数来将所有的静态方法挂载到原型上,让他成为一个实例方法。我们试着一步一步的来实现下。
先写一个静态方法我们先来写一个简单的map方法,将它挂载到_上成为静态方法:
_.map = function(array, callback) { var result = []; var length = array.length; for(var i = 0; i< length; i++) { var res = callback(array[i]); result[i] = res; } return result; }这个方法写完其实就可以直接用了,用上面那个例子调用如下:
映射成实例方法在Underscore里面是用一个mixin方法来将静态方法映射到原型上的,mixin方法接收一个对象作为参数,然后将这个对象上的方法全部复制到原型上。具体流程如下:
取出参数里面的函数属性,将其塞入一个数组
遍历这个数组,将里面的每个项设置到原型上
设置原型的时候注意处理下实例方法和静态方法的参数
下面来看看代码:
_.mixin = function(obj) { // 遍历obj里面的函数属性 _.each(_.functions(obj), function(item){ // 取出每个函数 var func = obj[item]; // 在原型上设置一个同名函数 _.prototype[item] = function() { // 注意这里,实例方法待处理数据是构造函数接收的参数,改造构造函数的代码在后面 // 这里将数据取出来作为静态方法的第一个参数 var value = this._wrapped; var args = [value]; // 将数据和其他参数放到一个数组里面,作为静态方法的参数 Array.prototype.push.apply(args, arguments); // 用处理好的参数来调用静态方法 var res = func.apply(this, args); // 将结果返回 return res; } }); } // 上面的mixin写好后不要忘了调用一下,将_自己作为参数传进去 _.mixin(_); // 构造函数需要接收处理的数据 // 并将它挂载到this上,这里的this是实例对象 function _(value){ if(!(this instanceof _)) { return new _(value); } this._wrapped = value; }上面的_.mixin(_);调用之后就会将_上的静态方法全部映射到原型上,这样_()返回的实例也有了所有的静态方法,这就让_支持了两种调用方式。可能有朋友注意到,我们上面的代码还有each和functions两个辅助方法,我们也来实现下这两个方法:
// functions就是取出对象上所有函数的名字,塞到一个数组里面返回 _.functions = function(obj){ var result = []; for(var key in obj) { if(typeof obj[key] === 'function'){ result.push(key); } } return result; } // each就是对一个数组进行遍历,每个都执行下callback _.each = function(array, callback){ var length = array.length; for(var i = 0; i < length; i++) { callback(array[i]); } } mixin顺便支持插件Underscore的mixin不仅让他支持了静态和实例方法两种调用方式,同时因为他自己也是_的一个静态方法,我们也是可以拿来用的。官方支持自定义插件就是用的这个方法,下面是官方例子:
_.mixin({ capitalize: function(string) { return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); } }); _("fabio").capitalize(); // Fabio其实我们前面写的那个mixin方法已经支持将自定义方法作为实例方法了,但是还差一点,还差静态方法,所以我们再加一行代码,同时将接收到的参数赋值给_就行了:
_.mixin = function(obj) { _.each(_.functions(obj), function(item){ var func = obj[item]; // 注意这里,我们同时将这个方法赋值给_作为静态方法,这下就完全支持自定义插件了 _[item] = func; _.prototype[item] = function() { var value = this.value; var args = [value]; Array.prototype.push.apply(args, arguments); var res = func.apply(this, args); return res; } }); } 支持链式调用链式调用也很常见,比如jQuery的点点点,我在另一篇文章详细讲解过这种实例方法的链式调用怎么实现,关键是每个实例方法计算完成后都返回当前实例,对于实例方法来说,当前实例就是this。这种方式也适用于Underscore,但是Underscore因为自身需求和API结构的原因,他的链式调用需要支持更多场景:
Underscore的实例方法还支持直接调用返回结果,不能简单的返回实例
Underscore的静态方法也要支持链式调用
实例方法支持链式调用