本文是underscore源碼剖析系列的第二篇,主要介紹underscore中一些基礎方法的實現。segmentfault
在上篇文章underscore總體架構分析中,咱們講過_上面的方法有兩種掛載方式,一個是掛載到_構造函數上以_.map(arr)的形式直接調用(在後文上統稱構造函數調用),另外一種則是掛到_.prototype上以_(arr).map()的形式被實例調用(在後文上統稱原型調用)。數組
翻一遍underscore源碼你會發現underscore中的方法都是直接掛到_構造函數上實現的,可是會經過mixin方法來將_上面的方法擴展到_.prototype上面,這樣這些方法既能夠直接調用,又能夠經過實例來調用。架構
_.mixin = function(obj) { // 遍歷obj上全部的方法 _.each(_.functions(obj), function(name) { // 保存方法的引用 var func = _[name] = obj[name]; _.prototype[name] = function() { // 將一開始傳入的值放到數組中 var args = [this._wrapped]; // 將方法的參數一塊兒push到數組中(這裏處理的很好,保證了func方法參數的順序) push.apply(args, arguments); // 這裏先用apply方法執行了func,並將結果傳給了result return result(this, func.apply(_, args)); }; }); }; _.mixin(_);
從這段代碼中咱們能夠看出,mixin方法將_上的全部方法經過遍歷的形式掛載到了_.prototype上面。app
細心觀察一下,構造函數調用和原型調用的區別在哪裏?
沒錯,區別就在於調用方式和傳參,構造函數調用時通常會把要處理的值當作第一個參數傳入,而原型調用的時候會把要處理的值傳入_構造函數來建立一個實例。函數
var arr = [1, 2, 3] var func = function(item) { console.log(item); } // 構造函數調用時arr被傳入第一個參數 _.each(arr, func) // 原型調用的時候,arr被當作參數傳給_方法來建立一個實例 _(arr).each(func) // 鏈式調用,和上面相似 _.chain(arr).each(func)
從上一節中咱們知道,在建立一個_的實例時,會用this._wrapped將傳入的值保存起來,因此在mixin裏面這一句:var args = [this._wrapped];是將咱們傳給_的值放到args數組第一項中,以後再將arguments也放入args數組中,藉助apply方法執行當前遍歷的方法(在這個例子中是each),這個時候傳給each方法的是arr和func,正好和原來直接_.each調用each傳入參數的順序是同樣的(underscore中的方法第一項基本上都是要處理的數據)。源碼分析
那麼上面最後return result(this, func.apply(_, args)),result又是作什麼的呢?this
首先來看result源碼:prototype
var result = function(instance, obj) { // 首先判斷是否使用鏈式調用,若是是,那就繼續將剛剛執行後返回的結果鏈式調用一下,若是不是,則直接返回執行後的結果 return instance._chain ? _(obj).chain() : obj; }; _.chain = function(obj) { // 建立一個實例 var instance = _(obj); // 給這個實例加個_chain屬性來代表這是鏈式調用 instance._chain = true; return instance; };
咱們知道underscore中也是有和jQuery相似的鏈式調用,來看一下鏈式調用的例子:code
var arr = [1, 2, 3] var newArr = _.chain(a).map(function(item) { return item + 1 }).filter(function(item) { return item > 2 }).value()
鏈式調用的關鍵在於每次執行方法後都須要返回一個實例,以確保可以繼續調用其餘方法。
chain方法會用傳入的obj建立一個_的實例,這個實例能夠調用原型上的方法。從上面mixin的實現來看,每次調用原型方法後會將執行後的結果傳給result方法,在result內部會判斷你是否使用了鏈式調用(chain),若是是鏈式的,那麼就會將返回結果鏈式化(傳入chain中建立新的實例)。
鏈式調用必定要在結尾執行value方法,否則最後返回的是一個對象(最後一次建立的_實例)對象
underscore構造方法上面並無直接對push、pop、shift等數組方法進行實現,可是鏈式調用的時候每每須要用到這些方法,因此在原型上對這些方法作了一些封裝,實現方法和mixin相似,這裏再也不多作解釋。
_.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); // 這句是好像是爲了解決ie上的bug? if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return result(this, obj); }; }); _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; });