1625行,解開 underscore.js 的面紗 - 第一章

一直想寫一篇這樣的文章,因而心動不如行動,這裏選擇的是 Underscore.js 1.8.3 版本,源碼註釋加在一塊兒1625行。javascript

  • Underscore.js 1.8.3php

  • http://underscorejs.orghtml

  • (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 / draftCMD 模塊定義規範

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()

相關文章
相關標籤/搜索