摘要:源碼如下通過來判斷到底通過來區分對象以及數組。傳入回調函數的參數分別為對象鍵值對中的值或者數組中的序號值對象鍵值對中的鍵或者數組中的相應序號舉個例子,傳入回調的參數依次為如果是數組,則傳入參數依次為三這幾個方法都是利用一個核心函數。
一、_.each
一開始我并沒有以為_.each這個方法會有多大的用處,不就是一個遍歷嘛~
但當我利用自己測試這個函數的時候,發現了一件“大事”
underscore的初始化時怎么做的?你是不是跟我一樣都以為underscore的初始化就是在_這個對象上面加上一堆屬性?
Naive!
underscore的真是做法是通過一系列的函數編程實現初始化的“自動化”
看下面的代碼:
// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet. _.each(["Arguments", "Function", "String", "Number", "Date", "RegExp", "Error", "Symbol", "Map", "WeakMap", "Set", "WeakSet"], function(name) { _["is" + name] = function(obj) { return toString.call(obj) === "[object " + name + "]"; }; });
underscore通過如上的代碼實現數據類型的判斷
類似的,作者通過調用_.each方法來實現
自己拓展的函數跟庫提供函數的合并(詳見_.mixin)
Array方法的自動初始化(line1588+line1599)
二、_.map_.map源碼的精華之處在于將Array以及Object合在一起處理。
源碼如下:
_.map = _.collect = function(obj, iteratee, context) { debugger iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { // 通過key來判斷到底 var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; };
通過var keys = !isArrayLike(obj) && _.keys(obj)來區分對象以及數組。如果為對象,則keys為true,否則為false。傳入回調函數的參數分別為:
對象鍵值對中的值或者數組中的序號值
對象鍵值對中的鍵或者數組中的相應序號
舉個例子,{one:1, two:2, three:3}:
傳入回調的參數依次為:
1, one
2, two
3, three
如果是數組[1, 2, 3],則傳入參數依次為:
1, 0
2, 1
3, 2
三、_.reduce _.foldl _.inject _.reduceRight _.foldr這幾個方法都是利用一個核心函數createReduce。
createReduce利用了高階函數的方式進行封裝, 通過傳入的參數來確定是正序遍歷還是倒敘遍歷。下面來看源碼:
// 創建一個遍歷函數,從左到右或者從右向左 // param: dir >0(從左向右遍歷),dir < 0(從右向左遍歷) var createReduce = function(dir) { // 包裝代碼,在一個多帶帶的函數中分配參數變量,而不是訪問`arguments.length",以避免發生issue #1991 var reducer = function(obj, iteratee, memo, initial) { var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; if (!initial) { // 未初始化memo參數的情況下,設置memo值為傳入參數的第一個值(有可能是倒序的第一個值也有可能是正序的第一個值) memo = obj[keys ? keys[index] : index]; index += dir; } // 利用 for 循環 迭代用戶傳入的參數 for (; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; }; return function(obj, iteratee, memo, context) { var initial = arguments.length >= 3; return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); }; };
看來這個主要函數,來看看這個函數的調用方式:
_.reduce = _.foldl = _.inject = createReduce(1); _.reduceRight = _.foldr = createReduce(-1);
主要是在初始化時,先運行createReduce形成一個閉包,從而可以在運行時可以很好的區分是從左向右遍歷還是從右向左遍歷。
四、_.findIndex|_.findKey|_.find|_.findLastIndex_.find基于_.findIndex以及_.findKey,所以我們先來看后兩者。
先來看_.findIndex。查看findIndex源碼會發現,它與findLastIndex原理一直,都是調用一個名為createPredicateIndexFinder
// 利用此函數來生成findIndex以及findLastIndex var createPredicateIndexFinder = function(dir) { // 判斷 dir 參數的正負來確認是findIndex還是findLastIndex return function(array, predicate, context) { predicate = cb(predicate, context); var length = getLength(array); var index = dir > 0 ? 0 : length - 1; // 遍歷來尋找符合要求的index for (; index >= 0 && index < length; index += dir) { // 傳入參數給回調函數 if (predicate(array[index], index, array)) return index; } // 如果未找到符合要求的index則返回 -1 return -1; }; };
看懂上面的函數,則findIndex以及findLatIndex就淺顯了:
_.findIndex = createPredicateIndexFinder(1); _.findLastIndex = createPredicateIndexFinder(-1);
看懂findIndex之后,來了解_.findKey,這個函數又牽扯到了另一個函數_.keys了(別那樣的表情,函數式編程就是這樣。。。)
_.keys = function(obj) { // 容錯處理,判斷是否為對象 if (!_.isObject(obj)) return []; // 如果能調用 ES5 的方法,則調用 Object.keys 方法 if (nativeKeys) return nativeKeys(obj); var keys = []; // 遍歷key值 for (var key in obj) if (_.has(obj, key)) keys.push(key); // Ahem, IE < 9.(IE9以下的處理沒太看懂。。。) if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; };
上面源碼的處理有幾個點值得我們注意以下:
_.isObject = function(obj) { var type = typeof obj; // function 屬于 object,typeof null = "object",需要通過!!obj排除null這種情況 return type === "function" || type === "object" && !!obj; };
_.has = function(obj, key) { // 通過原生的hasOwnProperty進行屬性的判斷 return obj != null && hasOwnProperty.call(obj, key); };
// IE < 9 ,有些key值不會被遍歷,導致key的遍歷缺失bug // hasEnumBug 用來辨識是否在IE < 9 的環境下 var hasEnumBug = !{toString: null}.propertyIsEnumerable("toString"); var nonEnumerableProps = ["valueOf", "isPrototypeOf", "toString", "propertyIsEnumerable", "hasOwnProperty", "toLocaleString"]; var collectNonEnumProps = function(obj, keys) { var nonEnumIdx = nonEnumerableProps.length; var constructor = obj.constructor; var proto = _.isFunction(constructor) && constructor.prototype || ObjProto; // Constructor is a special case. var prop = "constructor"; if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); while (nonEnumIdx--) { prop = nonEnumerableProps[nonEnumIdx]; if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { keys.push(prop); } } };五、_.filter
上源碼:
// 返回所有通過測試的元素 // 別名為`select` _.filter = _.select = function(obj, predicate, context) { var results = []; // 改變predicate的函數指向 predicate = cb(predicate, context); // 遍歷每個值,如果通過測試則放入要返回的數組中 _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; };
主要采用了內部函數_.each,這個函數沒有什么好說的~
六、_.reject | _.negate_.reject這個函數內部使用了_.negate,所以,我們先從_.negate看起.
_.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; };
這個函數的意思就是運行傳入進來的cb函數之后,對其運行的結果取反。
下面來看_.reject
_.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); };
不難理解,_.reject就是通過_.filter來實現的。
七、_.every | _.some如果你有興趣查看代碼的話,你會發現這兩個函數除了判斷部分,其它地方基本一模一樣。也不難理解,兩個函數只是邏輯稍微不同而已。
// 判斷是否所有元素都符合條件 // 別名為 `all`. _.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; }; // 判斷至少又一個元素符合條件 // 別名為 `any`. _.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; };八、_.indexOf | _.lastIndexOf
這兩個函數與findIndex以及findLastIndex在寫法上面有異曲同工之處。
_.indexOf以及_.lastIndexOf都引用的一個公共函數,參數中都傳入標識符,用來表示是從頭到尾尋找還是從尾到頭尋找。
我們先來看這個公共函數createIndexFinder:
/** * dir Number 1表示正序查找,-1表示倒敘查找 * predicateFind Function _.findIndex 或者 _.findLastIndex * sortedIndex Function _.sortedIndex */ var createIndexFinder = function(dir, predicateFind, sortedIndex) { return function(array, item, idx) { var i = 0, length = getLength(array); if (typeof idx == "number") { // 正序查找,重置i if (dir > 0) { i = idx >= 0 ? idx : Math.max(idx + length, i); } else { // 倒敘查找,重置length length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } else if (sortedIndex && idx && length) { // 二分法查找 idx = sortedIndex(array, item); return array[idx] === item ? idx : -1; } // item為NaN的情況處理 if (item !== item) { idx = predicateFind(slice.call(array, i, length), _.isNaN); return idx >= 0 ? idx + i : -1; } // NaN處理之后,可以放心的使用for循環迭代了 for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } // 如果上面的都不存在,則返回 -1 return -1; }; };
基本上看懂上面的那個函數,_.findIndex和_.findLastIndex就可以理解啦
_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);九、_.sortedIndex
這個方法咋眼一看對我而言還是有點難理解的。所以我先看它的api描述。
使用二分查找確定value在list中的位置序號,value按此序號插入能保持list原有的排序。如果提供iterator函數,iterator將作為list排序的依據,包括你傳遞的value 。iterator也可以是字符串的屬性名用來排序(比如length)
看源碼如下:
_.sortedIndex = function(array, obj, iteratee, context) { // 如果是一個數組元素為數字,則直接以數組元素大小為查找條件 // 如果是數組元素為對象,則以 iteratee 為查找條件 // 通過 cb 來查找出 數組元素中的 iterate 屬性值 (利用 _.property ) iteratee = cb(iteratee, context, 1); var value = iteratee(obj); var low = 0, high = getLength(array); while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; } return low; };
總體而言,理解二分法以及其對數組元素為對象這種情況的處理方式即可以很好的理解這個方法了。
十、_.range/** * start: 返回數組的起始值 * stop: 返回數組的終止值 * step: 返回數組中間的間隔 */ _.range = function(start, stop, step) { // 如果不存在stop,則默認設置stop為0,start也為0 if (stop == null) { stop = start || 0; start = 0; } // 如果不存在 step 值,則默認設置為 1 或者 -1 if (!step) { step = stop < start ? -1 : 1; } var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); // 遍歷生成數組 for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; };
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80931.html
摘要:什么鬼結合上面的函數,貌似可以看到每次調用函數時都會判斷一次是否等于。主要原理是利用回調函數來處理調用方法傳入的參數。 本文基于underscore v1.8.3版本 源頭 一直想學習一下類庫的源碼,jQuery剛剛看到選擇器那塊,直接被那一大塊正則搞懵逼了。經過同事的推薦,選擇了underscore來作為類庫研究的起點。 閉包 所有函數都在一個閉包內,避免污染全局變量,這沒什么特殊的...
摘要:寫在前面專題系列是我寫的第二個系列,第一個系列是深入系列。專題系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 專題系列是我寫的第二個系列,第一個系列是 JavaScript 深入系列。 JavaScript 專題系列共計 20 篇,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里...
摘要:模塊化是隨著前端技術的發展,前端代碼爆炸式增長后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調也不等同于異步。將會討論安全的類型檢測惰性載入函數凍結對象定時器等話題。 Vue.js 前后端同構方案之準備篇——代碼優化 目前 Vue.js 的火爆不亞于當初的 React,本人對寫代碼有潔癖,代碼也是藝術。此篇是準備篇,工欲善其事,必先利其器。我們先在代...
前言 這是underscore.js源碼分析的第六篇,如果你對這個系列感興趣,歡迎點擊 underscore-analysis/ watch一下,隨時可以看到動態更新。 下劃線中有非常多很有趣的方法,可以用比較巧妙的方式解決我們日常生活中遇到的問題,比如_.after,_.before,_.defer...等,也許你已經用過他們了,今天我們來深入源碼,一探究竟,他們到底是怎么實現的。 showIm...
閱讀 1294·2023-04-25 19:33
閱讀 1175·2021-10-21 09:39
閱讀 3648·2021-09-09 09:32
閱讀 2627·2019-08-30 10:58
閱讀 1618·2019-08-29 16:17
閱讀 881·2019-08-29 15:29
閱讀 2892·2019-08-26 11:55
閱讀 2664·2019-08-26 10:33