资讯专栏INFORMATION COLUMN

1625行,解开 underscore.js 的面纱 - 第一章

MockingBird / 1990人阅读

摘要:新出台的则规定,包括六种原始类型和,还有一种,详见数据类型和数据结构。用于返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,。接下来判断数字进行相应的操作,其中有和两个方法,详见和。

一直想写一篇这样的文章,于是心动不如行动,这里选择的是 Underscore.js 1.8.3 版本,源码注释加在一起1625行。

Underscore.js 1.8.3

http://underscorejs.org

(c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors Underscore may be freely distributed under the MIT license.

这里我们首先看到的是一个闭包,概念不再熬述,诸君有意详勘闭包的概念,请移步 Closures。源码如下:

(function() {

这里如果这里有 this 那么一定是指向 window,即:

Window {external: Object, chrome: Object, document: document, speechSynthesis: SpeechSynthesis, caches: CacheStorage…}

window 具有的众多属性中就包含了 self 引用其自身,根据javascript的运算符执行顺序:

. [] () 字段访问、数组下标、函数调用以及表达式分组

++ -- - ~ ! delete new typeof void 一元运算符、返回数据类型、对象创建、未定义值

* / % 乘法、除法、取模

+ - + 加法、减法、字符串连接

<< >> >>> 移位

< <= > >= instanceof 小于、小于等于、大于、大于等于、instanceof

== != === !== 等于、不等于、严格相等、非严格相等

& 按位与

^ 按位异或

| 按位或

&& 逻辑与

|| 逻辑或

?: 条件

= 赋值、运算赋值

, 多重求值

  var root = typeof self == "object" && self.self === self && self ||
            typeof global == "object" && global.global === global && global ||
            this;

这里首先判断的是存在 self 或者 node 环境下的全局变量 global,然后复制给 root,作为根对象。

  var previousUnderscore = root._;

previousUnderscore,从字面上理解就是“以前的 underscore”,说实话我并没理解这个赋值的用意,最开始以为是用来做判断全局 window是否已经存在 window._ 这个对象,然后通过判断 previousUnderscore 用来避免 window._ 污染 underscore 引起命名冲突,但是从头到尾只有一个地方用到了 previousUnderscore,即(1352行):

_.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

在外部可执行 var underscore_cache = _.noConflict(); 用来重新定义 underscore 命名,很简单也很巧妙,noConflict 方法内将 root._ 也就是 window._ 重新定义为 previousUnderscore (previousUnderscore = undefined),而 noConflict 是_的一个属性方法,所以 this 指向其自身(41行),即将 _ 赋值给了 underscore_cache。

 var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

 var ArrayProto = Array.prototype, ObjProto = Object.prototype;

这两句很简单,就是将原生 JAVASCRIPT 的 Array 和 Object 对象的 prototype 缓存,这样做的好处是使用 push、slice、toString等方法的代码行数会减少、减少 JAVASCRIPT 遍历等等,更具体的介绍会在下面讲解,不要心急。

  var SymbolProto = typeof Symbol !== "undefined" ? Symbol.prototype : null;

2009年的 ES5 规定了六种语言类型:Null Undefined Number Boolean String Object,详见ES5/类型 和 ES5/类型转换与测试。新出台的 ES6 则规定,包括六种原始类型:Null Undefined Number Boolean String 和 Symbol,还有一种 Object,详见JavaScript 数据类型和数据结构。新增加的 Symbol 很早就已经提出,其具体概念这里不再复述请移步参考 Symbol ,得益于 ES6 的渐渐普及,客户端浏览器也有很多已经支持 Symbol,比如 Firefox v36+ 和 Chrome v38+ 等,具体参考 ES6 支持情况,如果大家对 ES6 想要深入了解可以看 ES6 In Depth 这篇文章和 ES6草案,说实话我的水平有限这份草案还没有读懂(+﹏+),如果想要进一步为 ES6 普及贡献自己的力量 ES6 WIKI 的编写是一个蛮好的选择。

回归正题,上述代码的目的显而易见就是判断客户端是否支持 Symbol,支持则缓存 Symbol.prototype 原型链,不支持则赋值为 Null,三元运算符的灵活运用是判断一个人语言到达一个阶段的标识,这句话有点武断,但是算的上肺腑之言,要熟悉且灵活运用它。

  var push = ArrayProto.push,
      slice = ArrayProto.slice,
      toString = ObjProto.toString,
      hasOwnProperty = ObjProto.hasOwnProperty;

这里是简单缓存了 push、slice、toString、hasOwnProperty 四个方法。

  var nativeIsArray = Array.isArray,
      nativeKeys = Object.keys,
      nativeCreate = Object.create;

这里就比较有意思了,Array.isArray(element) 是 ES5 后来新增的静态函数,用来判断一个对象是不是数组,具体描述可见 Array.isArray() 和 Array.isArray 函数 (JavaScript):https://msdn.microsoft.com/zh-cn/library/ff848265(v=vs.94).aspx,我一点都不喜欢微软,就比如现在我想粘一个微软的网址,但是它的网址里面居然有(),以至于我必须把网址贴到代码框里才能保证不出现错误ヽ(ˋДˊ)ノ。Object.keys 用于返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,Object.keys()。Object.create 用于创建一个拥有指定原型和若干个指定属性的对象,这一系列的函数方法都可以在 Object 处了解详情。同时这里面有些内容可以参考 Annotated ECMAScript 5.1,有兴趣的同学可以看一看,雾里探花,蛮有趣的。

  var Ctor = function(){};

ctor 英文译为男星,或者我的百度翻译打开方式不对,翻译错了???,实际上就是一个空的方法,这种写法很常见,一般用于和 call、apply、argument 等配合使用,在 Underscore.js 中作者并没有上述的用法,只是用 Ctor 这个函数扩展了自身的 prototype,将一些函数方法绑定到自身作为一个 return function,具体细节后面接触到再详述。

  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

定义 _ 对象,作者的备注是”Create a safe reference to the Underscore object for use below.“,这里我们了解到 _ 本身是一个函数,而在 JAVASCRIPT 中函数本身就是对象的一种,所以 Underscore.js 的一系列函数都是作为对象函数绑定到 _ 这个函数对象上面的,上面这个函数默认传入一个 obj 参数,可以通过 _(obj) 用来校验 _ 是否是 obj 的父类型以此判断继承关系,instanceof的用法详见 JavaScript instanceof 运算符深入剖析,至于 _wrapped 涉及到后面的链式操作,在(887行)一起讲。

  if (typeof exports != "undefined" && !exports.nodeType) {
    if (typeof module != "undefined" && !module.nodeType && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

这是 Node.js 中对通用模块的封装方法,通过对判断 exports 是否存在来决定将局部变量 _ 赋值给exports,顺便说一下 AMD 规范、CMD规范和 UMD规范,Underscore.js 是支持 AMD 的,在源码尾部有定义,这里简单叙述一下:

amd:AMDJS

define(["underscore"], function (_) {
    //todo
});

cmd:Common Module Definition / draft、CMD 模块定义规范

var _ = require("underscore");
module.exports = _;

另一种常见的写法:

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["underscore"], factory);
    } else if (typeof exports === "object") {
        module.exports = factory(require("underscore"));
    } else {
        root.returnExports = factory(root._);
    }
}(this, function ($) {
    //todo
}));
  _.VERSION = "1.8.3";

underscore 版本为 "1.8.3"。

  var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

optimizeCb 翻译成汉语就是优化回调(optimize callback),那么 optimizeCb 是如何优化的呢,我们可以首先看到它传入了三个参数,分别为:func、context、argCount,语义化可知一个是将要优化的 callback function,一个是 context 上下文函数,最后 argCount 是一个 number 类型的数字。void 0 的用法很巧妙,这里用 context === void 0 判断是否存在上下文环境,也就是第二个参数,其他的一些关于 void 的用法详见 谈谈Javascript中的void操作符。接下来判断 argCount 数字进行相应的操作,其中有 call 和 apply 两个方法,详见 Function.prototype.apply() 和 Function.prototype.call()。

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

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

相关文章

  • 如何自制 JS 注释文档生成工具

    摘要:组件的选择命令行工具首先我们需要一个命令行工具来方便的执行命令,这里我们选择组件,如果不喜欢使用且有能力的人完全可以通过组件自己封装执行命令函数。 对于一个成熟的项目而言,一定需要一个注释文档生成工具,我们有很多可选的开源项目,如jsdoc、yuidocjs 等等,拥有这些强大的工具我们完全可以胜任任何注释方面的管理了么? 一个成熟的开发者都会知道不管怎么样的项目都会在不同的开发条件下...

    Cristalven 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第二章

    摘要:第四个判断如果是对象执行返回一个断言函数,用来判定传入对象是否匹配指定键值属性。都不匹配最后执行,返回传入的对象的属性。设置的值并生成函数,等同于,使具有属性且有值则返回,否则返回,这是一个判断函数。 在第二小章节里面我按照源码顺序介绍几个方法,源码紧接着第一章继续: var builtinIteratee; builtinIteratee,内置的 Iteratee (迭代器)。...

    yuxue 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第六章

    摘要:用来构成和两个函数,主要针对的是为了将函数调用模式更改为构造器调用和方法调用。通过函数设定时间为毫秒后执行函数的回调函数,用以达到在规定时间毫秒时执行函数的目的,并且规定时间内只执行一次函数。 北京的雨已经断断续续下了好久,昏昏欲睡的躲在家里不愿意出门,火影忍者快要结束了,一拳超人第二季据说还要等好多年,勇者大冒险貌似断更了,我又是在不喜欢海贼王的画风,所以,我该看什么好呢。 va...

    v1 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第三章

    摘要:传入值进行判断以此决定函数,将三个参数包括回调传入中其中回调函数充当迭代器进行真值检测,最后。是从一个中随机返回值,并且返回值受限于这个参数,如果没有传入或者传入了则执行语句,目的是将判断处理之后返回单一值。 今天继续上次的内容,之前我们讲到了 reduce 的用法,其实我觉得用法倒是其次的关键是作者实现 reduce 过程中所灵活用到的函数处理方法,我们只要有心稍加总觉完全可以拿来主...

    dack 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第五章

    摘要:对多个一维数组进行并运算,实际上就是加强版的。所以我要说的是这个函数,将传入参数转换为一个数组进行到的回调函数中,以此达到函数接到的是一个一维数组的集合。 每次小章节的开题都烦恼写什么好,所以直接接下文 (~o▔▽▔)~o o~(▔▽▔o~) 。 _.first = _.head = _.take = function(array, n, guard) { if (arra...

    Rango 评论0 收藏0

发表评论

0条评论

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