摘要:在中我們最常用的就是和兩個(gè)方法了,這兩個(gè)方法一般接收三個(gè)參數(shù),分別是數(shù)組對(duì)象函數(shù)上下文。這個(gè)函數(shù)主要是給傳進(jìn)來(lái)的函數(shù)綁定作用域。
這是underscore源碼剖析系列第三篇文章,主要介紹underscore中each、map、filter、every、reduce等我們常用的一些遍歷數(shù)組的方法。
each在underscore中我們最常用的就是each和map兩個(gè)方法了,這兩個(gè)方法一般接收三個(gè)參數(shù),分別是數(shù)組/對(duì)象、函數(shù)、上下文。
// iteratee函數(shù)有三個(gè)參數(shù),分別是item、index、array或者value、key、obj _.each = _.forEach = function(obj, iteratee, context) { // 如果不傳context,那么each方法里面的this就會(huì)指向window iteratee = optimizeCb(iteratee, context); var i, length; // 如果是類數(shù)組,一般來(lái)說(shuō)包括數(shù)組、arguments、DOM集合等等 if (isArrayLike(obj)) { for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } // 一般是指對(duì)象 } else { var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } return obj; };
each函數(shù)的源碼很簡(jiǎn)單,函數(shù)內(nèi)部會(huì)使用isArrayLike方法來(lái)判斷當(dāng)前傳入的第一個(gè)參數(shù)是類數(shù)組或者對(duì)象,如果是類數(shù)組,直接使用訪問(wèn)下標(biāo)的方式來(lái)遍歷,并將數(shù)組的項(xiàng)和index傳給iteratee函數(shù),如果是對(duì)象,則先獲取到對(duì)象的keys,再進(jìn)行遍歷后將對(duì)象的value和key傳給iteratee函數(shù)
不過(guò)在這里,我們主要分析optimizeCb和isArrayLike兩個(gè)函數(shù)。
optimizeCb// 這個(gè)函數(shù)主要是給傳進(jìn)來(lái)的func函數(shù)綁定context作用域。 var optimizeCb = function (func, context, argCount) { // 如果沒(méi)有傳context,那就直接返回func函數(shù) if (context === void 0) return func; // 如果沒(méi)有傳入argCount,那就默認(rèn)是3。這里是根據(jù)第二次傳入的參數(shù)個(gè)數(shù)來(lái)給call函數(shù)傳入不同數(shù)量的參數(shù) switch (argCount == null ? 3 : argCount) { case 1: return function (value) { return func.call(context, value); }; case 2: return function (value, other) { return func.call(context, value, other); }; // 一般是each、map等 case 3: return function (value, index, collection) { return func.call(context, value, index, collection); }; // 一般是reduce等 case 4: return function (accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } // 如果參數(shù)數(shù)量大于4 return function () { return func.apply(context, arguments); }; };
其實(shí)我們很容易就看出來(lái)optimizeCb函數(shù)只是幫func函數(shù)綁定context的,如果不存在context,那么直接返回func,否則則會(huì)根據(jù)第二次傳給func函數(shù)的參數(shù)數(shù)量來(lái)判斷給call函數(shù)傳幾個(gè)值。
這里有個(gè)重點(diǎn),為什么要用這么麻煩的方式,而不直接用apply來(lái)將arguments全部傳進(jìn)去?
原因是call方法的速度要比apply方法更快,因?yàn)閍pply會(huì)對(duì)數(shù)組參數(shù)進(jìn)行檢驗(yàn)和拷貝,所以這里就對(duì)常用的幾種形式使用了call,其他情況下使用了apply,詳情可以看這里:call和apply
關(guān)于isArrayLike方法,我們來(lái)看underscore的實(shí)現(xiàn)。(這個(gè)延伸比較多,如果沒(méi)興趣,可以跳過(guò))
// 一個(gè)高階函數(shù),返回對(duì)象上某個(gè)具體屬性的值 var property = function (key) { return function (obj) { return obj == null ? void 0 : obj[key]; }; }; // 這里有個(gè)ios8上面的bug,會(huì)導(dǎo)致類似var pbj = {1: "a", 2: "b", 3: "c"}這種對(duì)象的obj.length = 4; jQuery中也有這個(gè)bug。 // https://github.com/jashkenas/underscore/issues/2081 // https://github.com/jquery/jquery/issues/2145 // MAX_SAFE_INTEGER is 9007199254740991 (Math.pow(2, 53) - 1). // http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; // 據(jù)說(shuō)用obj["length"]就可以解決?我沒(méi)有ios8的環(huán)境,有興趣的可以試試 var getLength = property("length"); // 判斷是否是類數(shù)組,如果有l(wèi)ength屬性并且值為number類型即可視作類數(shù)組 var isArrayLike = function (collection) { var length = getLength(collection); return typeof length == "number" && length >= 0 && length <= MAX_ARRAY_INDEX; };
在underscore中,只要帶有l(wèi)ength屬性,都可以被認(rèn)為是類數(shù)組,所以即使是{length: 10}這種情況也會(huì)被歸為類數(shù)組。
我個(gè)人感覺(jué)這樣寫其實(shí)太過(guò)片面,我還是更喜歡jQuery里面isArrayLike方法的實(shí)現(xiàn)。
function isArrayLike(obj) { // Support: real iOS 8.2 only (not reproducible in simulator) // `in` check used to prevent JIT error (gh-2145) // hasOwn isn"t used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, type = toType(obj); // 排除了obj為function和全局中有l(wèi)ength變量的情況 if (isFunction(obj) || isWindow(obj)) { return false; } return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj; }
jQuery中使用in來(lái)解決ios8下面那個(gè)JIT的錯(cuò)誤,同時(shí)還會(huì)排除obj是函數(shù)和window的情況,因?yàn)槿绻鹢bj是函數(shù),那么obj.length則是這個(gè)函數(shù)參數(shù)的個(gè)數(shù),而如果obj是window,那么我在全局中定義一個(gè)var length = 10,這個(gè)同樣也能獲取到length。
最后的三個(gè)判斷分別是:
如果obj的類型是數(shù)組,那么返回true
如果obj的length是0,也返回true。即使是{length: 0}這種情況,因?yàn)樵谡{(diào)用isArrayLike的each和map等方法中會(huì)在for循環(huán)里面判斷l(xiāng)ength,所以也不會(huì)造成影響。
最后這個(gè)(length - 1) in obj我個(gè)人理解就是為了排除{length: 10}這種情況,因?yàn)檫@個(gè)可以滿足length>0和length==="number"的情況,但是一般情況下是無(wú)法滿足最后(length - 1) in obj的,但是NodeList和arguments這些卻可以滿足這個(gè)條件。
map說(shuō)完了each,我們?cè)賮?lái)說(shuō)說(shuō)map,map函數(shù)其實(shí)和each的實(shí)現(xiàn)很類似,不過(guò)不一樣的一個(gè)地方在于,map函數(shù)的第二個(gè)參數(shù)不一定是函數(shù),我們可以什么都不傳,甚至還可以傳個(gè)對(duì)象。
var arr = [{name:"Kevin"}, {name: "Daisy", age: 18}] var result1 = _.map(arr); // [{name:"Kevin"}, {name: "Daisy", age: 18}] var result2 = _.map(arr, {name: "Daisy"}) // [false, true]
所以這里就會(huì)對(duì)傳入map的第二個(gè)參數(shù)進(jìn)行判斷,整體來(lái)說(shuō)map函數(shù)的實(shí)現(xiàn)比each更加簡(jiǎn)潔。
_.map = _.collect = function (obj, iteratee, context) { // 因?yàn)樵趍ap中,第二個(gè)參數(shù)可能不是函數(shù),所以用cb,這點(diǎn)和each的實(shí)現(xiàn)不一樣。 iteratee = cb(iteratee, context); // 如果不是類數(shù)組(是對(duì)象),則獲取到keys var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); // 這里根據(jù)keys是否存在來(lái)判斷傳給iteratee是key還是index for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; };cb
我們來(lái)看看map函數(shù)中這個(gè)cb函數(shù)到底是什么來(lái)歷?
_.identity = function (value) { return value; }; var cb = function (value, context, argCount) { // 如果value不存在 if (value == null) return _.identity; // 如果傳入的是個(gè)函數(shù) if (_.isFunction(value)) return optimizeCb(value, context, argCount); // 如果傳入的是個(gè)對(duì)象 if (_.isObject(value)) return _.matcher(value); return _.property(value); };
cb函數(shù)在underscore中一般是用在遍歷方法中,大多數(shù)情況下value都是一個(gè)函數(shù),我們結(jié)合上面map的源碼和例子來(lái)看。
如果value不存在,那就對(duì)應(yīng)上面的_.map(obj)的情況,map中的iteratee就是_.identity函數(shù),他會(huì)將后面接收到的obj[currentKey]直接返回。
如果value是一個(gè)函數(shù),就對(duì)應(yīng)_.map(obj, func)這種情況,那么會(huì)再調(diào)用optimizeCb方法,這里就和each的實(shí)現(xiàn)是一樣的
如果value是個(gè)對(duì)象,對(duì)應(yīng)_.map(obj, arrts)的情況,就會(huì)比較obj中的屬性是否在arr里面,這個(gè)時(shí)候會(huì)調(diào)用_.matcher函數(shù)
這種情況一般是用在_.iteratee函數(shù)中,用來(lái)訪問(wèn)對(duì)象的某個(gè)屬性,具體看這里:iteratee函數(shù)
matcher那么我們?cè)賮?lái)看matcher函數(shù),matcher函數(shù)內(nèi)部對(duì)兩個(gè)對(duì)象做了淺比較。
_.matcher = _.matches = function (attrs) { // 將attrs和{}合并為一個(gè)對(duì)象(避免attrs為undefined) attrs = _.extendOwn({}, attrs); return function (obj) { return _.isMatch(obj, attrs); }; }; // isMatch方法會(huì)對(duì)接收到的attrs對(duì)象進(jìn)行遍歷,同時(shí)比較obj中是否有這一項(xiàng) _.isMatch = function (object, attrs) { var keys = _.keys(attrs), length = keys.length; // 如果object和attr都是空,那么返回true,否則object為空時(shí)返回false if (object == null) return !length; // 這一步?jīng)]懂是為了做什么? 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; };
matcher是個(gè)高階方法,他會(huì)將兩次接收到的對(duì)象傳給isMatch函數(shù)來(lái)進(jìn)行判斷。首先是以attrs為被遍歷的對(duì)象,通過(guò)對(duì)比obj[key]和attrs[key]的值,只要obj中的值和attrs中的不想等,就會(huì)返回false。
這里還會(huì)排除一種情況,如果attrs中對(duì)應(yīng)key的value正好是undefined,而且obj中并沒(méi)有key這個(gè)屬性,這樣obj[key]和attrs[key]其實(shí)都是undefined,這里使用!==來(lái)比較必然會(huì)返回false,實(shí)際上兩者應(yīng)該是不想等的。
所以使用in來(lái)判斷obj上到底有沒(méi)有key這個(gè)屬性,如果沒(méi)有,也會(huì)返回false。如果attrs上面所有屬性在obj中都能找到,并且兩者的值正好相等,那么就會(huì)返回true。
這也就是為什么_.map([{name:"Kevin"}, {name: "Daisy", age: 18}], {name: "Daisy"}); 會(huì)返回 [false, true]。
each和map實(shí)現(xiàn)原理基本上一樣,不過(guò)map更加簡(jiǎn)潔,這里可以用map的形式重寫一下each
_.each = _.forEach = function (obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; iteratee(obj[currentKey], currentKey, obj); } return obj; };filter、every、some、reject
這幾種方法的實(shí)現(xiàn)和上面的each、map類似,這里就不多做解釋了,有興趣的可以自己去看一下。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/93559.html
摘要:在上篇文章整體架構(gòu)分析中,我們講過(guò)上面的方法有兩種掛載方式,一個(gè)是掛載到構(gòu)造函數(shù)上以的形式直接調(diào)用在后文上統(tǒng)稱構(gòu)造函數(shù)調(diào)用,另一種則是掛到上以的形式被實(shí)例調(diào)用在后文上統(tǒng)稱原型調(diào)用。 underscore源碼分析之基礎(chǔ)方法 本文是underscore源碼剖析系列的第二篇,主要介紹underscore中一些基礎(chǔ)方法的實(shí)現(xiàn)。 mixin 在上篇文章underscore整體架構(gòu)分析中,我們講...
摘要:引子數(shù)組去重是一個(gè)老生常談的話題,在面試中也經(jīng)常會(huì)被問(wèn)道。其中如果數(shù)組是排序的,去重運(yùn)算效率更高,因?yàn)榕判蚰軌驅(qū)⑾嗤臄?shù)排列在一起,方便前后比較。當(dāng)數(shù)組有序?qū)τ趯?duì)象的去重,我們知道為,所以使用比較對(duì)象在實(shí)際場(chǎng)景中沒(méi)有意義。 引子 數(shù)組去重是一個(gè)老生常談的話題,在面試中也經(jīng)常會(huì)被問(wèn)道。對(duì)于去重,有兩種主流思想: 先排序,線性遍歷后去重,時(shí)間復(fù)雜度O(n*log2n); 使用哈希,空間換...
摘要:譯立即執(zhí)行函數(shù)表達(dá)式處理支持瀏覽器環(huán)境微信小程序。學(xué)習(xí)整體架構(gòu),利于打造屬于自己的函數(shù)式編程類庫(kù)。下一篇文章可能是學(xué)習(xí)的源碼整體架構(gòu)。也可以加微信,注明來(lái)源,拉您進(jìn)前端視野交流群。 前言 上一篇文章寫了jQuery整體架構(gòu),學(xué)習(xí) jQuery 源碼整體架構(gòu),打造屬于自己的 js 類庫(kù) 雖然看過(guò)挺多underscore.js分析類的文章,但總感覺(jué)少點(diǎn)什么。這也許就是紙上得來(lái)終覺(jué)淺,絕知此...
摘要:遍歷中的所有元素,按順序用遍歷輸出每個(gè)元素。如果傳遞了參數(shù),則把綁定到對(duì)象上。返回以方便鏈?zhǔn)秸{(diào)用。 each _.each(list, iteratee, [context])?Alias:?forEach?遍歷list中的所有元素,按順序用遍歷輸出每個(gè)元素。如果傳遞了context參數(shù),則把iteratee綁定到context對(duì)象上。每次調(diào)用iteratee都會(huì)傳遞三個(gè)參數(shù):(ele...
摘要:目前通行的模塊規(guī)范主要集中在和,因此為了讓定義的庫(kù)能夠適用于各種規(guī)范。在框架的定義時(shí)需檢測(cè)使用環(huán)境并兼容各種規(guī)范。服務(wù)端規(guī)范,檢測(cè)是否存在,滿足時(shí)通過(guò)將暴露出來(lái),不滿足則通過(guò)對(duì)象暴露出來(lái)。前者回調(diào)函數(shù)處理的是值和下標(biāo),后者處理的是值和屬性。 本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處 https://www.cnblogs.com/kidfl... underscore作為開(kāi)發(fā)中比較常用的一個(gè)...
閱讀 2101·2021-11-18 10:02
閱讀 2850·2021-09-04 16:41
閱讀 1142·2019-08-30 15:55
閱讀 1405·2019-08-29 17:27
閱讀 1070·2019-08-29 17:12
閱讀 2535·2019-08-29 15:38
閱讀 2855·2019-08-29 13:02
閱讀 2831·2019-08-29 12:29