本文同步自我得博客:http://www.joeray61.com
最近准备折腾一下backbone.js,在事先了解了backbone之后,我知道了backbone对underscore这个库有着强依赖,正好underscore之前也没使用过,于是我就想先把underscore彻底了解一下,这样之后折腾backbone的时候也少一点阻碍。
*underscore*是一个很实用且小巧的框架,提供了很多我们在编程时需要的基本功能函数,而且他没有扩展*javascript*的原生对象,主要涉及对*Object*、*Array*、*Function*的操作。
我曾经问我的朋友[@小胡子哥][1] 怎么学习一个框架?他给我的回答是:“直接看源码。”现在想想深感同意,因为研究源码是最直接的学习途径,可以深入地了解这个框架的思想和精髓,同时也能学习框架作者的编程技巧,提升自己的coding水平。
好了,题外话就说到这里,下面咱们进入正题。
简化源码看结构
我这次看的underscore版本是1.3.3,整个文件也就1000多行,我把代码简化了一下,并加入了相关的注释:
// underscore的代码包裹在一个匿名自执行函数中
(function() {
// 创建一个全局对象, 在浏览器中表示为window对象, 在Node.js中表示global对象
var root = this;
// 保存"_"(下划线变量)被覆盖之前的值
// 如果出现命名冲突或考虑到规范, 可通过_.noConflict()方法恢复"_"被Underscore占用之前的值, 并返回Underscore对象以便重新命名
var previousUnderscore = root._;
// 创建一个空的对象常量, 便于内部共享使用
var breaker = {};
// 将内置对象的原型链缓存在局部变量
var ArrayProto = Array.prototype,
ObjProto = Object.prototype,
FuncProto = Function.prototype;
// 将内置对象原型中的常用方法缓存在局部变量
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// 这里定义了一些JavaScript 1.6提供的新方法
// 如果宿主环境中支持这些方法则优先调用, 如果宿主环境中没有提供, 则会由Underscore实现
var nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// 创建对象式的调用方式, 将返回一个Underscore包装器, 包装器对象的原型中包含Underscore所有方法(类似与将DOM对象包装为一个jQuery对象)
var _ = function(obj) {
// 所有Underscore对象在内部均通过wrapper对象进行构造
return new wrapper(obj);
};
// 针对不同的宿主环境, 将Undersocre的命名变量存放到不同的对象中
if( typeof exports !== 'undefined') {// Node.js环境
if( typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {// 浏览器环境中Underscore的命名变量被挂在window对象中
root['_'] = _;
}
// 版本声明
_.VERSION = '1.3.3';
//在_对象上定义各种方法
. . . . . .
// underscore对象的包装函数
var wrapper = function(obj) {
// 原始数据存放在包装对象的_wrapped属性中
this._wrapped = obj;
};
// 将Underscore的原型对象指向wrapper的原型, 因此通过像wrapper原型中添加方法, Underscore对象也会具备同样的方法
_.prototype = wrapper.prototype;
// 返回一个对象, 如果当前Underscore调用了chain()方法(即_chain属性为true), 则返回一个被包装的Underscore对象, 否则返回对象本身
// result函数用于在构造方法链时返回Underscore的包装对象
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// 将一个自定义方法添加到Underscore对象中(实际是添加到wrapper的原型中, 而Underscore对象的原型指向了wrapper的原型)
var addToWrapper = function(name, func) {
// 向wrapper原型中添加一个name函数, 该函数调用func函数, 并支持了方法链的处理
wrapper.prototype[name] = function() {
// 获取func函数的参数, 并将当前的原始数据添加到第一个参数
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
// 执行函数并返回结果, 并通过result函数对方法链进行封装, 如果当前调用了chain()方法, 则返回封装后的Underscore对象, 否则返回对象本身
return result(func.apply(_, args), this._chain);
};
};
// 将内部定义的_(即Underscore方法集合对象)中的方法复制到wrapper的原型链中(即Underscore的原型链中)
// 这是为了在构造对象式调用的Underscore对象时, 这些对象也会具有内部定义的Underscore方法
_.mixin(_);
// 将Array.prototype中的相关方法添加到Underscore对象中, 因此在封装后的Underscore对象中也可以直接调用Array.prototype中的方法
// 如: _([]).push()
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
// 获取Array.prototype中对应方法的引用
var method = ArrayProto[name];
// 将该方法添加到Underscore对象中(实际是添加到wrapper的原型对象, 因此在创建Underscore对象时同时具备了该方法)
wrapper.prototype[name] = function() {
// _wrapped变量中存储Underscore对象的原始值
var wrapped = this._wrapped;
// 调用Array对应的方法并返回结果
method.apply(wrapped, arguments);
var length = wrapped.length;
if((name == 'shift' || name == 'splice') && length === 0)
delete wrapped[0];
// 即使是对于Array中的方法, Underscore同样支持方法链操作
return result(wrapped, this._chain);
};
});
// 作用同于上一段代码, 将数组中的一些方法添加到Underscore对象, 并支持了方法链操作
// 区别在于上一段代码所添加的函数, 均返回Array对象本身(也可能是封装后的Array), concat, join, slice方法将返回一个新的Array对象(也可能是封装后的Array)
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// 对Underscore对象进行链式操作的声明方法
wrapper.prototype.chain = function() {
// this._chain用来标示当前对象是否使用链式操作
// 对于支持方法链操作的数据, 一般在具体方法中会返回一个Underscore对象, 并将原始值存放在_wrapped属性中, 也可以通过value()方法获取原始值
this._chain = true;
return this;
};
// 返回被封装的Underscore对象的原始值(存放在_wrapped属性中)
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);
小结
underscore这个库的结构(或者说思路)大致是这样的:
创建一个包装器, 将一些原始数据进行包装,所有的*undersocre*对象, 内部均通过*wrapper*函数进行构造和封装
*underscore*与*wrapper*的内部关系是:
内部定义变量_, 将underscore相关的方法添加到_, 这样就可以支持函数式的调用, 如_.bind()
内部定义wrapper类, 将_的原型对象指向wrapper类的原型
将underscore相关的方法添加到wrapper原型, 创建的_对象就具备了underscore的方法
将Array.prototype相关方法添加到wrapper原型, 创建的_对象就具备了Array.prototype中的方法
-
new _()时实际创建并返回了一个wrapper()对象, 并将原始数组存储到_wrapped变量, 并将原始值作为第一个参数调用对应方法
之后我会对underscore中所有方法的具体实现进行介绍,感谢关注