【underscore 源碼解讀】Array Functions 相關源碼拾遺 & 小結

Why underscore

最近開始看 underscore.js 源碼,并將 underscore.js 源碼解讀 放在了我的 2016 計劃中。

閱讀一些著名框架類庫的源碼,就好像和一個個大師對話,你會學到很多。為什么是 underscore?最主要的原因是 underscore 簡短精悍(約 1.5k 行),封裝了 100 多個有用的方法,耦合度低,非常適合逐個方法閱讀,適合樓主這樣的 JavaScript 初學者。從中,你不僅可以學到用 void 0 代替 undefined 避免 undefined 被重寫等一些小技巧 ,也可以學到變量類型判斷、函數節流&函數去抖等常用的方法,還可以學到很多瀏覽器兼容的 hack,更可以學到作者的整體設計思路以及 API 設計的原理(向后兼容)。

之后樓主會寫一系列的文章跟大家分享在源碼閱讀中學習到的知識。

歡迎圍觀~ (如果有興趣,歡迎 star & watch~)您的關注是樓主繼續寫作的動力

Main

很快,Array Functions 部分到了尾聲,今天來做個了(xiao)結。

underscore 給數組(以及 arguments,這里特別說明下,underscore 的數組擴展方法,同樣適用于 arguments)增加了 20 個擴展方法,值得一提的是,很多有意思的方法,比如 map,shuffle 等,都被放在了 Collection Functions 中。本文來看看 Array Functions 中還有哪些有意思的方法(之前沒有被提及)。

_.compact

這個方法很有意思,它的作用是剔除數組中的假值,返回數組副本。

實現非常的簡單:

_.compact = function(array) {
  return _.filter(array, _.identity);
};

_.filter 我們在以后會講到,這里你可以把它理解為 Array.prototype.filter 的一個 polyfill,來看看 _.identity 是個什么東東。

_.identity = function(value) {
  return value;
};

乍一看,_.identity 似乎沒什么卵用,傳入一個參數,原封不動返回這個參數,什么鬼?而再看 _.compact 的實現,就會發現非常巧妙!細細品味下,直接過濾了數組的假值,而 _.identity 在源碼中能在多個地方復用。

從這個方法可以想到 PHP 的 array_filter 函數。array_filter 的基本用法和 Array.prototype.filter 相似,都是為了過濾數組中的元素。

function isOdd($num) {
  return $num & 1;
}

$a = Array(1, 2, 3);

$a = array_filter($a, 'isOdd');

var_dump($a);

// array
//   0 => int 1
//   2 => int 3

但是,值得注意的是:

If no callback is supplied, all entries of array equal to FALSE (see converting to boolean) will be removed.

這就有點 6 了,直接把 _.filter 和 _.compact 兩個方法合二為一了。

$a = Array(0, 1, 2, 3, null, false, 4);

$a = array_filter($a);

var_dump($a);

// array
//   1 => int 1
//   2 => int 2
//   3 => int 3
//   6 => int 4

Array.prototype.filter 為何不設計成這樣呢?沒有 callback 傳入的時候,直接過濾假值...

_.difference & _.without

先來看 _.without,它的作用是從數組中剔除指定的元素。

var a = [1, 2, 3, 4, 5];
var ans = _.without(a, 1, 2, 3);
console.log(ans); // [4, 5]

恩,沒錯,剔除數組 a 中的 value 為 1, 2, 3 的元素,這個過程中用 === 來進行比較。該方法傳入的第一個參數是數組,后面的參數為單個元素。

而 _.difference 呢?和 _.without 的唯一區別是,第二個參數開始傳入的是數組。(分別和數組中的元素比較)

var a = [1, 2, 3, 4, 5];
var ans = _.difference(a, [1, 2, 3], [5, 6]);
console.log(ans); // [4]

從 a 數組中剔除 1,2,3,5,6。

仔細一想,如果已經實現了 _.difference,我們把 _.without 的參數放入數組,然后傳入 _.difference 就 ok 了!倒過來就不行了(思考下為什么)。

來看 _.difference 的實現,非常簡單:

// _.difference(array, *others)
_.difference = function(array) {
  // 將 others 數組展開一層
  // rest[] 保存展開后的元素組成的數組
  // strict 參數為 true
  // 不可以這樣用 _.difference([1, 2, 3, 4, 5], [5, 2], 10);
  // 10 就會取不到
  var rest = flatten(arguments, true, true, 1);
  
  // 遍歷 array,過濾
  return _.filter(array, function(value){
    // 如果 value 存在在 rest 中,則過濾掉
    return !_.contains(rest, value);
  });
};

不熟悉 flatten 的可以看看 前文,當 shallow 和 strict 均為 true 時,展開一層,并且過濾非數組元素,即可以起到將多個數組合并的作用。之后利用 ._filter 進行過濾即可。

而 _.without 方法則建立在 _.difference 基礎上。

_.without = function(array) {
  // slice.call(arguments, 1)
  // 將 arguments 轉為數組(同時去掉第一個元素)
  // 之后便可以調用 _.difference 方法
  return _.difference(array, slice.call(arguments, 1));
};

總結

數組的擴展方法就解讀到這里了,相關源碼可以參考 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L450-L693 這部分。接下去要解讀的是 Collection Functions 部分,所謂 Collection,正是 Object & Array,也就是說這部分方法既可以用于 Object 也能用于 Array,比如我們熟悉的 map,filter,shuffle 等等,都在這部分內。

放個預告,下一篇會暫緩下 Collection Functions,講下 array-like 相關的東西,敬請期待。

PS:堅持一件事真的挺難,一個月來,每天堅持看點源碼,幾乎把所有業余時間花在了上面,寫了 10 篇隨筆,每篇文章寫的時間不短,關鍵還需要構思,如何提煉出一個主題,如何寫讓人看了會有所收獲,恩,繼續堅持。請關注我的 Repo https://github.com/hanzichi/underscore-analysis 支持我~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容