摘要:類似于,但更加健壯和完善。當為一個函數,正常處理。系列系列目錄地址。系列預計寫八篇左右,重點介紹中的代碼架構鏈式調用內部函數模板引擎等內容,旨在幫助大家閱讀源碼,以及寫出自己的。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。
前言
僅看 cb 和 optimizeCb 兩個函數的名字,你可能想不到這是用來做什么的,盡管你可能想到 cb 是 callback 的縮寫。
如果直接講解源碼,你可能想不明白為什么要這么寫,所以我們從 _.map 函數開始講起。
_.map_.map 類似于 Array.prototype.map,但更加健壯和完善。我們看下 _.map 的源碼:
// 簡化過,這里僅假設 obj 是數組 _.map = function (obj, iteratee, context) { iteratee = cb(iteratee, context); var length = obj.length, results = Array(length); for (var index = 0; index < length; index++) { results[index] = iteratee(obj[index], index, obj); } return results; };
map 方法除了傳入要處理的數組之外,還有兩個參數 iteratee 和 context,類似于 Array.prototype.map 中的其他兩個參數,其中 iteratee 表示處理函數,context 表示指定的執行上下文,即 this 的值。
然后在源碼中,我們看到,我們將 iteratee 和 context 傳入一個 cb 函數,然后覆蓋掉 iteratee 函數,然后將這個函數用作最終的處理函數。
實際上,需要這么麻煩嗎?不就是使用 iteratee 函數處理每次迭代的值嗎?不就是通過 context 指定 this 的值嗎?我們可以直接這樣寫吶:
_.map = function (obj, iteratee, context) { var length = obj.length, results = Array(length); for (var index = 0; index < length; index++) { results[index] = iteratee.call(context, obj[index], index, obj); } return results; }; // [2, 3, 4] console.log(_.map([1, 2, 3], function(item){ return item + 1; })) // [2, 3, 4] console.log(_.map([1, 2, 3], function(item){ return item + this.value; }, {value: 1}))
你看看也沒有什么問題吶,可是,萬一 iteratee 我們不傳入一個函數呢?比如我們什么也不傳,或者傳入一個對象,又或者傳入一個字符串、數字呢?
如果用我們的方法自然是會報錯的,那 underscore 呢?
// 使用 underscore // 什么也不傳 var result = _.map([1,2,3]); // [1, 2, 3] // 傳入一個對象 var result = _.map([{name:"Kevin"}, {name: "Daisy", age: 18}], {name: "Daisy"}); // [false, true] var result = _.map([{name: "Kevin"}, {name: "Daisy"}], "name"); // ["Kevin", "daisy"]
我們會發現,underscore 竟然還能根據傳入的值的類型不同,實現的效果不同。我們總結下:
當 iteratee 不傳時,返回一個相同的數組。
當 iteratee 為一個函數,正常處理。
當 iteratee 為一個對象,返回元素是否匹配指定的對象。
當 iteratee 為字符串,返回元素對應的屬性值的集合。
由此,我們可以推測在 underscore 的 cb 函數中,有對 iteratee 值類型的判斷,然后根據不同的類型,返回不同的 iteratee 函數。
cb所以我們來看看 cb 函數的源碼:
var cb = function(value, context, argCount) { if (_.iteratee !== builtinIteratee) return _.iteratee(value, context); if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value) && !_.isArray(value)) return _.matcher(value); return _.property(value); };
這一看就牽扯到了 8 個函數!不要害怕,我們一個一個看。
_.iterateeif (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
我們看看 _.iteratee 的源碼:
_.iteratee = builtinIteratee = function(value, context) { return cb(value, context, Infinity); };
因為 _.iteratee = builtinIteratee 的緣故,_.iteratee !== builtinIteratee 值為 false,所以正常情況下 _.iteratee(value, context) 并不會執行。
但是如果我們在外部修改了 _.iteratee 函數,結果便會為 true,cb 函數直接返回 _.iteratee(value, context)。
這個意思其實是說用我們自定義的 _.iteratee 函數來處理 value 和 context。
試想我們并不需要現在 _.map 這么強大的功能,我只希望當 value 是一個函數,就用該函數處理數組元素,如果不是函數,就直接返回當前元素,我們可以這樣修改:
underscore map
當然更多的情況是自定義對不同的 value 使用不同的處理函數,值得注意的是,underscore 中的多個函數都是用了 cb 函數,而因為 cb 函數使用了 _.iteratee 函數,如果你修改這個函數,其實會影響多個函數,這些函數基本都屬于集合函數,具體包括 map、find、filter、reject、every、some、max、min、sortBy、groupBy、indexBy、countBy、sortedIndex、partition、和 unique。
_.identityif (value == null) return _.identity;
讓我們看看 _.identity 的源碼:
_.identity = function(value) { return value; };
這也就是為什么當 map 的第二個參數什么都不傳的時候,結果會是一個相同數組的原因。
_.map([1,2,3]); // [1, 2, 3]
如果直接看這個函數,可能覺得沒有什么用,但用在這里,卻又十分的合適。
optimizeCbif (_.isFunction(value)) return optimizeCb(value, context, argCount);
當 value 是一個函數的時候,就傳入 optimizeCb 函數,我們來看看 optimizeCb 函數:
var optimizeCb = function(func, context, argCount) { // 如果沒有傳入 context,就返回 func 函數 if (context === void 0) return func; switch (argCount) { case 1: return function(value) { return func.call(context, value); }; case null: case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; };
也許你會好奇,為什么我要對 argCount 進行判斷呢?就不能直接返回嗎?比如這樣:
var optimizeCb = function(func, context) { // 如果沒有傳入 context,就返回 func 函數 if (context === void 0) return func; return function() { return func.apply(context, arguments); }; };
當然沒有問題,但為什么 underscore 要這樣做呢?其實就是為了避免使用 arguments,提高一點性能而已,如果不是寫一個庫,其實還真是沒有必要做到這點。
而為什么當參數是 3 個時候,參數名稱分別是 value, index, collection ,又為什么沒有參數為 2 的情況呢?其實這都是根據 underscore 函數用到的情況,沒有函數用到兩個參數,于是就省略了,像 map 函數就會用到 3 個參數,就根據這三個參數的名字起了這里的變量名啦。
_.matcherif (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
這段就是用來處理當 map 的第二個參數是對象的情況:
// 傳入一個對象 var result = _.map([{name:"Kevin"}, {name: "Daisy", age: 18}], {name: "Daisy"}); // [false, true]
如果 value 是一個對象,并且不是數組,就使用 _.matcher 函數。看看各個函數的源碼:
var nativeIsArray = Array.isArray; _.isArray = nativeIsArray || function(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; _.isObject = function(obj) { var type = typeof obj; return type === "function" || type === "object" && !!obj; }; // extend 函數可以參考 《JavaScript 專題之手寫一個 jQuery 的 extend》 _.matcher = function(attrs) { attrs = _.extend({}, attrs); return function(obj) { return _.isMatch(obj, attrs); }; }; // 該函數判斷 attr 對象中的鍵值是否在 object 中有并且相等 // var stooge = {name: "moe", age: 32}; // _.isMatch(stooge, {age: 32}); => true // 其中 _.keys 相當于 Object.keys _.isMatch = function(object, attrs) { var keys = _.keys(attrs), length = keys.length; if (object == null) return !length; var obj = Object(object); for (var i = 0; i < length; i++) { var key = keys[i]; if (attrs[key] !== obj[key] || !(key in obj)) return false; } return true; };_.property
return _.property(value);
這個就是處理當 value 是基本類型的值的時候,返回元素對應的屬性值的情況:
var result = _.map([{name: "Kevin"}, {name: "Daisy"}], "name"); // ["Kevin", "daisy"]
我們看下源碼:
_.property = function(path) { // 如果不是數組 if (!_.isArray(path)) { return shallowProperty(path); } return function(obj) { return deepGet(obj, path); }; }; var shallowProperty = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; // 根據路徑取出深層次的值 var deepGet = function(obj, path) { var length = path.length; for (var i = 0; i < length; i++) { if (obj == null) return void 0; obj = obj[path[i]]; } return length ? obj : void 0; };
我們好像發現了新大陸,原來 value 還可以傳一個數組,用來取深層次的值,舉個例子:
var person1 = { child: { nickName: "Kevin" } } var person2 = { child: { nickName: "Daisy" } } var result = _.map([person1, person2], ["child", "nickName"]); console.log(result) // ["Kevin", "daisy"]最后
如果你想學習 underscore 的源碼,在分析集合相關的函數時一定會接觸 cb 和 optimizeCb 函數,先掌握這兩個函數,會幫助你更好更快的解讀源碼。
underscore 系列underscore 系列目錄地址:https://github.com/mqyqingfeng/Blog。
underscore 系列預計寫八篇左右,重點介紹 underscore 中的代碼架構、鏈式調用、內部函數、模板引擎等內容,旨在幫助大家閱讀源碼,以及寫出自己的 undercore。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90089.html
摘要:所以它與其他系列的文章并不沖突,完全可以在閱讀完這個系列后,再跟著其他系列的文章接著學習。如何閱讀我在寫系列的時候,被問的最多的問題就是該怎么閱讀源碼我想簡單聊一下自己的思路。感謝大家的閱讀和支持,我是冴羽,下個系列再見啦 前言 別名:《underscore 系列 8 篇正式完結!》 介紹 underscore 系列是我寫的第三個系列,前兩個系列分別是 JavaScript 深入系列、...
摘要:他指示了一個對象的屬性,返回的將用來獲得該屬性對應的值在上面的分析中,我們知道,當傳入的是一個函數時,還要經過一個叫的內置函數才能獲得最終的所以此處的必然是優化回調的作用了。 開篇說明 對的,讓你所見,又開始造輪子了。哈哈,造輪子我們是認真的~ 源碼閱讀是必須的,Underscore是因為剛剛學習整理了一波函數式編程,加上自己曾經沒有太多閱讀源碼的經驗,先拿Underscore練練手,...
摘要:第四個判斷如果是對象執行返回一個斷言函數,用來判定傳入對象是否匹配指定鍵值屬性。都不匹配最后執行,返回傳入的對象的屬性。設置的值并生成函數,等同于,使具有屬性且有值則返回,否則返回,這是一個判斷函數。 在第二小章節里面我按照源碼順序介紹幾個方法,源碼緊接著第一章繼續: var builtinIteratee; builtinIteratee,內置的 Iteratee (迭代器)。...
摘要:你可以輕松為你的函數庫添加防沖突功能。系列系列目錄地址。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。 防沖突 underscore 使用 _ 作為函數的掛載對象,如果頁面中已經存在了 _ 對象,underscore 就會覆蓋該對象,舉個例子: var _ = {value: 1 } // 引入 underscore 后 console.log(_.value); // un...
摘要:在中,真值檢測函數的參數被命名為,有斷言的意思,非常形象。函數的功能是檢測一個對象或數組是否包含指定的某個元素。 順著underscore源碼的順序讀下來,弄懂了之前underscore的基本結構,接下來看看underscore為我們提供的一些關于集合的API。 迭代 關于迭代,我們都知道ES5原生方法也提供了迭代函數供我們使用,而在underscore中的迭代則是對原生的迭代函數進行...
閱讀 2986·2021-11-16 11:45
閱讀 5176·2021-09-22 10:57
閱讀 1773·2021-09-08 09:36
閱讀 1597·2021-09-02 15:40
閱讀 2514·2021-07-26 23:38
閱讀 1200·2019-08-30 15:55
閱讀 929·2019-08-30 15:54
閱讀 1217·2019-08-29 14:06