Underscore 總體架構淺析

前言

終於,樓主的「Underscore 源碼解讀系列」underscore-analysis 即將進入尾聲,關注下 timeline 會發現樓主最近加快了解讀速度。十一月,多事之秋,最近好多事情搞的樓主心力憔悴,身心俱疲,也想盡快把這個系列完結掉,也好了卻一件心事。javascript

本文預計是解讀系列的倒數第二篇,最後一篇那麼顯然就是大總結了。樓主的 Underscore 系列解讀完整版地址 https://github.com/hanzichi/u...java

常規調用

以前寫的文章,關注點大多在具體的方法,具體的知識細節,也有讀者留言建議樓主講講總體架構,這是必須會講的,只是樓主把它安排在了最後,也就是本文,由於樓主以爲不掌握總體架構對於具體方法的理解也是沒有大的問題的。git

Underscore 大多數時候的調用形式爲 _.funcName(xx, xx),這也是 文檔中 的調用方式。github

_.each([1, 2, 3], alert);

最簡單的實現方式,咱們能夠把 _ 看作一個簡單的對象:api

var _ = {};
_.each = function() {
  // ...
};

在 JavaScript 中,一切皆對象,實際上,源碼中的 _ 變量是一個方法:數組

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

爲何會是方法?咱們接下去看。架構

OOP

Underscore 支持 OOP 形式的調用:app

_([1, 2, 3]).each(alert);

這實際上是很是經典的「無 new 構造」,_ 其實就是一個 構造函數_([1, 2, 3]) 的結果就是一個對象實例,該實例有個 _wrapped 屬性,屬性值是 [1, 2, 3]。實例要調用 each 方法,其自己沒有這個方法,那麼應該來自原型鏈,也就是說 _.prototype 上應該有這個方法,那麼,方法是如何掛載上去的呢?函數

方法掛載

如今咱們已經明確如下兩點:this

  1. _ 是一個函數(支持無 new 調用的構造函數)

  2. _ 的屬性有不少方法,好比 _.each_.template 等等

咱們的目標是讓 _ 的構造實例也能調用這些方法。仔細想一想,其實也不難,咱們能夠遍歷 _ 上的屬性,若是屬性值類型是函數,那麼就將函數掛到 _ 的原型鏈上去。

源碼中用來完成這件事的是 _.mixin 方法:

// Add your own custom functions to the Underscore object.
// 可向 underscore 函數庫擴展本身的方法
// obj 參數必須是一個對象(JavaScript 中一切皆對象)
// 且本身的方法定義在 obj 的屬性上
// 如 obj.myFunc = function() {...}
// 形如 {myFunc: function(){}}
// 以後即可使用以下: _.myFunc(..) 或者 OOP _(..).myFunc(..)
_.mixin = function(obj) {
  // 遍歷 obj 的 key,將方法掛載到 Underscore 上
  // 實際上是將方法淺拷貝到 _.prototype 上
  _.each(_.functions(obj), function(name) {
    // 直接把方法掛載到 _[name] 上
    // 調用相似 _.myFunc([1, 2, 3], ..)
    var func = _[name] = obj[name];

    // 淺拷貝
    // 將 name 方法掛載到 _ 對象的原型鏈上,使之能 OOP 調用
    _.prototype[name] = function() {
      // 第一個參數
      var args = [this._wrapped];

      // arguments 爲 name 方法須要的其餘參數
      push.apply(args, arguments);
      // 執行 func 方法
      // 支持鏈式操做
      return result(this, func.apply(_, args));
    };
  });
};

// Add all of the Underscore functions to the wrapper object.
// 將前面定義的 underscore 方法添加給包裝過的對象
// 即添加到 _.prototype 中
// 使 underscore 支持面向對象形式的調用
_.mixin(_);

_.mixin 方法能夠向 Underscore 庫增長本身定義的方法:

_.mixin({
  capitalize: function(string) {
    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  }
});
_("fabio").capitalize();
=> "Fabio"

同時,Underscore 也加入了一些 Array 原生的方法:

// Add all mutator Array functions to the wrapper.
// 將 Array 原型鏈上有的方法都添加到 underscore 中
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    var obj = this._wrapped;
    method.apply(obj, arguments);

    if ((name === 'shift' || name === 'splice') && obj.length === 0)
      delete obj[0];

    // 支持鏈式操做
    return result(this, obj);
  };
});

// Add all accessor Array functions to the wrapper.
// 添加 concat、join、slice 等數組原生方法給 Underscore
_.each(['concat', 'join', 'slice'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    return result(this, method.apply(this._wrapped, arguments));
  };
});

鏈式調用

Underscore 也支持鏈式調用:

// 非 OOP 鏈式調用
_.chain([1, 2, 3])
  .map(function(a) {return a * 2;})
  .reverse()
  .value(); // [6, 4, 2]

// OOP 鏈式調用
_([1, 2, 3])
  .chain()
  .map(function(a){return a * 2;})
  .first()
  .value(); // 2

乍一看彷佛有 OOP 和非 OOP 兩種鏈式調用形式,其實只是一種,_.chain([1, 2, 3])_([1, 2, 3]).chain() 的結果是同樣的。如何實現的?咱們深刻 chain 方法看下。

_.chain = function(obj) {
  // 不管是否 OOP 調用,都會轉爲 OOP 形式
  // 而且給新的構造對象添加了一個 _chain 屬性
  var instance = _(obj);

  // 標記是否使用鏈式操做
  instance._chain = true;

  // 返回 OOP 對象
  // 能夠看到該 instance 對象除了多了個 _chain 屬性
  // 其餘的和直接 _(obj) 的結果同樣
  return instance;
};

咱們看下 _.chain([1, 2, 3]) 的結果,將參數代入函數中,其實就是對參數進行無 new 構造,而後返回實例,只是實例多了個 _chain 屬性,其餘的和直接 _([1, 2, 3]) 如出一轍。再來看 _([1, 2, 3]).chain()_([1, 2, 3]) 返回構造實例,該實例有 chain 方法,調用方法,爲實例添加 _chain 屬性,返回該實例對象。因此,這二者效果是一致的,結果都是轉爲了 OOP 的形式。

說了這麼多,彷佛還沒講到正題上,它是如何「鏈」下去的?咱們以以下代碼爲例:

_([1, 2, 3])
  .chain()
  .map(function(a){return a * 2;})
  .first()
  .value(); // 2

當調用 map 方法的時候,實際上可能會有返回值。咱們看下 _.mixin 源碼:

// 執行 func 方法
// 支持鏈式操做
return result(this, func.apply(_, args));

result 是一個重要的內部幫助函數(Helper function ):

// Helper function to continue chaining intermediate results.
// 一個幫助方法(Helper function)
var result = function(instance, obj) {
  // 若是須要鏈式操做,則對 obj 運行 chain 方法,使得能夠繼續後續的鏈式操做
  // 若是不須要,直接返回 obj
  return instance._chain ? _(obj).chain() : obj;
};

若是須要鏈式操做(實例會有帶有 _chain 屬性),則對運算結果調用 chain 函數,使之能夠繼續鏈式調用。

小結

Underscore 總體架構,或者說是基礎實現大概就是這個樣子,代碼部分就講到這了,接下去系列解讀最後一篇,講講這段時間(幾乎也是歷時半年了)的一些心得體會吧,沒錢的就捧我的場吧!

相關文章
相關標籤/搜索