Why underscoregit
最近開始看 underscore.js 源碼,並將 underscore.js 源碼解讀 放在了個人 2016 計劃中。github
閱讀一些著名框架類庫的源碼,就好像和一個個大師對話,你會學到不少。爲何是 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.jside
歡迎圍觀~ (若是有興趣,歡迎 star & watch~)您的關注是樓主繼續寫做的動力函數
Main學習
今天把源碼解讀部分 Object Functions 更新完畢。this
若是你有心,可能就會發現樓主以前的解讀系列文章說的都是 Object 上的擴展方法,也就是源碼中 Object Functions 部分。underscore 爲 5 種類型添加了擴展方法,分別是 Object -> Array -> Collection -> Function -> Utility,這也正是樓主的源碼解讀順序(並非源碼順序)。其中,Object 上的擴展方法多達 38 個,方法多並不表明代碼多,好比類型檢測,兩行代碼就能夠搞定好幾個方法,而上一篇中說的 _.isEqual 方法,卻要百來行去實現。今天是 Object Functions 部分的最後一篇,咱們來看看樓主認爲的幾個沒被解讀過的可是卻有意思的方法的源碼。(其實不少方法使用簡單,實現也很是簡單,有興趣的同窗能夠本身扒下源碼)prototype
_.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% 左右,歡迎探討交流。