资讯专栏INFORMATION COLUMN

Underscore源码解析(一)

neu / 2523人阅读

摘要:本文同步自我得博客最近准备折腾一下,在事先了解了之后,我知道了对这个库有着强依赖,正好之前也没使用过,于是我就想先把彻底了解一下,这样之后折腾的时候也少一点阻碍。

本文同步自我得博客:http://www.joeray61.com

最近准备折腾一下backbone.js,在事先了解了backbone之后,我知道了backboneunderscore这个库有着强依赖,正好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中所有方法的具体实现进行介绍,感谢关注 

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78128.html

相关文章

  • Underscore源码解析(四)

    摘要:本文同步自我得博客我在这个系列的第一篇文章说过,我学是为了在学的时候少一些阻碍,从第一篇的写作时间到今天,大概也有个十几二十天,感觉拖得有点久,所以今天将会是源码解析系列的最后一篇文章,我会在这篇文章中介绍剩下的所有函数。 本文同步自我得博客:http://www.joeray61.com 我在这个系列的第一篇文章说过,我学underscore是为了在学backbone的时候少一些阻碍...

    高胜山 评论0 收藏0
  • Underscore源码解析(二)

    摘要:本文同步自我得博客最近十几天都在忙毕业论文的事,所以上一次为大家介绍完这个框架的结构或者说是这个框架的设计思路之后就一直没动静了,今天我又满血复活了,让我们继续来探索的源码奥秘吧。 本文同步自我得博客:http://www.joeray61.com 最近十几天都在忙毕业论文的事,所以上一次为大家介绍完underscore这个框架的结构(或者说是这个框架的设计思路)之后就一直没动静了,今...

    骞讳护 评论0 收藏0
  • JS基础篇-underscore源码解析

    摘要:总想找个机会夯实一下自己的基础,正好最近略有清闲,看视频读书撸代码我选择了第三者怎么感觉有点别扭,看视频的话效率不高适合入门,看书的话一本你不知道的推荐给大家,选择继续看书的话还是算了吧,毕竟读万卷书不如行万里路是吧。 总想找个机会夯实一下自己的JS基础,正好最近略有清闲,看视频?读书?撸代码?我选择了第三者(怎么感觉有点别扭),看视频的话效率不高适合入门,看书的话,一本《你不知道的J...

    anyway 评论0 收藏0
  • Underscore源码中文注释(转)

    摘要:创建一个全局对象在浏览器中表示为对象在中表示对象保存下划线变量被覆盖之前的值如果出现命名冲突或考虑到规范可通过方法恢复被占用之前的值并返回对象以便重新命名创建一个空的对象常量便于内部共享使用将内置对象的原型链缓存在局部变量方便快速调用将 // Underscore.js 1.3.3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc....

    Guakin_Huang 评论0 收藏0
  • underscore源码解析

    说明1、源码结构通览,简单注释说明2、通过调用方法讲解核心代码逻辑 一、源码的结构 为了方便比对源码,按源码的结构顺序展示。underscore是个轻量级的工具库,大部分代码是实现特定功能以函数的形式存在,本身会比较简单,没对方法具体说明,可直接参考underscore中文文档 (function() { var root = this; var previousUnderscore = ...

    kid143 评论0 收藏0

发表评论

0条评论

neu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<