Underscore源码解析(一)

      最近准备折腾一下backbone.js,在事先了解了backbone之后,我知道了backbone对underscore这个库有着强依赖,正好underscore之前也没使用过,于是我就想先把underscore彻底了解一下,这样之后折腾backbone的时候也少一点阻碍。

    underscore是一个很实用且小巧的框架,提供了很多我们在编程时需要的基本功能函数,而且他没有扩展javascript的原生对象,主要涉及对Object、Array、Function的操作。
    我曾经问我的朋友@小胡子哥 怎么学习一个框架?他给我的回答是:“直接看源码。”现在想想深感同意,因为研究源码是最直接的学习途径,可以深入地了解这个框架的思想和精髓,同时也能学习框架作者的编程技巧,提升自己的coding水平。
    好了,题外话就说到这里,下面咱们进入正题。

简化源码看结构

    我这次看的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中所有方法的具体实现进行介绍,感谢关注 

posted on 2014-05-21 14:29  JoeRay  阅读(208)  评论(0)    收藏  举报

导航