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