最近开始读源码了,遵循大多数过来人的建议,underscore.js是最适合新手阅读的一个js库,因此我就火烧眉毛地下载了underscore.js,1.8.3版本。javascript
与其余第三方库同样,underscore.js也是经过IIFE
来包裹本身的业务逻辑。java
(function(){ ... }.call(this))
经过传入this
(浏览器环境中其实就是window
对象)来改变函数的做用域。(function(){}.call(this))
等同于(function(){}())
,就变成熟知的IIFE的写法了。git
关于IIFE
和this
,附上两篇文章,有助于更好地认识相关问题。web
详解javascript当即执行函数表达式(IIFE)
如何理解JavaScript中的函数调用和“this”编程
_
(function(){ var root = this; // 定义underscore对象 var _ = function (obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // 把underscore对象暴露到全局做用域 if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } }.call(this));
能够看到代码中的_
被定义成了一个函数(暂时先无论函数里面的代码是什么意思),函数也是对象,因此能够在_
对象上添加一些方法。好比:segmentfault
(function(){ ... _.sayHello = function(name) { console.log('Hello, ' + name + '!'); }; }.call(this)); // 此时就能够在咱们本身的js代码中调用这个方法了 _.sayHello('underscore'); // 'Hello, underscore!'
这也是underscore.js大部分状况下的调用方式,_.funcName(arg1, arg2)
,文档中也是以这种方式调用的。设计模式
看,咱们好像也写出了一个类underscore.js库呢,好开森啊,哈哈~~~api
在函数式编程(FP)范式中,函数是拿来就用的,即使是对象,也只是函数的一个参数:浏览器
var arr = [1, 2, 3]; _.map(arr, function(item) { return item * 2; });
而在OOP中,函数通常是隶属于某个对象的方法:架构
var arr = [1, 2, 3]; arr.map(function(item) { return item * 2; });
underscore.js是推崇函数式编程的,可是也提供了OOP风格的函数调用方式,仅须要经过_()
来包裹一下对象便可:
var arr = [1, 2, 3]; // FP风格 _.map(arr, function(item) { return itme * 2; }); // OOP风格 _(arr).map(function(item) { return item * 2; });
经过_()
构造出一个实例对象,下面来分析一下这个构造过程:
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _([1, 2]); // 当执行这句代码时,函数中的this指向window // obj instanceof _ === false, this instanceof _ === false // 因此执行 return new _(obj) // 在new的时候,this指向了构造函数_的实例,因此 this instanceof _ === true // this._wrapped = obj; 表示构造实例有一个属性_wrapped,值为obj(供后面_.mixin方法调用) // 当obj已是一个实例的时候,_(obj)直接返回这个实例: _(_([1,2])) == _([1,2]) // 自此,构造过程就结束了 // 这也就是所谓的 无new调用的构造函数
_
的构造实例怎么调用_.func
这些方法呢?构造实例自己没有这些方法,那么这些方法应该存在原型链上,也就是_.prototype
上。_
又是如何把自身的方法挂载到_.prototype
上面呢?
思路:遍历_
的属性,若是某个属性的类型是function
,就把该函数挂载到_.prototype
上:
_.mixin = function(obj) { // _.functions(obj) 返回obj中全部的方法 _.each(_.functions(obj), function(name) { _.prototype[name] = function() { var args = [this._wrapped]; // _(args1).func(args2) == _.func(args1, args2) // 右侧func的参数比左侧func的参数多一个,也就是this._wrapped push.apply(args, arguments); return obj[name].apply(_, args); // 方法调用结果 }; }); }; _.mixin(_); // 把 _ 对象上的方法都挂载到其原型上
想要实现链式调用,一般咱们会在支持链式调用的函数中返回对象自己:
var car = { run: function() { console.log('begin run'); return this; }, stop: function() { console.log('stopped'); } }; car.run().stop(); // => begin run // => stopped
underscore.js中链式调用的实现:
// 开启一个链式调用 _.chain = function(obj) { var instance = _(obj); // 得到一个underscore实例 instance._chain = true; // 标识当前实例支持链式调用 return instance; }; // Helper函数,用来判断 实例调用某一方法以后的返回结果是否支持继续链式调用 var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; // 此时,咱们要把上面的_.mixin函数更新一下了 _.mixin = function(obj) { _.each(_.functions(obj), function(name) { _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, obj[name].apply(_, args)); // this指向underscore实例 }; }); }; // 因为链式调用的结果被转化为一个带有_chain属性的underscore实例对象, // {_wrapped: obj, _chain: true} // 因此想要获取链式调用的结果时,须要有个取值过程 _.prototype.value = function() { return this._wrapped; // 返回被包裹的对象 }; // 链式调用demo var testArr = [1, 2, 3]; _.chain(testArr) .map(function(item) { return item * 5; }) .each(function(item) { console.log(item); }) .first() .value(); // 5 // _(testArr).chain().map().each()... 这种方式也能够
mixin(混入)模式是增长代码复用度的一个普遍使用的设计模式:向一个对象混入一系列方法,使之具有更强大的能力,这一系列方法又包裹在一个称之为mixin的对象中,这样,其余对象也可以经过该mixin进行扩展。
underscore.js也容许用户扩充_
的功能,只须要在_.mixin函数上加上一行代码:
// 完整版 _.mixin = function(obj) { _.each(_.functions(obj), function(name) { // 当obj是自定义对象时, // obj的方法被扩充到underscore的方法集合中以及_.prototype上 var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, func.apply(_, args)); }; }); }; // demo _.mixin({ add5: function(num) { return num + 5; } }); _.add5(5); // 10 _([1,2,3]).chain().map(function(item) { return item * 10; }).first().add5().value(); // 15
理解了以上所说的内容,underscore.js的总体代码架构就掌握的差很少了,接下去就能够一个一个方法的去看具体代码细节。才刚读了这些内容,我就感受收货颇多,特别是读到Mixin模式的时候,感受像发现了新大陆似的,接下来会去再看一些其余类型的设计模式。
第一次读源码,认识不足或分析不到位的地方,还请路过的大大给以指正,谢谢!
如下两个连接的内容对于我理清underscore.js的代码结构起了很大帮助,很是感谢原做者。
underscore源码分析 - 这是本gitbook,写得特别好