摘要:值得注意的是,如果值在前面也就是值小于值,那么值域會被認為是零長度,而不是負增長。
underscore.js源碼加注釋一共1500多行,它提供了一整套函數(shù)式編程實用的功能,一共一百多個函數(shù),幾乎每一個函數(shù)都可以作為參考典范。初讀的時候,真是一臉懵圈,各種函數(shù)閉包、迭代和嵌套的使用,讓我一時很難消化。
在這里,我來記錄一下我學習underscore.js的一些發(fā)現(xiàn),以及幾個我認為比較經(jīng)典的函數(shù)使用。
首先我們可以看到,underscore.js中所有的函數(shù)和方法都在一個閉包里:(function() {...}.call(this));這么做的目的是為了避免污染全局變量。
為了壓縮代碼,underscore中用到將原型賦值給變量保存的方法:
// Save bytes in the minified (but not gzipped) version: // 原型賦值,便于壓縮代碼,這里的壓縮指壓縮到min.js而不是gzip壓縮 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. // 將內(nèi)置對象原型中的常用方法賦值給引用變量,減少在原型鏈中的查找次數(shù),從而提高代碼效率 var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty;
我們在處理代碼時,不能直接將Array.prototype等直接壓縮,因為壓縮過后,瀏覽器是無法識別這些壓縮字段的。壓縮過后,我們在使用obj.prototype方法時,直接使用其相對應的變量就可以了。如果在我們的代碼中會多次用到某個方法,用上面的方法就進行處理,使用起來就方便多了。
接著創(chuàng)建了一個"_"對象,之后將underscore中的相關(guān)方法添加到"_"原型中,那么創(chuàng)建的"_"對象也就具備了underscore方法。
// 創(chuàng)建一個"_"對象 var _ = function(obj) { if (obj instanceof _) return obj; //如果obj是"—"的實例,則直接返回obj if (!(this instanceof _)) return new _(obj); //如果不是,則調(diào)用new運算符,返回實例化的對象 this._wrapped = obj; //將underscore對象存放在_.wrapped屬性中 };
上面用到了instanceof運算符,JavaScript中instanceof運算符是返回一個 Boolean 值,指出對象是否是特定類的一個實例。
使用方法:result = object instanceof class
其中,result是必選項,表任意變量;object是必選項,表任意對象表達式;class是必選項,表任意已定義的對象類。如果 object 是 class 的一個實例,則 instanceof 運算符返回 true。如果 object 不是指定類的一個實例,或者 object 是 null,則返回 false。
接下來我就列舉幾個underscore中的函數(shù)。
1、_.each
_.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); // 根據(jù) context 確定不同的迭代函數(shù) var i, length; if (isArrayLike(obj)) { // 如果是類數(shù)組 (默認不會傳入類似 {length: 10} 這樣的數(shù)據(jù)) for (i = 0, length = obj.length; i < length; i++) { //遍歷 iteratee(obj[i], i, obj); } } else { // 如果 obj 是對象 var keys = _.keys(obj); // 獲取對象的所有 key 值 for (i = 0, length = keys.length; i < length; i++) { //如果是對象,則遍歷處理 values 值 iteratee(obj[keys[i]], keys[i], obj); } } return obj; //返回 obj 參數(shù),供鏈式調(diào)用(Returns the list for chaining) }; _.each = _.forEach = function(obj, iteratee, context)中,一共有三個參數(shù): 第一個參數(shù)為數(shù)組(包括類數(shù)組)或者對象;第二個參數(shù)為迭代方法,對數(shù)組或者對象每個元素都執(zhí)行 該方法,該方法又能傳入三個參數(shù),分別為 (item, index, array)((value, key, obj) for object); 第三個參數(shù)(可省略)確定第二個參數(shù) iteratee 函數(shù)中的(可能有的)this 指向, 即 iteratee 中出現(xiàn)的(如果有)所有 this 都指向 context。
2、_.contains
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); // // 如果是對象,返回 values 組成的數(shù)組 //fromIndex 表示查詢起始位置,如果沒有指定該參數(shù),則默認從頭找起 if (typeof fromIndex != "number" || guard) fromIndex = 0; //_.indexOf 是數(shù)組的擴展方法(Array Functions) //數(shù)組中尋找某一元素 return _.indexOf(obj, item, fromIndex) >= 0; };
判斷數(shù)組或者對象中(value 值)是否有指定元素,如果是 object,則忽略 key 值,只需要查找 value 值即可,如果obj 中是否有指定的 value 值,則返回布爾值。
3、 _.uniq
_.uniq = _.unique = function(array, isSorted, iteratee, context) { if (!_.isBoolean(isSorted)) { // 沒有傳入 isSorted 參數(shù) context = iteratee; iteratee = isSorted; isSorted = false; // 轉(zhuǎn)為 _.unique(array, false, undefined, iteratee) } // 如果有迭代函數(shù),則根據(jù) this 指向二次返回新的迭代函數(shù) if (iteratee != null) iteratee = cb(iteratee, context); var result = []; // 結(jié)果數(shù)組,是 array 的子集 var seen = []; //// 已經(jīng)出現(xiàn)過的元素(或者經(jīng)過迭代過的值),用來過濾重復值 for (var i = 0, length = getLength(array); i < length; i++) { var value = array[i], //如果指定了迭代函數(shù),則對數(shù)組每一個元素進行迭代 //迭代函數(shù)傳入的三個參數(shù)通常是 value, index, array 形式 computed = iteratee ? iteratee(value, i, array) : value; //如果是有序數(shù)組,則當前元素只需跟上一個元素對比即可,并用 seen 變量保存上一個元素 if (isSorted) { //如果 i === 0,是第一個元素,則直接 push,否則比較當前元素是否和前一個元素相等 if (!i || seen !== computed) result.push(value); seen = computed; // seen 保存當前元素,供下一次對比 } else if (iteratee) { if (!_.contains(seen, computed)) { //// 如果 seen[] 中沒有 computed 這個元素值 seen.push(computed); result.push(value); } } else if (!_.contains(result, value)) { // 如果不用經(jīng)過迭代函數(shù)計算,也就不用 seen[] 變量了 result.push(value); } } return result; };
這是一個數(shù)組去重函數(shù),如果函數(shù)參數(shù)中第二個參數(shù) isSorted 為 true,則說明事先已經(jīng)知道數(shù)組有序,程序會跑一個更快的算法;如果有第三個參數(shù) iteratee,則對數(shù)組每個元素迭代,對迭代之后的結(jié)果進行去重,然后返回去重后的數(shù)組(array 的子數(shù)組);另外,暴露的 API 中沒 context 參數(shù)。
4、_.range
// 返回某一個范圍內(nèi)的數(shù)組成的數(shù)組 _.range = function(start, stop, step) { if (stop == null) { stop = start || 0; start = 0; } step = step || 1; // 返回數(shù)組的長度 var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); // 返回的數(shù)組 for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; };
_.range([start], stop, [step]) 是一個用來創(chuàng)建整數(shù)靈活編號的列表的函數(shù),便于each 和 map循環(huán)。如果省略start則默認為 0;step 默認為 1.返回一個從start 到stop的整數(shù)的列表,用step來增加 (或減少)獨占。值得注意的是,如果stop值在start前面(也就是stop值小于start值),那么值域會被認為是零長度,而不是負增長。-如果要一個負數(shù)的值域 ,則使用負數(shù)step. (參考http://www.css88.com/doc/unde...)
5、_.bind
_.bind = function(func, context) { if (nativeBind && func.bind === nativeBind) // 如果瀏覽器支持 ES5 bind 方法,并且 func 上的 bind 方法沒有被重寫,則優(yōu)先使用原生的 bind 方法 return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) //如果傳入的參數(shù) func 不是方法,則拋出錯誤 throw new TypeError("Bind must be called on a function"); //經(jīng)典閉包,函數(shù)返回函數(shù) var args = slice.call(arguments, 2); // args 獲取優(yōu)先使用的參數(shù) var bound = function() { //最終函數(shù)的實際調(diào)用參數(shù)由兩部分組成 //一部分是傳入 _.bind 的參數(shù)(會被優(yōu)先調(diào)用),另一部分是傳入 bound(_.bind 所返回方法)的參數(shù) return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; };
該方法是ES5 bind 方法的擴展, 將 func 中的 this 指向 context(對象),用法為_.bind(function, object, *arguments),其中arguments 參數(shù)可選,它會被當作 func 的參數(shù)傳入,func 在調(diào)用時,會優(yōu)先用 arguments 參數(shù),然后使用 _.bind 返回方法所傳入的參數(shù)。
6、 _.memoize
_.memoize = function(func, hasher) { var memoize = function(key) { // 儲存變量,方便使用 var cache = memoize.cache; //求 key //如果傳入了 hasher,則用 hasher 函數(shù)來計算 key,否則用 參數(shù) key(即 memoize 方法傳入的第一個參數(shù))當 key var address = "" + (hasher ? hasher.apply(this, arguments) : key); //如果這個 key 還沒被 hash 過(還沒求過值) if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; // cache 對象被當做 key-value 鍵值對緩存中間運算結(jié)果 return memoize; // 返回一個函數(shù)(經(jīng)典閉包) };
Memoizes方法可以緩存某函數(shù)的計算結(jié)果,用法:_.memoize(function, [hashFunction])
如果傳遞了 hashFunction 參數(shù),就用 hashFunction 的返回值作為key存儲函數(shù)的計算結(jié)果。hashFunction 默認使用function的第一個參數(shù)作為key。memoized值的緩存可作為返回函數(shù)的cache屬性。
7、_.throttle
_.throttle = function(func, wait, options) { var context, args, result; var timeout = null; //標記時間戳,上一次執(zhí)行回調(diào)的時間戳 var previous = 0; if (!options) //如果沒有傳入 options 參數(shù) options = {}; // 則將 options 參數(shù)置為空對象 var later = function() { //如果 options.leading === false,則每次觸發(fā)回調(diào)后將 previous 置為 0,否則置為當前時間戳 previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; // _.throttle 方法返回的函數(shù) return function() { var now = _.now(); // 記錄當前時間戳 //第一次執(zhí)行回調(diào)(此時 previous 為 0,之后 previous 值為上一次時間戳) //并且如果程序設定第一個回調(diào)不是立即執(zhí)行的(options.leading === false),則將 previous 值(表示上次執(zhí)行的時間戳)設為 now 的時間戳(第一次觸發(fā)時),表示剛執(zhí)行過,這次就不用執(zhí)行了 if (!previous && options.leading === false) previous = now; // 距離下次觸發(fā) func 還需要等待的時間 var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); // 解除引用,防止內(nèi)存泄露 timeout = null; } // 重置前一次觸發(fā)的時間戳 previous = now; // 觸發(fā)方法,result 為該方法返回值 result = func.apply(context, args); if (!timeout) context = args = null; //引用置為空,防止內(nèi)存泄露 } else if (!timeout && options.trailing !== false) {// 最后一次需要觸發(fā)的情況 //如果已經(jīng)存在一個定時器,則不會進入該 if 分支 // 如果 {trailing: false},即最后一次不需要觸發(fā)了,也不會進入這個分支 timeout = setTimeout(later, remaining); // 間隔 remaining milliseconds 后觸發(fā) later 方法 } return result; // 回調(diào)返回值 }; };
函數(shù)節(jié)流(如果有連續(xù)事件響應,則每間隔一定時間段觸發(fā)),每間隔 wait(Number) milliseconds 觸發(fā)一次 func 方法,如果 options 參數(shù)傳入 {leading: false},不會馬上觸發(fā)(等待 wait milliseconds 后第一次觸發(fā) func),如果 options 參數(shù)傳入 {trailing: false},那么最后一次回調(diào)不會被觸發(fā)。options 不能同時設置 leading 和 trailing 為 false。
8、_.debounce
_.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { //定時器設置的回調(diào) later 方法的觸發(fā)時間,和連續(xù)事件觸發(fā)的最后一次時間戳的間隔 var last = _.now() - timestamp; //如果間隔為 wait(或者剛好大于 wait),則觸發(fā)事件 if (last < wait && last >= 0) { //時間間隔 last 在 [0, wait) 中,還沒到觸發(fā)的點,則繼續(xù)設置定時器 timeout = setTimeout(later, wait - last); } else { //到了可以觸發(fā)的時間點 timeout = null; // 如果不是立即執(zhí)行,隨即執(zhí)行 func 方法 if (!immediate) { result = func.apply(context, args); // 執(zhí)行 func 函數(shù) if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; //每次觸發(fā)函數(shù),更新時間戳 timestamp = _.now(); // 立即觸發(fā)需要滿足兩個條件 // immediate 參數(shù)為 true,并且 timeout 還沒設置 var callNow = immediate && !timeout; // 設置 wait seconds 后觸發(fā) later 方法 // 在某一段的連續(xù)觸發(fā)中,只會在第一次觸發(fā)時進入這個 if 分支中 if (!timeout) // 設置了 timeout,所以以后不會進入這個 if 分支了 timeout = setTimeout(later, wait); // 如果是立即觸發(fā) if (callNow) { result = func.apply(context, args); context = args = null; // 解除引用 } return result; }; };
函數(shù)去抖(連續(xù)事件觸發(fā)結(jié)束后只觸發(fā)一次),如_.debounce(function(){}, 1000)表示連續(xù)事件結(jié)束后的 1000ms 后觸發(fā)。
9、 _.pick
_.pick = function(object, oiteratee, context) { var result = {}, // result 為返回的對象副本 obj = object, iteratee, keys; if (obj == null) return result; // 如果第二個參數(shù)是函數(shù) if (_.isFunction(oiteratee)) { keys = _.allKeys(obj); iteratee = optimizeCb(oiteratee, context); } else { // 如果第二個參數(shù)不是函數(shù) // 則后面的 keys 可能是數(shù)組 // 也可能是連續(xù)的幾個并列的參數(shù) // 用 flatten 將它們展開 keys = flatten(arguments, false, false, 1); //也轉(zhuǎn)為 predicate 函數(shù)判斷形式,將指定 key 轉(zhuǎn)化為 predicate 函數(shù) iteratee = function(value, key, obj) { return key in obj; }; obj = Object(obj); } for (var i = 0, length = keys.length; i < length; i++) { var key = keys[i]; var value = obj[key]; if (iteratee(value, key, obj)) result[key] = value; } return result; }; _.pick(object, *keys)根據(jù)一定的需求(key 值,或者通過 predicate 函數(shù)返回真假),返回擁有一定鍵值對的對象副本。第二個參數(shù)可以是一個 predicate 函數(shù),也可以是0個或多個key。
10、_.noConflict
_.noConflict = function() { root._ = previousUnderscore; return this; };
如果全局環(huán)境中已經(jīng)使用了 _ 變量,可以用該方法返回其他變量,繼續(xù)使用 underscore 中的方法。
11、_.template
_.template = function(text, settings, oldSettings) { // 兼容舊版本 if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. // // 正則表達式 pattern,用于正則匹配 text 字符串中的模板字符串 var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join("|") + "|$", "g"); // Compile the template source, escaping string literals appropriately. // 編譯模板字符串,將原始的模板字符串替換成函數(shù)字符串 // 用拼接成的函數(shù)字符串生成函數(shù)(new Function(...)) var index = 0; // source 變量拼接的字符串用來生成函數(shù),用于當做 new Function 生成函數(shù)時的函數(shù)字符串變量 var source = "__p+=""; // replace 函數(shù)不需要為返回值賦值,主要是為了在函數(shù)內(nèi)對 source 變量賦值 // // 將 text 變量中的模板提取出來 text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { // escape/interpolate/evaluate 為匹配的子表達式(如果沒有匹配成功則為 undefined) // offset 為字符匹配(match)的起始位置(偏移量) source += text.slice(index, offset).replace(escaper, escapeChar); index = offset + match.length; // 改變 index 值,為了下次的 slice if (escape) { // 需要對變量進行編碼(=> HTML 實體編碼) // 避免 XSS 攻擊 source += ""+ ((__t=(" + escape + "))==null?"":_.escape(__t))+ ""; } else if (interpolate) { // 單純的插入變量 source += ""+ ((__t=(" + interpolate + "))==null?"":__t)+ ""; } else if (evaluate) { source += ""; " + evaluate + " __p+=""; } // Adobe VMs need the match returned to produce the correct offest. // return 的作用是將匹配到的內(nèi)容原樣返回(Adobe VMs 需要返回 match 來使得 offset 值正常) return match; }); source += ""; "; // If a variable is not specified, place data values in local scope. // 如果設置了 settings.variable,能顯著提升模板的渲染速度,否則,默認用 with 語句指定作用域 if (!settings.variable) source = "with(obj||{}){ " + source + "} "; // 增加 print 功能 source = "var __t,__p="",__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,"");}; " + source + "return __p; "; // __p 為返回的字符串 try { // render 方法,前兩個參數(shù)為 render 方法的參數(shù) // obj 為傳入的 JSON 對象,傳入 _ 參數(shù)使得函數(shù)內(nèi)部能用 Underscore 的函數(shù) var render = new Function(settings.variable || "obj", "_", source); } catch (e) { e.source = source; throw e; } // 返回的函數(shù) //data 一般是 JSON 數(shù)據(jù),用來渲染模板 var template = function(data) { // render 為模板渲染函數(shù) return render.call(this, data, _); // 傳入?yún)?shù) _ ,使得模板里 <% %> 里的代碼能用 underscore 的方法 }; // Provide the compiled source as a convenience for precompilation. var argument = settings.variable || "obj"; template.source = "function(" + argument + "){ " + source + "}"; return template; }; _.template(templateString, [settings]) // 將 JavaScript 模板編譯為可以用于頁面呈現(xiàn)的函數(shù),setting 參數(shù)可以用來自定義字符串模板,是一個哈希表包含任何可以覆蓋的設置。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/80858.html
摘要:它通過數(shù)據(jù)模型進行鍵值綁定及事件處理,通過模型集合器提供一套豐富的用于枚舉功能,通過視圖來進行事件處理及與現(xiàn)有的通過接口進行交互。 本人兼職前端付費技術(shù)顧問,如需幫助請加本人微信hawx1993或QQ345823102,非誠勿擾 1.為初學前端而不知道怎么做項目的你指導 2.指導并扎實你的JavaScript基礎 3.幫你準備面試并提供相關(guān)指導性意見 4.為你的前端之路提供極具建設性的...
摘要:所以經(jīng)常會在一個源碼中看到寫法吧立即執(zhí)行函數(shù)創(chuàng)建變量,保存全局根變量。 // ================立即執(zhí)行函數(shù)================ // 使用(function(){}())立即執(zhí)行函數(shù),減少全局變量 // ----????----函數(shù)聲明 function (){} 與函數(shù)表達式 var funName = function(){}----????---- /...
摘要:所以,剛開始,我從源碼比較短的包含注釋只有行開始學習起。一般,在客戶端瀏覽器環(huán)境中,即為,暴露在全局中。學習以后判斷直接使用看起來也優(yōu)雅一點滑稽臉。在的函數(shù)視線中,的作用執(zhí)行一個傳入函數(shù)次,并返回由每次執(zhí)行結(jié)果組成的數(shù)組。 前言 最近在社區(qū)瀏覽文章的時候,看到了一位大四學長在尋求前端工作中的面經(jīng),看完不得不佩服,掌握知識點真是全面,無論是前端后臺還是其他,都有涉獵。 在他寫的文章中,有...
摘要:今天要講的是,如何在數(shù)組中尋找元素,對應中的,,,以及方法。如果往一個有序數(shù)組中插入元素,使得數(shù)組繼續(xù)保持有序,那么這個插入位置是這就是這個方法的作用,有序,很顯然用二分查找即可。 Why underscore (覺得這部分眼熟的可以直接跳到下一段了...) 最近開始看 underscore.js 源碼,并將 underscore.js 源碼解讀 放在了我的 2016 計劃中。 閱讀一...
摘要:加載的模塊會以參數(shù)形式傳入該函數(shù),從而在回調(diào)函數(shù)內(nèi)部就可以使用這些模塊。異步加載,和,瀏覽器不會失去響應它指定的回調(diào)函數(shù),只有前面的模塊都加載成功后,才會運行,解決了依賴性的問題。插件,可以讓回調(diào)函數(shù)在頁面結(jié)構(gòu)加載完成后再運行。 這次主要是對《高性能JavaScript》一書的讀書筆記,記錄下自己之前沒有注意到或者需要引起重視的地方 第一章 加載和執(zhí)行 js代碼在執(zhí)行過程中會阻塞瀏覽...
閱讀 3067·2021-11-25 09:43
閱讀 1042·2021-11-24 10:22
閱讀 1372·2021-09-22 15:26
閱讀 697·2019-08-30 15:44
閱讀 2475·2019-08-29 16:33
閱讀 3715·2019-08-26 18:42
閱讀 929·2019-08-23 18:07
閱讀 1845·2019-08-23 17:55