Underscore源码解析(一)
最近准备折腾一下backbone.js,在事先了解了backbone之后,我知道了backbone对underscore这个库有着强依赖,正好underscore之前也没使用过,于是我就想先把underscore彻底了解一下,这样之后折腾backbone的时候也少一点阻碍。
简化源码看结构
我这次看的underscore版本是1.3.3,整个文件也就1000多行,我把代码简化了一下,并加入了相关的注释:
1 // underscore的代码包裹在一个匿名自执行函数中 2 (function() { 3 4 // 创建一个全局对象, 在浏览器中表示为window对象, 在Node.js中表示global对象 5 var root = this; 6 7 // 保存"_"(下划线变量)被覆盖之前的值 8 // 如果出现命名冲突或考虑到规范, 可通过_.noConflict()方法恢复"_"被Underscore占用之前的值, 并返回Underscore对象以便重新命名 9 var previousUnderscore = root._; 10 11 // 创建一个空的对象常量, 便于内部共享使用 12 var breaker = {}; 13 14 // 将内置对象的原型链缓存在局部变量 15 var ArrayProto = Array.prototype, 16 ObjProto = Object.prototype, 17 FuncProto = Function.prototype; 18 19 // 将内置对象原型中的常用方法缓存在局部变量 20 var slice = ArrayProto.slice, 21 unshift = ArrayProto.unshift, 22 toString = ObjProto.toString, 23 hasOwnProperty = ObjProto.hasOwnProperty; 24 25 // 这里定义了一些JavaScript 1.6提供的新方法 26 // 如果宿主环境中支持这些方法则优先调用, 如果宿主环境中没有提供, 则会由Underscore实现 27 var nativeForEach = ArrayProto.forEach, 28 nativeMap = ArrayProto.map, 29 nativeReduce = ArrayProto.reduce, 30 nativeReduceRight = ArrayProto.reduceRight, 31 nativeFilter = ArrayProto.filter, 32 nativeEvery = ArrayProto.every, 33 nativeSome = ArrayProto.some, 34 nativeIndexOf = ArrayProto.indexOf, 35 nativeLastIndexOf = ArrayProto.lastIndexOf, 36 nativeIsArray = Array.isArray, 37 nativeKeys = Object.keys, 38 nativeBind = FuncProto.bind; 39 40 // 创建对象式的调用方式, 将返回一个Underscore包装器, 包装器对象的原型中包含Underscore所有方法(类似与将DOM对象包装为一个jQuery对象) 41 var _ = function(obj) { 42 // 所有Underscore对象在内部均通过wrapper对象进行构造 43 return new wrapper(obj); 44 }; 45 46 // 针对不同的宿主环境, 将Undersocre的命名变量存放到不同的对象中 47 if( typeof exports !== 'undefined') {// Node.js环境 48 if( typeof module !== 'undefined' && module.exports) { 49 exports = module.exports = _; 50 } 51 exports._ = _; 52 } else {// 浏览器环境中Underscore的命名变量被挂在window对象中 53 root['_'] = _; 54 } 55 56 // 版本声明 57 _.VERSION = '1.3.3'; 58 59 //在_对象上定义各种方法 60 . . . . . . 61 62 // underscore对象的包装函数 63 var wrapper = function(obj) { 64 // 原始数据存放在包装对象的_wrapped属性中 65 this._wrapped = obj; 66 }; 67 68 // 将Underscore的原型对象指向wrapper的原型, 因此通过像wrapper原型中添加方法, Underscore对象也会具备同样的方法 69 _.prototype = wrapper.prototype; 70 71 // 返回一个对象, 如果当前Underscore调用了chain()方法(即_chain属性为true), 则返回一个被包装的Underscore对象, 否则返回对象本身 72 // result函数用于在构造方法链时返回Underscore的包装对象 73 var result = function(obj, chain) { 74 return chain ? _(obj).chain() : obj; 75 }; 76 77 // 将一个自定义方法添加到Underscore对象中(实际是添加到wrapper的原型中, 而Underscore对象的原型指向了wrapper的原型) 78 var addToWrapper = function(name, func) { 79 // 向wrapper原型中添加一个name函数, 该函数调用func函数, 并支持了方法链的处理 80 wrapper.prototype[name] = function() { 81 // 获取func函数的参数, 并将当前的原始数据添加到第一个参数 82 var args = slice.call(arguments); 83 unshift.call(args, this._wrapped); 84 // 执行函数并返回结果, 并通过result函数对方法链进行封装, 如果当前调用了chain()方法, 则返回封装后的Underscore对象, 否则返回对象本身 85 return result(func.apply(_, args), this._chain); 86 }; 87 }; 88 89 // 将内部定义的_(即Underscore方法集合对象)中的方法复制到wrapper的原型链中(即Underscore的原型链中) 90 // 这是为了在构造对象式调用的Underscore对象时, 这些对象也会具有内部定义的Underscore方法 91 _.mixin(_); 92 93 // 将Array.prototype中的相关方法添加到Underscore对象中, 因此在封装后的Underscore对象中也可以直接调用Array.prototype中的方法 94 // 如: _([]).push() 95 each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 96 // 获取Array.prototype中对应方法的引用 97 var method = ArrayProto[name]; 98 // 将该方法添加到Underscore对象中(实际是添加到wrapper的原型对象, 因此在创建Underscore对象时同时具备了该方法) 99 wrapper.prototype[name] = function() { 100 // _wrapped变量中存储Underscore对象的原始值 101 var wrapped = this._wrapped; 102 // 调用Array对应的方法并返回结果 103 method.apply(wrapped, arguments); 104 var length = wrapped.length; 105 if((name == 'shift' || name == 'splice') && length === 0) 106 delete wrapped[0]; 107 // 即使是对于Array中的方法, Underscore同样支持方法链操作 108 return result(wrapped, this._chain); 109 }; 110 }); 111 112 // 作用同于上一段代码, 将数组中的一些方法添加到Underscore对象, 并支持了方法链操作 113 // 区别在于上一段代码所添加的函数, 均返回Array对象本身(也可能是封装后的Array), concat, join, slice方法将返回一个新的Array对象(也可能是封装后的Array) 114 each(['concat', 'join', 'slice'], function(name) { 115 var method = ArrayProto[name]; 116 wrapper.prototype[name] = function() { 117 return result(method.apply(this._wrapped, arguments), this._chain); 118 }; 119 }); 120 121 // 对Underscore对象进行链式操作的声明方法 122 wrapper.prototype.chain = function() { 123 // this._chain用来标示当前对象是否使用链式操作 124 // 对于支持方法链操作的数据, 一般在具体方法中会返回一个Underscore对象, 并将原始值存放在_wrapped属性中, 也可以通过value()方法获取原始值 125 this._chain = true; 126 return this; 127 }; 128 129 // 返回被封装的Underscore对象的原始值(存放在_wrapped属性中) 130 wrapper.prototype.value = function() { 131 return this._wrapped; 132 }; 133 134 }).call(this);
小结
underscore这个库的结构(或者说思路)大致是这样的:
创建一个包装器, 将一些原始数据进行包装,所有的undersocre对象, 内部均通过wrapper函数进行构造和封装
underscore与wrapper的内部关系是:
- 内部定义变量_, 将underscore相关的方法添加到_, 这样就可以支持函数式的调用, 如_.bind()
- 内部定义wrapper类, 将_的原型对象指向wrapper类的原型
- 将underscore相关的方法添加到wrapper原型, 创建的_对象就具备了underscore的方法
- 将Array.prototype相关方法添加到wrapper原型, 创建的_对象就具备了Array.prototype中的方法
- new _()时实际创建并返回了一个wrapper()对象, 并将原始数组存储到_wrapped变量, 并将原始值作为第一个参数调用对应方法
之后我会对underscore中所有方法的具体实现进行介绍,感谢关注
浙公网安备 33010602011771号