Why underscore
最近開始看 underscore.js 源碼,並將 underscore.js 源碼解讀 放在了我的 2016 計劃中。
閱讀一些著名框架類庫的源碼,就好像和一個個大師對話,你會學到很多。爲什麼是 underscore?最主要的原因是 underscore 簡短精悍(約 1.5k 行),封裝了 100 多個有用的方法,耦合度低,非常適合逐個方法閱讀,適合樓主這樣的 JavaScript 初學者。從中,你不僅可以學到用 void 0 代替 undefined 避免 undefined 被重寫等一些小技巧 ,也可以學到變量類型判斷、函數節流&函數去抖等常用的方法,還可以學到很多瀏覽器兼容的 hack,更可以學到作者的整體設計思路以及 API 設計的原理(向後兼容)。
之後樓主會寫一系列的文章跟大家分享在源碼閱讀中學習到的知識。
-
underscore-1.8.3 源碼解讀項目地址 https://github.com/hanzichi/underscore-analysis
-
underscore-1.8.3 源碼全文註釋 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/underscore-1.8.3-analysis.js
- underscore-1.8.3 源碼解讀系列文章 https://github.com/hanzichi/underscore-analysis/issues
歡迎圍觀~ (如果有興趣,歡迎 star & watch~)您的關注是樓主繼續寫作的動力
Main
今天把源碼解讀部分 Object Functions 更新完畢。
如果你有心,可能就會發現樓主之前的解讀系列文章說的都是 Object 上的擴展方法,也就是源碼中 Object Functions 部分。underscore 爲 5 種類型添加了擴展方法,分別是 Object -> Array -> Collection -> Function -> Utility,這也正是樓主的源碼解讀順序(並不是源碼順序)。其中,Object 上的擴展方法多達 38 個,方法多並不代表代碼多,比如類型檢測,兩行代碼就可以搞定好幾個方法,而上一篇中說的 _.isEqual 方法,卻要百來行去實現。今天是 Object Functions 部分的最後一篇,我們來看看樓主認爲的幾個沒被解讀過的但是卻有意思的方法的源碼。(其實很多方法使用簡單,實現也非常簡單,有興趣的同學可以自己扒下源碼)
_.pick
首先來看看 _.pick 方法,該方法傳入一個對象,然後刪選對象的鍵值對,返回一個對象副本。
直接來看例子:
_.pick({name: 'moe', age: 50, userid: 'moe1'}, 'name', 'age');
=> {name: 'moe', age: 50}
_.pick({name: 'moe', age: 50, userid: 'moe1'}, ['name', 'age']);
=> {name: 'moe', age: 50}
_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
return _.isNumber(value);
});
=> {age: 50}
一目瞭然,第一個參數 obj 是對象,第二個參數可以是一系列的 key 值,也可以是數組(數組中含 key),也可以是迭代函數,我們根據 key 值,或者迭代函數來過濾 obj 中的鍵值對,返回新的對象副本。
如果讓我來設計,估計會根據參數來判斷類型,然後寫幾個 if-else,每個 if-else 分支裏的內容毫無關聯。但是 underscore 的寫法簡直美妙,將幾種情況轉爲了一種。
// 如果第二個參數是函數
if (_.isFunction(oiteratee)) {
keys = _.allKeys(obj);
iteratee = optimizeCb(oiteratee, context);
}
首先 if-else 是不可避免的,如果傳入的第二個參數是 function,那麼就是傳入迭代函數了,根據 context(this)返回新的迭代函數(optimizeCb 我以後會講,就是規定了迭代函數中的 this 指向,不是很重要,這裏可以選擇性忽略)。
如果第二個參數不是函數,則後面的 keys 可能是數組,也可能是連續的幾個並列的參數。這裏我們要用到 underscore 中另一個重要的內部方法,flatten,它的作用是將嵌套的數組展開,這個方法我以後會分析,這裏知道它的作用就可以了。
else {
// 如果第二個參數不是函數
// 則後面的 keys 可能是數組
// 也可能是連續的幾個並列的參數
keys = flatten(arguments, false, false, 1);
// 也轉爲 predicate 函數判斷形式
// 將指定 key 轉化爲 predicate 函數
iteratee = function(value, key, obj) { return key in obj; };
obj = Object(obj);
}
也轉爲和傳入迭代函數一樣的形式,就可以用一個方法判斷了,而且 keys 變量在兩種情況下的意義是不同的,真的非常巧妙。這點令我思考良多,很多時候的代碼冗餘,其實大概是自己代碼能力太差了吧,打死我也想不到這樣做。
_.create
_.create 方法非常簡單,根據你給的原型(prototype),以及一些 own properties,構造新的對象返回。
舉個簡單的例子:
var Person = function() {};
Person.prototype = {
show: function() {
alert(this.name);
}
};
var me = _.create(Person.prototype, {name: 'hanzichi'});
console.log(me);
// Object {name: "hanzichi"}
// name: "hanzichi"
// __proto__: Object
// show: function()
其實 me 變量就是一個擁有 name 作爲 own properties,且用 Person 函數構造的對象。
如果瀏覽器支持 ES5,我們可以用 Object.create():
var Person = function() {};
Person.prototype = {
show: function() {
alert(this.name);
}
};
var me = Object.create(Person.prototype);
_.extendOwn(me, {name: 'hanzichi'});
console.log(me);
如果不支持 ES5,我們可以新定義一個構造函數,將該構造函數的 prototype 賦值爲已知的 prototype 變量,然後用 new 運算符來獲取實例:
var Person = function() {};
Person.prototype = {
show: function() {
alert(this.name);
}
};
var _Person = function() {};
_Person.prototype = Person.prototype;
var me = new _Person();
_.extendOwn(me, {name: 'hanzichi'});
console.log(me);
undercore 的實現思路也大抵如此。
_.tap
本來打算把 _.tap 講掉,突然覺得跟鏈式調用一起講比較好,挖個坑。
小結
關於 Objects Function 部分的源碼剖析就到這了,具體這部分代碼可以參考 https://github.com/hanzichi/underscore-analysis/blob/master/underscore-1.8.3.js/src/underscore-1.8.3.js#L901-L1269。雖然說看完了這部分代碼,但是要真正理解消化還是需要時間的,我只能說自己理解了 90% 左右,歡迎探討交流。