摘要:本文同步自我得博客我在這個系列的第一篇文章說過,我學是為了在學的時候少一些阻礙,從第一篇的寫作時間到今天,大概也有個十幾二十天,感覺拖得有點久,所以今天將會是源碼解析系列的最后一篇文章,我會在這篇文章中介紹剩下的所有函數(shù)。
本文同步自我得博客:http://www.joeray61.com
我在這個系列的第一篇文章說過,我學underscore是為了在學backbone的時候少一些阻礙,從第一篇的寫作時間到今天,大概也有個十幾二十天,感覺拖得有點久,所以今天將會是underscore源碼解析系列的最后一篇文章,我會在這篇文章中介紹underscore剩下的所有函數(shù)。
先附上前三篇文章的地址:Underscore源碼解析(一)、Underscore源碼解析(二)、Underscore源碼解析(三)
_.zip = function() { // 將參數(shù)轉換為數(shù)組, 此時args是一個二維數(shù)組 var args = slice.call(arguments); // 計算每一個數(shù)組的長度, 并返回其中最大長度值 var length = _.max(_.pluck(args, "length")); // 依照最大長度值創(chuàng)建一個新的空數(shù)組, 該數(shù)組用于存儲處理結果 var results = new Array(length); // 循環(huán)最大長度, 在每次循環(huán)將調用pluck方法獲取每個數(shù)組中相同位置的數(shù)據(jù)(依次從0到最后位置) // 將獲取到的數(shù)據(jù)存儲在一個新的數(shù)組, 放入results并返回 for(var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); // 返回的結果是一個二維數(shù)組 return results; };
這個函數(shù)將每個數(shù)組的相同位置的數(shù)據(jù)作為一個新的二維數(shù)組返回, 返回的數(shù)組長度以傳入?yún)?shù)中最大的數(shù)組長度為準, 其它數(shù)組的空白位置使用undefined填充。zip函數(shù)應該包含多個參數(shù), 且每個參數(shù)應該均為數(shù)組。
_.indexOf_.indexOf = function(array, item, isSorted) { if(array == null) return -1; var i, l; // 若數(shù)組已經(jīng)經(jīng)過排序,則調用sortedIndex方法,獲取元素插入數(shù)組中所處位置的索引號 if(isSorted) { i = _.sortedIndex(array, item); return array[i] === item ? i : -1; } // 優(yōu)先調用宿主環(huán)境提供的indexOf方法 if(nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); // 循環(huán)并返回元素首次出現(xiàn)的位置 for( i = 0, l = array.length; i < l; i++) if( i in array && array[i] === item) return i; // 沒有找到元素, 返回-1 return -1; };
這個函數(shù)的作用是搜索一個元素在數(shù)組中首次出現(xiàn)的位置, 如果元素不存在則返回 -1,搜索時使用 === 對元素進行匹配
_.lastIndexOf_.lastIndexOf = function(array, item) { if(array == null) return -1; // 優(yōu)先調用宿主環(huán)境提供的lastIndexOf方法 if(nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); var i = array.length; // 循環(huán)并返回元素最后出現(xiàn)的位置 while(i--) if( i in array && array[i] === item) return i; // 沒有找到元素, 返回-1 return -1; };
這個函數(shù)返回一個元素在數(shù)組中最后一次出現(xiàn)的位置, 如果元素不存在則返回 -1,搜索時使用 === 對元素進行匹配
_.range_.range = function(start, stop, step) { // 參數(shù)控制 if(arguments.length <= 1) { // 如果沒有參數(shù), 則start = 0, stop = 0, 在循環(huán)中不會生成任何數(shù)據(jù), 將返回一個空數(shù)組 // 如果有1個參數(shù), 則參數(shù)指定給stop, start = 0 stop = start || 0; start = 0; } // 生成整數(shù)的步長值, 默認為1 step = arguments[2] || 1; // 根據(jù)區(qū)間和步長計算將生成的最大值 var len = Math.max(Math.ceil((stop - start) / step), 0); var idx = 0; var range = new Array(len); // 生成整數(shù)列表, 并存儲到range數(shù)組 while(idx < len) { range[idx++] = start; start += step; } // 返回列表結果 return range; };
這個函數(shù)根據(jù)區(qū)間和步長, 生成一系列整數(shù), 并作為數(shù)組返回,start參數(shù)表示最小數(shù),stop參數(shù)表示最大數(shù),step參數(shù)表示步長
_.bind_.bind = function bind(func, context) { var bound, args; // 優(yōu)先調用宿主環(huán)境提供的bind方法 if(func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); // func參數(shù)必須是一個函數(shù)(Function)類型 if(!_.isFunction(func)) throw new TypeError; // args變量存儲了bind方法第三個開始的參數(shù)列表, 每次調用時都將傳遞給func函數(shù) args = slice.call(arguments, 2); return bound = function() { if(!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); ctor.prototype = func.prototype; var self = new ctor; var result = func.apply(self, args.concat(slice.call(arguments))); if(Object(result) === result) return result; return self; }; };
這個函數(shù)為一個函數(shù)綁定執(zhí)行上下文, 任何情況下調用該函數(shù), 函數(shù)中的this均指向context對象,綁定函數(shù)時, 可以同時給函數(shù)傳遞調用形參
_.bindAll_.bindAll = function(obj) { // 第二個參數(shù)開始表示需要綁定的函數(shù)名稱 var funcs = slice.call(arguments, 1); // 如果沒有指定特定的函數(shù)名稱, 則默認綁定對象本身所有類型為Function的屬性 if(funcs.length == 0) funcs = _.functions(obj); // 循環(huán)并將所有的函數(shù)上下本設置為obj對象本身 // each方法本身不會遍歷對象原型鏈中的方法, 但此處的funcs列表是通過_.functions方法獲取的, 它已經(jīng)包含了原型鏈中的方法 each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); return obj; };
這個函數(shù)將指定的函數(shù), 或對象本身的所有函數(shù)上下本綁定到對象本身, 被綁定的函數(shù)在被調用時, 上下文對象始終指向對象本身
_.memoize_.memoize = function(func, hasher) { // 用于存儲緩存結果的memo對象 var memo = {}; // hasher參數(shù)應該是一個function, 它用于返回一個key, 該key作為讀取緩存的標識 // 如果沒有指定key, 則默認使用函數(shù)的第一個參數(shù)作為key, 如果函數(shù)的第一個參數(shù)是復合數(shù)據(jù)類型, 可能會返回類似[Object object]的key, 這個key可能會造成后續(xù)計算的數(shù)據(jù)不正確 hasher || ( hasher = _.identity); // 返回一個函數(shù), 該函數(shù)首先通過檢查緩存, 再對沒有緩存過的數(shù)據(jù)進行調用 return function() { var key = hasher.apply(this, arguments); return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); }; };
這個函數(shù)將返回一個函數(shù), 該函數(shù)集成了緩存功能, 將經(jīng)過計算的值緩存到局部變量并在下次調用時直接返回
_.delay_.delay = function(func, wait) { var args = slice.call(arguments, 2); // 通過setTimeout來延時執(zhí)行 return setTimeout(function() { return func.apply(null, args); }, wait); };
這個函數(shù)的作用是延時執(zhí)行一個函數(shù),wait單位為ms, 第3個參數(shù)開始將被依次傳遞給執(zhí)行函數(shù)
_.defer_.defer = function(func) { // 相當于_.delay(func, 1, [arguments]); return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); };
這個函數(shù)的作用是延遲1ms執(zhí)行函數(shù),javascript是一個單線程的程序,setTimeout(func, time)作用是把func放到處理任務的隊列末尾,在其他任務都完成之后的time ms 后執(zhí)行func
_.throttle_.throttle = function(func, wait) { var context, args, timeout, throttling, more, result; // whenDone變量調用了debounce方法, 因此在多次連續(xù)調用函數(shù)時, 最后一次調用會覆蓋之前調用的定時器, 清除狀態(tài)函數(shù)也僅會被執(zhí)行一次 // whenDone函數(shù)在最后一次函數(shù)執(zhí)行的時間間隔截止時調用, 清除節(jié)流和調用過程中記錄的一些狀態(tài) var whenDone = _.debounce(function() { more = throttling = false; }, wait); // 返回一個函數(shù), 并在函數(shù)內(nèi)進行節(jié)流控制 return function() { // 保存函數(shù)的執(zhí)行上下文和參數(shù) context = this; args = arguments; // later函數(shù)在上一次函數(shù)調用時間間隔截止時執(zhí)行 var later = function() { // 清除timeout句柄, 方便下一次函數(shù)調用 timeout = null; // more記錄了在上一次調用至時間間隔截止之間, 是否重復調用了函數(shù) // 如果重復調用了函數(shù), 在時間間隔截止時將自動再次調用函數(shù) if(more) func.apply(context, args); // 調用whenDone, 用于在時間間隔后清除節(jié)流狀態(tài) whenDone(); }; // timeout記錄了上一次函數(shù)執(zhí)行的時間間隔句柄 // timeout時間間隔截止時調用later函數(shù), later中將清除timeout, 并檢查是否需要再次調用函數(shù) if(!timeout) timeout = setTimeout(later, wait); // throttling變量記錄上次調用的時間間隔是否已經(jīng)結束, 即是否處于節(jié)流過程中 // throttling在每次函數(shù)調用時設為true, 表示需要進行節(jié)流, 在時間間隔截止時設置為false(在whenDone函數(shù)中實現(xiàn)) if(throttling) { // 節(jié)流過程中進行了多次調用, 在more中記錄一個狀態(tài), 表示在時間間隔截止時需要再次自動調用函數(shù) more = true; } else { // 沒有處于節(jié)流過程, 可能是第一次調用函數(shù), 或已經(jīng)超過上一次調用的間隔, 可以直接調用函數(shù) result = func.apply(context, args); } // 調用whenDone, 用于在時間間隔后清除節(jié)流狀態(tài) whenDone(); // throttling變量記錄函數(shù)調用時的節(jié)流狀態(tài) throttling = true; // 返回調用結果 return result; }; };
這是函數(shù)節(jié)流方法, throttle方法主要用于控制函數(shù)的執(zhí)行頻率, 在被控制的時間間隔內(nèi), 頻繁調用函數(shù)不會被多次執(zhí)行,在時間間隔內(nèi)如果多次調用了函數(shù), 時間隔截止時會自動調用一次, 不需要等到時間截止后再手動調用(自動調用時不會有返回值),throttle函數(shù)一般用于處理復雜和調用頻繁的函數(shù), 通過節(jié)流控制函數(shù)的調用頻率, 節(jié)省處理資源
_.debounce_.debounce = function(func, wait, immediate) { // timeout用于記錄函數(shù)上一次調用的執(zhí)行狀態(tài)(定時器句柄) // 當timeout為null時, 表示上一次調用已經(jīng)結束 var timeout; // 返回一個函數(shù), 并在函數(shù)內(nèi)進行節(jié)流控制 return function() { // 保持函數(shù)的上下文對象和參數(shù) var context = this, args = arguments; var later = function() { // 設置timeout為null // later函數(shù)會在允許的時間截止時被調用 // 調用該函數(shù)時, 表明上一次函數(shù)執(zhí)行時間已經(jīng)超過了約定的時間間隔, 此時之后再進行調用都是被允許的 timeout = null; if(!immediate) func.apply(context, args); }; // 如果函數(shù)被設定為立即執(zhí)行, 且上一次調用的時間間隔已經(jīng)過去, 則立即調用函數(shù) if(immediate && !timeout) func.apply(context, args); // 創(chuàng)建一個定時器用于檢查和設置函數(shù)的調用狀態(tài) // 創(chuàng)建定時器之前先清空上一次setTimeout句柄, 無論上一次綁定的函數(shù)是否已經(jīng)被執(zhí)行 // 如果本次函數(shù)在調用時, 上一次函數(shù)執(zhí)行還沒有開始(一般是immediate設置為false時), 則函數(shù)的執(zhí)行時間會被推遲, 因此timeout句柄會被重新創(chuàng)建 clearTimeout(timeout); // 在允許的時間截止時調用later函數(shù) timeout = setTimeout(later, wait); }; };
debounce與throttle方法類似, 用于函數(shù)節(jié)流, 它們的不同之處在于:
-- throttle關注函數(shù)的執(zhí)行頻率, 在指定頻率內(nèi)函數(shù)只會被執(zhí)行一次 -- debounce函數(shù)更關注函數(shù)執(zhí)行的間隔, 即函數(shù)兩次的調用時間不能小于指定時間
如果兩次函數(shù)的執(zhí)行間隔小于wait, 定時器會被清除并重新創(chuàng)建, 這意味著連續(xù)頻繁地調用函數(shù), 函數(shù)一直不會被執(zhí)行, 直到某一次調用與上一次調用的時間不小于wait毫秒
_.once_.once = function(func) { // ran記錄函數(shù)是否被執(zhí)行過 // memo記錄函數(shù)最后一次執(zhí)行的結果 var ran = false, memo; return function() { // 如果函數(shù)已被執(zhí)行過, 則直接返回第一次執(zhí)行的結果 if(ran) return memo; ran = true; return memo = func.apply(this, arguments); }; };
這個函數(shù)創(chuàng)建一個只會被執(zhí)行一次的函數(shù), 如果該函數(shù)被重復調用, 將返回第一次執(zhí)行的結果
_.wrap_.wrap = function(func, wrapper) { return function() { // 將當前函數(shù)作為第一個參數(shù), 傳遞給wrapper函數(shù) var args = [func].concat(slice.call(arguments, 0)); // 返回wrapper函數(shù)的處理結果 return wrapper.apply(this, args); }; };
這個函數(shù)返回一個函數(shù), 該函數(shù)會將當前函數(shù)作為參數(shù)傳遞給一個包裹函數(shù),在包裹函數(shù)中可以通過第一個參數(shù)調用當前函數(shù), 并返回結果
_.compose_.compose = function() { // 獲取函數(shù)列表, 所有參數(shù)需均為Function類型 var funcs = arguments; // 返回一個供調用的函數(shù)句柄 return function() { // 從后向前依次執(zhí)行函數(shù), 并將記錄的返回值作為參數(shù)傳遞給前一個函數(shù)繼續(xù)處理 var args = arguments; for(var i = funcs.length - 1; i >= 0; i--) { args = [funcs[i].apply(this, args)]; } // 返回最后一次調用函數(shù)的返回值 return args[0]; }; };
這個函數(shù)將多個函數(shù)組合到一起, 按照參數(shù)傳遞的順序, 后一個函數(shù)的返回值會被依次作為參數(shù)傳遞給前一個函數(shù)作為參數(shù)繼續(xù)處理,_.compose(A, B, C)等同于 A(B(C()))
_.after_.after = function(times, func) { // 如果沒有指定或指定無效次數(shù), 則func被直接調用 if(times <= 0) return func(); // 返回一個計數(shù)器函數(shù) return function() { // 每次調用計數(shù)器函數(shù)times減1, 調用times次之后執(zhí)行func函數(shù)并返回func函數(shù)的返回值 if(--times < 1) { return func.apply(this, arguments); } }; };
after返回一個函數(shù), 該函數(shù)作為調用計數(shù)器, 當該函數(shù)被調用times次(或超過times次)后, func函數(shù)將被執(zhí)行
_.keys_.keys = nativeKeys || function(obj) { if(obj !== Object(obj)) throw new TypeError("Invalid object"); var keys = []; // 記錄并返回對象的所有屬性名 for(var key in obj) if(_.has(obj, key)) keys[keys.length] = key; return keys; };
這個函數(shù)用于獲取一個對象的屬性名列表(不包含原型鏈中的屬性)
_.values_.values = function(obj) { return _.map(obj, _.identity); };
這個函數(shù)返回一個對象中所有屬性的值列表(不包含原型鏈中的屬性)
_.functions / _.methods_.functions = _.methods = function(obj) { var names = []; for(var key in obj) { if(_.isFunction(obj[key])) names.push(key); } return names.sort(); };
這個函數(shù)獲取一個對象中所有屬性值為Function類型的key列表, 并按key名進行排序(包含原型鏈中的屬性)
_.extend_.extend = function(obj) { // each循環(huán)參數(shù)中的一個或多個對象 each(slice.call(arguments, 1), function(source) { // 將對象中的全部屬性復制或覆蓋到obj對象 for(var prop in source) { obj[prop] = source[prop]; } }); return obj; };
這個函數(shù)將一個或多個對象的屬性(包含原型鏈中的屬性), 復制到obj對象, 如果存在同名屬性則覆蓋
_.pick_.pick = function(obj) { // 創(chuàng)建一個對象, 存放復制的指定屬性 var result = {}; // 從第二個參數(shù)開始合并為一個存放屬性名列表的數(shù)組 each(_.flatten(slice.call(arguments, 1)), function(key) { // 循環(huán)屬性名列表, 如果obj中存在該屬性, 則將其復制到result對象 if( key in obj) result[key] = obj[key]; }); // 返回復制結果 return result; };
這個函數(shù)返回一個新對象, 并從obj中復制指定的屬性到新對象中,第2個參數(shù)開始為指定的需要復制的屬性名
_.defaults_.defaults = function(obj) { // 從第二個參數(shù)開始可指定多個對象, 這些對象中的屬性將被依次復制到obj對象中(如果obj對象中不存在該屬性的話) each(slice.call(arguments, 1), function(source) { // 遍歷每個對象中的所有屬性 for(var prop in source) { // 如果obj中不存在或屬性值轉換為Boolean類型后值為false, 則將屬性復制到obj中 if(obj[prop] == null) obj[prop] = source[prop]; } }); return obj; };
這個函數(shù)將obj中不存在或轉換為Boolean類型后值為false的屬性, 從參數(shù)中指定的一個或多個對象中復制到obj,一般用于給對象指定默認值
_.clone_.clone = function(obj) { // 不支持非數(shù)組和對象類型的數(shù)據(jù) if(!_.isObject(obj)) return obj; // 復制并返回數(shù)組或對象 return _.isArray(obj) ? obj.slice() : _.extend({}, obj); };
這個函數(shù)創(chuàng)建一個obj的副本, 返回一個新的對象, 該對象包含obj中的所有屬性和值的狀態(tài)
_.tap_.tap = function(obj, interceptor) { interceptor(obj); return obj; };
這個函數(shù)執(zhí)行一個函數(shù), 并將obj作為參數(shù)傳遞給該函數(shù), 函數(shù)執(zhí)行完畢后最終返回obj對象
eqfunction eq(a, b, stack) { // 檢查兩個簡單數(shù)據(jù)類型的值是否相等 // 對于復合數(shù)據(jù)類型, 如果它們來自同一個引用, 則認為其相等 // 如果被比較的值其中包含0, 則檢查另一個值是否為-0, 因為 0 === -0 是成立的 // 而 1 / 0 == 1 / -0 是不成立的(1 / 0值為Infinity, 1 / -0值為-Infinity, 而Infinity不等于-Infinity) if(a === b) return a !== 0 || 1 / a == 1 / b; // 將數(shù)據(jù)轉換為布爾類型后如果值為false, 將判斷兩個值的數(shù)據(jù)類型是否相等(因為null與undefined, false, 0, 空字符串, 在非嚴格比較下值是相等的) if(a == null || b == null) return a === b; // 如果進行比較的數(shù)據(jù)是一個Underscore封裝的對象(具有_chain屬性的對象被認為是Underscore對象) // 則將對象解封后獲取本身的數(shù)據(jù)(通過_wrapped訪問), 然后再對本身的數(shù)據(jù)進行比較 // 它們的關系類似與一個jQuery封裝的DOM對象, 和瀏覽器本身創(chuàng)建的DOM對象 if(a._chain) a = a._wrapped; if(b._chain) b = b._wrapped; // 如果對象提供了自定義的isEqual方法(此處的isEqual方法并非Undersocre對象的isEqual方法, 因為在上一步已經(jīng)對Undersocre對象進行了解封) // 則使用對象自定義的isEqual方法與另一個對象進行比較 if(a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); if(b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); // 對兩個數(shù)據(jù)的數(shù)據(jù)類型進行驗證 // 獲取對象a的數(shù)據(jù)類型(通過Object.prototype.toString方法) var className = toString.call(a); // 如果對象a的數(shù)據(jù)類型與對象b不匹配, 則認為兩個數(shù)據(jù)值也不匹配 if(className != toString.call(b)) return false; // 執(zhí)行到此處, 可以確保需要比較的兩個數(shù)據(jù)均為復合數(shù)據(jù)類型, 且數(shù)據(jù)類型相等 // 通過switch檢查數(shù)據(jù)的數(shù)據(jù)類型, 針對不同數(shù)據(jù)類型進行不同的比較 // (此處不包括對數(shù)組和對象類型, 因為它們可能包含更深層次的數(shù)據(jù), 將在后面進行深層比較) switch (className) { case "[object String]": // 如果被比較的是字符串類型(其中a的是通過new String()創(chuàng)建的字符串) // 則將B轉換為String對象后進行匹配(這里匹配并非進行嚴格的數(shù)據(jù)類型檢查, 因為它們并非來自同一個對象的引用) // 在調用 == 進行比較時, 會自動調用對象的toString()方法, 返回兩個簡單數(shù)據(jù)類型的字符串 return a == String(b); case "[object Number]": // 通過+a將a轉成一個Number, 如果a被轉換之前與轉換之后不相等, 則認為a是一個NaN類型 // 因為NaN與NaN是不相等的, 因此當a值為NaN時, 無法簡單地使用a == b進行匹配, 而是用相同的方法檢查b是否為NaN(即 b != +b) // 當a值是一個非NaN的數(shù)據(jù)時, 則檢查a是否為0, 因為當b為-0時, 0 === -0是成立的(實際上它們在邏輯上屬于兩個不同的數(shù)據(jù)) return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); case "[object Date]": // 對日期類型沒有使用return或break, 因此會繼續(xù)執(zhí)行到下一步(無論數(shù)據(jù)類型是否為Boolean類型, 因為下一步將對Boolean類型進行檢查) case "[object Boolean]": // 將日期或布爾類型轉換為數(shù)字 // 日期類型將轉換為數(shù)值類型的時間戳(無效的日期格式將被換轉為NaN) // 布爾類型中, true被轉換為1, false被轉換為0 // 比較兩個日期或布爾類型被轉換為數(shù)字后是否相等 return +a == +b; case "[object RegExp]": // 正則表達式類型, 通過source訪問表達式的字符串形式 // 檢查兩個表達式的字符串形式是否相等 // 檢查兩個表達式的全局屬性是否相同(包括g, i, m) // 如果完全相等, 則認為兩個數(shù)據(jù)相等 return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase; } // 當執(zhí)行到此時, ab兩個數(shù)據(jù)應該為類型相同的對象或數(shù)組類型 if( typeof a != "object" || typeof b != "object") return false; // stack(堆)是在isEqual調用eq函數(shù)時內(nèi)部傳遞的空數(shù)組, 在后面比較對象和數(shù)據(jù)的內(nèi)部迭代中調用eq方法也會傳遞 // length記錄堆的長度 var length = stack.length; while(length--) { // 如果堆中的某個對象與數(shù)據(jù)a匹配, 則認為相等 if(stack[length] == a) return true; } // 將數(shù)據(jù)a添加到堆中 stack.push(a); // 定義一些局部變量 var size = 0, result = true; // 通過遞歸深層比較對象和數(shù)組 if(className == "[object Array]") { // 被比較的數(shù)據(jù)為數(shù)組類型 // size記錄數(shù)組的長度 // result比較兩個數(shù)組的長度是否一致, 如果長度不一致, 則方法的最后將返回result(即false) size = a.length; result = size == b.length; // 如果兩個數(shù)組的長度一致 if(result) { // 調用eq方法對數(shù)組中的元素進行迭代比較(如果數(shù)組中包含二維數(shù)組或對象, eq方法會進行深層比較) while(size--) { // 在確保兩個數(shù)組都存在當前索引的元素時, 調用eq方法深層比較(將堆數(shù)據(jù)傳遞給eq方法) // 將比較的結果存儲到result變量, 如果result為false(即在比較中得到某個元素的數(shù)據(jù)不一致), 則停止迭代 if(!( result = size in a == size in b && eq(a[size], b[size], stack))) break; } } } else { // 被比較的數(shù)據(jù)為對象類型 // 如果兩個對象不是同一個類的實例(通過constructor屬性比較), 則認為兩個對象不相等 if("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false; // 深層比較兩個對象中的數(shù)據(jù) for(var key in a) { if(_.has(a, key)) { // size用于記錄比較過的屬性數(shù)量, 因為這里遍歷的是a對象的屬性, 并比較b對象中該屬性的數(shù)據(jù) // 當b對象中的屬性數(shù)量多余a對象時, 此處的邏輯成立, 但兩個對象并不相等 size++; // 迭代調用eq方法, 深層比較兩個對象中的屬性值 // 將比較的結果記錄到result變量, 當比較到不相等的數(shù)據(jù)時停止迭代 if(!( result = _.has(b, key) && eq(a[key], b[key], stack))) break; } } // 深層比較完畢, 這里已經(jīng)可以確保在對象a中的所有數(shù)據(jù), 對象b中也存在相同的數(shù)據(jù) // 根據(jù)size(對象屬性長度)檢查對象b中的屬性數(shù)量是否與對象a相等 if(result) { // 遍歷對象b中的所有屬性 for(key in b) { // 當size已經(jīng)到0時(即對象a中的屬性數(shù)量已經(jīng)遍歷完畢), 而對象b中還存在有屬性, 則對象b中的屬性多于對象a if(_.has(b, key) && !(size--)) break; } // 當對象b中的屬性多于對象a, 則認為兩個對象不相等 result = !size; } } // 函數(shù)執(zhí)行完畢時, 從堆中移除第一個數(shù)據(jù)(在比較對象或數(shù)組時, 會迭代eq方法, 堆中可能存在多個數(shù)據(jù)) stack.pop(); // 返回的result記錄了最終的比較結果 return result; }
eq函數(shù)只在isEqual方法中調用, 用于比較兩個數(shù)據(jù)的值是否相等,與 === 不同在于, eq更關注數(shù)據(jù)的值,如果進行比較的是兩個復合數(shù)據(jù)類型, 不僅僅比較是否來自同一個引用, 且會進行深層比較(對兩個對象的結構和數(shù)據(jù)進行比較)
_.isEqual_.isEqual = function(a, b) { return eq(a, b, []); };
不多說了,就是內(nèi)部函數(shù)eq的外部方法
_.isEmpty_.isEmpty = function(obj) { // obj被轉換為Boolean類型后值為false if(obj == null) return true; // 檢查對象或字符串長度是否為0 if(_.isArray(obj) || _.isString(obj)) return obj.length === 0; // 檢查對象(使用for in循環(huán)時將首先循環(huán)對象本身的屬性, 其次是原型鏈中的屬性), 因此如果第一個屬性是屬于對象本身的, 那么該對象不是一個空對象 for(var key in obj) if(_.has(obj, key)) return false; // 所有數(shù)據(jù)類型均沒有通過驗證, 是一個空數(shù)據(jù) return true; };
這個函數(shù)用于檢查數(shù)據(jù)是否為空值, 包含"", false, 0, null, undefined, NaN, 空數(shù)組(數(shù)組長度為0)和空對象(對象本身沒有任何屬性)
_.isElement_.isElement = function(obj) { return !!(obj && obj.nodeType == 1); };
這個函數(shù)用于驗證對象是否是一個DOM對象
_.isArray_.isArray = nativeIsArray || function(obj) { return toString.call(obj) == "[object Array]"; };
這個函數(shù)用于驗證一個變量是否是數(shù)組
_.isObject_.isObject = function(obj) { return obj === Object(obj); };
這個函數(shù)用于驗證對象是否是一個復合數(shù)據(jù)類型的對象(即非基本數(shù)據(jù)類型String, Boolean, Number, null, undefined)
_.isArguments_.isArguments = function(obj) { return toString.call(obj) == "[object Arguments]"; }; // 驗證isArguments函數(shù), 如果運行環(huán)境無法正常驗證arguments類型的數(shù)據(jù), 則重新定義isArguments方法 if(!_.isArguments(arguments)) { // 對于環(huán)境無法通過toString驗證arguments類型的, 則通過調用arguments獨有的callee方法來進行驗證 _.isArguments = function(obj) { // callee是arguments的一個屬性, 指向對arguments所屬函數(shù)自身的引用 return !!(obj && _.has(obj, "callee")); }; }
這個函數(shù)用于檢查一個數(shù)據(jù)是否是一個arguments參數(shù)對象
_.isFunction / _.isString / _.isNumber / _.isDate / _.isRegExp這幾個我就放在一起說了,他們都是通過Object.prototype.toString.call(obj)的值來進行判斷的
_.isFinite_.isFinite = function(obj) { return _.isNumber(obj) && isFinite(obj); };
這個函數(shù)用于檢查一個數(shù)字是否為有效數(shù)字且有效范圍(Number類型, 值在負無窮大 - 正無窮大之間)
_.isNaN_.isNaN = function(obj) { return obj !== obj; };
在js里,所有數(shù)據(jù)中只有NaN與NaN不相等
_.isBoolean_.isBoolean = function(obj) { // 支持字面量和對象形式的Boolean數(shù)據(jù) return obj === true || obj === false || toString.call(obj) == "[object Boolean]"; };
這個函數(shù)用于檢查數(shù)據(jù)是否是Boolean類型
_.isNull_.isNull = function(obj) { return obj === null; };
這個函數(shù)用于檢查數(shù)據(jù)是否是Null值
_.isUndefined_.isUndefined = function(obj) { return obj === void 0; };
這個函數(shù)用于檢查數(shù)據(jù)是否是Undefined值
_.has_.has = function(obj, key) { return hasOwnProperty.call(obj, key); };
這個函數(shù)檢查一個屬性是否屬于對象本身, 而非原型鏈中
_.noConflict_.noConflict = function() { // previousUnderscore變量記錄了Underscore定義前_(下劃線)的值 root._ = previousUnderscore; return this; };
這個函數(shù)一般用于避免命名沖突或規(guī)范命名方式,放棄_(下劃線)命名的Underscore對象, 并返回Underscore對象
_.identity_.identity = function(value) { return value; };
這個函數(shù)返回與參數(shù)相同的值, 一般用于將一個數(shù)據(jù)的獲取方式轉換為函數(shù)獲取方式(內(nèi)部用于構建方法時作為默認處理器函數(shù))
_.times_.times = function(n, iterator, context) { for(var i = 0; i < n; i++) iterator.call(context, i); };
這個函數(shù)的作用是使指定的函數(shù)迭代執(zhí)行n次(無參數(shù))
_.escape_.escape = function(string) { return ("" + string).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/"/g, "'").replace(///g, "/"); };
這個函數(shù)用于將HTML字符串中的特殊字符轉換為HTML實體, 包含 & < > " "
_.result_.result = function(object, property) { if(object == null) return null; // 獲取對象的值 var value = object[property]; // 如果值是一個函數(shù), 則執(zhí)行并返回, 否則將直接返回 return _.isFunction(value) ? value.call(object) : value; };
這個函數(shù)指定一個對象的屬性, 返回該屬性對應的值, 如果該屬性對應的是一個函數(shù), 則會執(zhí)行該函數(shù)并返回結果
_.mixin_.mixin = function(obj) { // obj是一個集合一系列自定義方法的對象, 此處通過each遍歷對象的方法 each(_.functions(obj), function(name) { // 通過addToWrapper函數(shù)將自定義方法添加到Underscore構建的對象中, 用于支持對象式調用 // 同時將方法添加到 _ 本身, 用于支持函數(shù)式調用 addToWrapper(name, _[name] = obj[name]); }); };
這個函數(shù)添加一系列自定義方法到Underscore對象中, 用于擴展Underscore插件
_.template_.template = function(text, data, settings) { // 模板配置, 如果沒有指定配置項, 則使用templateSettings中指定的配置項 settings = _.defaults(settings || {}, _.templateSettings); // 開始將模板解析為可執(zhí)行源碼 var source = "__p+="" + text.replace(escaper, function(match) { // 將特殊符號轉移為字符串形式 return "" + escapes[match]; }).replace(settings.escape || noMatch, function(match, code) { // 解析escape形式標簽 <%- %>, 將變量中包含的HTML通過_.escape函數(shù)轉換為HTML實體 return ""+ _.escape(" + unescape(code) + ")+ ""; }).replace(settings.interpolate || noMatch, function(match, code) { // 解析interpolate形式標簽 <%= %>, 將模板內(nèi)容作為一個變量與其它字符串連接起來, 則會作為一個變量輸出 return ""+ (" + unescape(code) + ")+ ""; }).replace(settings.evaluate || noMatch, function(match, code) { // 解析evaluate形式標簽 <% %>, evaluate標簽中存儲了需要執(zhí)行的JavaScript代碼, 這里結束當前的字符串拼接, 并在新的一行作為JavaScript語法執(zhí)行, 并將后面的內(nèi)容再次作為字符串的開始, 因此evaluate標簽內(nèi)的JavaScript代碼就能被正常執(zhí)行 return ""; " + unescape(code) + " ;__p+=""; }) + ""; "; if(!settings.variable) source = "with(obj||{}){ " + source + "} "; source = "var __p="";" + "var print=function(){__p+=Array.prototype.join.call(arguments, "")}; " + source + "return __p; "; // 創(chuàng)建一個函數(shù), 將源碼作為函數(shù)執(zhí)行體, 將obj和Underscore作為參數(shù)傳遞給該函數(shù) var render = new Function(settings.variable || "obj", "_", source); // 如果指定了模板的填充數(shù)據(jù), 則替換模板內(nèi)容, 并返回替換后的結果 if(data) return render(data, _); // 如果沒有指定填充數(shù)據(jù), 則返回一個函數(shù), 該函數(shù)用于將接收到的數(shù)據(jù)替換到模板 // 如果在程序中會多次填充相同模板, 那么在第一次調用時建議不指定填充數(shù)據(jù), 在獲得處理函數(shù)的引用后, 再直接調用會提高運行效率 var template = function(data) { return render.call(this, data, _); }; // 將創(chuàng)建的源碼字符串添加到函數(shù)對象中, 一般用于調試和測試 template.source = "function(" + (settings.variable || "obj") + "){ " + source + "}"; // 沒有指定填充數(shù)據(jù)的情況下, 返回處理函數(shù)句柄 return template; };
這個我要介紹的最后一個函數(shù),也是我個人認為比較重要的,它是Underscore模板解析方法, 用于將數(shù)據(jù)填充到一個模板字符串中,在模板體內(nèi), 可通過argments獲取2個參數(shù), 分別為填充數(shù)據(jù)(名稱為obj)和Underscore對象(名稱為_)
小結今天一口氣把剩下的所有函數(shù)都介紹完了,真是累感不愛啊,不過在寫作這幾篇博客的過程中,我也從Underscore這個框架中學到了很多東西,包括它的優(yōu)雅的代碼風格(至少比我自己寫的優(yōu)雅),還有一個優(yōu)秀的庫整個的架構是怎么搭建起來的。
以后我還會繼續(xù)為大家分享其他的前端知識和學習心得,thx for reading, hope u enjoy
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/87556.html
摘要:本文同步自我得博客最近準備折騰一下,在事先了解了之后,我知道了對這個庫有著強依賴,正好之前也沒使用過,于是我就想先把徹底了解一下,這樣之后折騰的時候也少一點阻礙。 本文同步自我得博客:http://www.joeray61.com 最近準備折騰一下backbone.js,在事先了解了backbone之后,我知道了backbone對underscore這個庫有著強依賴,正好undersc...
摘要:本文同步自我得博客最近十幾天都在忙畢業(yè)論文的事,所以上一次為大家介紹完這個框架的結構或者說是這個框架的設計思路之后就一直沒動靜了,今天我又滿血復活了,讓我們繼續(xù)來探索的源碼奧秘吧。 本文同步自我得博客:http://www.joeray61.com 最近十幾天都在忙畢業(yè)論文的事,所以上一次為大家介紹完underscore這個框架的結構(或者說是這個框架的設計思路)之后就一直沒動靜了,今...
摘要:總想找個機會夯實一下自己的基礎,正好最近略有清閑,看視頻讀書擼代碼我選擇了第三者怎么感覺有點別扭,看視頻的話效率不高適合入門,看書的話一本你不知道的推薦給大家,選擇繼續(xù)看書的話還是算了吧,畢竟讀萬卷書不如行萬里路是吧。 總想找個機會夯實一下自己的JS基礎,正好最近略有清閑,看視頻?讀書?擼代碼?我選擇了第三者(怎么感覺有點別扭),看視頻的話效率不高適合入門,看書的話,一本《你不知道的J...
摘要:所以,剛開始,我從源碼比較短的包含注釋只有行開始學習起。一般,在客戶端瀏覽器環(huán)境中,即為,暴露在全局中。學習以后判斷直接使用看起來也優(yōu)雅一點滑稽臉。在的函數(shù)視線中,的作用執(zhí)行一個傳入函數(shù)次,并返回由每次執(zhí)行結果組成的數(shù)組。 前言 最近在社區(qū)瀏覽文章的時候,看到了一位大四學長在尋求前端工作中的面經(jīng),看完不得不佩服,掌握知識點真是全面,無論是前端后臺還是其他,都有涉獵。 在他寫的文章中,有...
摘要:創(chuàng)建一個全局對象在瀏覽器中表示為對象在中表示對象保存下劃線變量被覆蓋之前的值如果出現(xiàn)命名沖突或考慮到規(guī)范可通過方法恢復被占用之前的值并返回對象以便重新命名創(chuàng)建一個空的對象常量便于內(nèi)部共享使用將內(nèi)置對象的原型鏈緩存在局部變量方便快速調用將 // Underscore.js 1.3.3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc....
閱讀 2984·2021-11-25 09:43
閱讀 3604·2021-11-24 11:13
閱讀 3376·2021-10-14 09:42
閱讀 2583·2021-09-23 11:53
閱讀 3625·2021-09-22 15:57
閱讀 3240·2021-09-02 09:54
閱讀 3513·2019-08-30 13:47
閱讀 1652·2019-08-29 16:55