一直想写一篇这样的文章,因而心动不如行动,这里选择的是 Underscore.js 1.8.3 版本,源码注释加在一块儿1625行。javascript
Underscore.js 1.8.3php
(c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors Underscore may be freely distributed under the MIT license.java
这里咱们首先看到的是一个闭包,概念再也不熬述,诸君有意详勘闭包的概念,请移步 Closures。源码以下:node
(function() {
这里若是这里有 this 那么必定是指向 window,即:git
Window {external: Object, chrome: Object, document: document, speechSynthesis: SpeechSynthesis, caches: CacheStorage…}es6
window 具备的众多属性中就包含了 self 引用其自身,根据javascript的运算符执行顺序:github
. [] () 字段访问、数组下标、函数调用以及表达式分组web
++ -- - ~ ! delete new typeof void 一元运算符、返回数据类型、对象建立、未定义值chrome
* / % 乘法、除法、取模
+ - + 加法、减法、字符串链接
<< >> >>> 移位
< <= > >= 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()。