首頁  >  文章  >  web前端  >  Underscore整體架構淺析

Underscore整體架構淺析

高洛峰
高洛峰原創
2016-11-05 09:23:511051瀏覽

前言

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

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

常規調用

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

Underscore 大多時候的呼叫形式為 _.funcName(xx, xx),這也是 文檔中 的呼叫方式。

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

最簡單的實作方式,我們可以把_ 看做一個簡單的對象:

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

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

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

為什麼會是方法?我們接下去去看。

OOP

Underscore 支援OOP 形式的呼叫:

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

這其實是非常經典的「無new 建構」,_ 其實就是一個建構函數,_([1, 2, 3]) 的結果就是一個物件實例,此實例有個_wrapped屬性,屬性值是[1, 2, 3]。實例要呼叫each 方法,本身沒有這個方法,那麼應該來自原型鏈,也就是說_.prototype 上應該有這個方法,那麼,方法是如何掛載上去的呢?

方法掛載

現在我們已經明確以下兩點:

_ 是一个函数(支持无 new 调用的构造函数)
_ 的属性有很多方法,比如 _.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 也加入了一些Arraycore 原生的方法:

// 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) {

_.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 函數,使之可以繼續鍊式呼叫。



陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn