摘要:用來構成和兩個函數,主要針對的是為了將函數調用模式更改為構造器調用和方法調用。通過函數設定時間為毫秒后執行函數的回調函數,用以達到在規定時間毫秒時執行函數的目的,并且規定時間內只執行一次函數。
北京的雨已經斷斷續續下了好久,昏昏欲睡的躲在家里不愿意出門,火影忍者快要結束了,一拳超人第二季據說還要等好多年,勇者大冒險貌似斷更了,我又是在不喜歡海賊王的畫風,所以,我該看什么好呢。
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; };
executeBound 用來構成 _.bind 和 _.partial 兩個函數,主要針對的是為了將函數調用模式更改為構造器調用和方法調用。
_.bind = restArgs(function(func, context, args) { if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function"); var bound = restArgs(function(callArgs) { return executeBound(func, bound, context, this, args.concat(callArgs)); }); return bound; });
也許我們可以參考下 Function.prototype.bind(),_.bind 函數這個需要仔細講一下了,先化簡:
_.bind = function(func, context, args) { var length = arguments.length - 2; args = Array(length); for (var index = 0; index < length; index++) { args[index] = arguments[index + startIndex]; } if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function"); var bound = function(args_2){ args_2 = Array(arguments.length); for (var index = 0; index < arguments.length; index++) { args_2[index] = arguments[index]; } (function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; })(func, bound, context, this, args.concat(args_2)); }; return bound; };
這樣看上去是不是直白很多,官網給它的定義是:綁定函數 function 到對象 object 上, 也就是無論何時調用函數, 函數里的 this 都指向這個 object.任意可選參數 arguments 可以傳遞給函數 function , 可以填充函數所需要的參數,這也被稱為 partial application。對于沒有結合上下文的partial application綁定,請使用partial。,怎么聽怎么別扭,我們可以這樣理解:_.bind 函數是為其傳參中的 function 的 this 上綁定相應對象屬性,并且同時進行 function 的參數傳入,而其中最關鍵的就是在執行這一系列動作的同時將傳入參數 context 綁定到了指向它的 Function 對象本身的 this 身上(可參考函數調用模式與方法調用模式的區別)。官網有個栗子:
var func = function(greeting){ return greeting + ": " + this.name }; func = _.bind(func, {name: "moe"}, "hi"); func(); {"hi: moe"}
實際上呢它等同于:
var func = _.bind(function(greeting){ return greeting + ": " + this.name; }, {name: "moe"}, "hi" ); func(); {"hi: moe"}
結合前面簡化的 _.bind 代碼示例可知這個函數的核心思想就是先通過 _.bind 初始化的時候優化第3+個參數 args,為什么叫 3+ 呢,因為從第三個參數開始,可能是不限定的參數數量,所以從第三個開始到最后一個參數同一處理為一個數組 args。
緊接著就是執行剛才初始化過后的函數了,當 func(); 的時候也就是開始執行 _.bind 中的 bound 函數。bound 允許傳遞參數并且其參數會被 push 到 args 中,具體實現參看上面的簡化代碼 args.concat(args_2)。這里我們有幾個需要注意的點,其一是 callingContext instanceof boundFunc,之前我們講過 instanceof 的神奇用法,在這里它用與判斷 bound 中的 this 的指向是否繼承于 bound。我們一定知道 this 指向的四個情況,如下:
var obj = {}; var func = function (){console.log(this);}; func(); new func(); obj.func = func; obj.func(); func.apply(["this is parameter"]); func.call(["this is parameter"]);
輸出結果為:
Window {external: Object, chrome: Object, document: document, alogObjectConfig: Object, alogObjectName: "alog"…} func {} Object {} ["this is parameter"] ["this is parameter"]
分別代表四種情況:
函數調用模式:指向 Global,瀏覽器客戶端即 window;
方法調用模式:指向對象本身;
構造器調用模式:指向為新構造的對象,繼承自原 Function 對象;
apply 或 call 調用模式:指向傳入的參數。
這里還有一些非常好的資料:this、Understanding JavaScript Function Invocation and "this",在這里我要說一下我在推庫上看到一篇關于 this 的介紹文章說:“比較系統的分類是《JavaScript語言精粹》中的,分為函數調用模式(this綁定全局對象window)和方法調用模式(this綁定調用方法的主體)”,我把《JavaScript語言精粹》這本書從頭到尾翻看了好幾遍,實際上它原文是這樣說的:“在 JAVASCRIPT 中一共有4種調用模式:方法調用模式、函數調用模式、構造器調用模式和 apply 調用模式。”,具體敘述在原書的P27~P30頁,感興趣的朋友可以看下,在給大家看一個彩蛋,嚴格模式下的 this。緊接上文,當 bound 中的 this 的指向是否繼承于 bound 函數的時候說明是使用了 new 關鍵字的構造器調用模式調用了 _.bind 函數,則繼續執行 executeBound 函數中的 baseCreate 創建基本函數然后進行一系列的操作,其實說到底 baseCreate 的目的就是為了保證傳入參數 Function 的 this 的干凈。
另外一個需要注意的地方是官網示例的暗示(特蛋疼的暗示),我擴展了一下:
var func = function(){ return JSON.stringify(arguments) + ": " + this.name }; func = _.bind(func, {name: "moe"}, "hi"); func(); func = _.bind(func, {name: "moe2"}, "hi2"); func();
輸出結果:
"{"0":"hi"}: moe" "{"0":"hi","1":"hi2"}: moe"
可能有些不明就里的同學會問這是為什么啊,怎么 this.name 的值沒有變化呢。實際上我們第一個 _.bind 是正常的函數綁定,而第二個 func = _.bind(func, {name: "moe2"}, "hi2"); 是將上一個 _.bind 作為了 Function 參數傳入到了新的 _.bind 中,而本來的函數 func 作為第一個 _.bind 的 func 參數一直傳遞到第二個 _.bind 中,但是中間的 this.name 卻被綁定到了第一個 _.bind 上面而不是第一個 _.bind 中的 func 上。有一點繞口。用個代碼介紹下,第二個 _.bind 的情況是這樣子的:
func = _.bind(function( function(greeting){ return greeting + ": " + this.name; }, context, args ) { var length = arguments.length - 2; args = Array(length); for (var index = 0; index < length; index++) { args[index] = arguments[index + startIndex]; } if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function"); var bound = function(args_2){ args_2 = Array(arguments.length); for (var index = 0; index < arguments.length; index++) { args_2[index] = arguments[index]; } (function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; })(func, bound, context, this, args.concat(args_2)); }; return bound; }, {name: "moe2"}, "hi2" );
所以 _.bind 一定要遵循正確的用法,不然真的出錯了可能調試都不好發現問題,多層回調嵌套的時候一層套一層,很麻煩。
_.partial = restArgs(function(func, boundArgs) { var placeholder = _.partial.placeholder; var bound = function() { var position = 0, length = boundArgs.length; var args = Array(length); for (var i = 0; i < length; i++) { args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); return executeBound(func, bound, this, this, args); }; return bound; });
_.partial 函數的核心思想與 _.bind 相同,都是為了解決 this 指向的問題,區別在于 _.partial 不需要對 this 上的值做什么處理。用法上我覺得 _.partial 看上去更怪異一些,也許用來做一些特定的計算可能更合適些。
_.partial.placeholder = _;
設置 _.partial.placeholder 為 _。
_.bindAll = restArgs(function(obj, keys) { keys = flatten(keys, false, false); var index = keys.length; if (index < 1) throw new Error("bindAll must be passed function names"); while (index--) { var key = keys[index]; obj[key] = _.bind(obj[key], obj); } });
這里我們看到 _.bindAll 函數官網的示例就有點糊涂了:
var buttonView = { label : "underscore", onClick: function(){ console.log("clicked: " + this.label); }, onHover: function(){ console.log("hovering: " + this.label); } }; _.bindAll(buttonView, "onClick", "onHover"); buttonView.onClick(); clicked: underscore
我們當然知道結果是 clicked: underscore,那么執行 _.bindAll(buttonView, "onClick", "onHover"); 的意義在哪呢,所以說這又是官網坑人的地方了,_.bindAll 的本意是將其傳入的第二個及以后的參數放到一個共同的上下文環境里面執行,從而達到 this 指向其第一個參數的本身的目的,而官網的示例為方法調用模式,this 指向已經是 Object 本身了所以看不到變化,但是我們在瀏覽器控制臺查看的話應該能知道 this 上多了 [[TargetFunction]]: function ()、[[BoundThis]]: Object、[[BoundArgs]]: Array[0] 三個參數并且 [[BoundThis]] 恰好是 Object。閑來無事這好看到有人也寫了這個問題并舉證了一個示例,詳見 Understanding bind and bindAll in Backbone.js。我 cope 一下:
function Developer(skill) { this.skill = skill; this.says = function(){ console.log(this.skill + " rocks!"); } } var john = new Developer("Ruby"); _.bindAll(john, "says"); var func = john.says; func(); //Ruby rocks!
這個函數調用模式的示例正好答疑了 this 指向已經被改變的這個問題。
_.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; var address = "" + (hasher ? hasher.apply(this, arguments) : key); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; return memoize; };
_.memoize 函數更像是一個可以緩存第一次執行結果的遞歸函數,我們從源碼中可以看到 memoize.cache = {}; 就是用來存儲計算結果的容器,這里面比較有意思的是 hasher 這個參數,官網釋義: hashFunction,實際上就是通過 hashFunction 對傳入的 key 值進行處理然后放到 memoize.cache = {}; 中,至于怎么處理 hash 也好、md5 也好、或者什么其他的計算加密真值判斷增加對象等等都可以通過 hasher 這個傳入的回調進行擴展。
————————— 疲憊的分割線 ———————————
這幾天北京總在下雨,身體特別的疲憊,狀態也不怎么好,所以今天才開始繼續更新。
————————— END ———————————
_.delay = restArgs(function(func, wait, args) { return setTimeout(function() { return func.apply(null, args); }, wait); });
_.delay 函數用于處理定時器相關函數,原理是通過 setTimeout 進行二次封裝,比較關鍵的就是 args 參數通過 restArgs 函數處理為一個數組,方便了下一步的 func.apply(null, args); 傳值。
_.defer = _.partial(_.delay, _, 1);
_.defer 這個函數我們首先可以看到內部應用了 _.partial 并且中間傳入參數 _,這意味著當 _.defer 執行的時候傳入的參數會被補全到 _.partial 內部 bound 中的 args[0] 位置,而此時 args 的值為 [func, 1]并將它傳給 _.delay 函數,即 _.delay.apply(null, args);,用著這種方式曲線的設置 setTimeout 函數的 wait = 1,目的就是處理代碼復用問題,不然的話完全可以改裝一下 _.delay 函數可以更簡單的實現這一功能。
_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
_.throttle 函數可以限制和控制其參數 func 的執行次數和執行時間,思想就是通過 wait、now、previous 和 remaining 進行判斷然后分別執行相應的策略。
wait:使用 _.throttle 函數時傳入的時間標識,在每個 wait 毫秒時間段內最多且一定調用一次該函數。
now:使用 _.now() 函數獲取當前時間戳。
previous:用來緩存函數執行時的時間戳,用于后面與下一次執行時的時間戳進行相關判斷。
remaining:緩存 wait - (now - previous) 的差值。
我們在看官網介紹可以知道 _.throttle 傳遞的 options 分四種情況(默認是 {leading:false,trailing:false}):
{leading:true,trailing:true}:從實例化 _.throttle 的時間開始到執行實例化的函數的時間為止,中間的差值定義為 now - previous,進而得出設定的時間 wait 與 now - previous 的差值 remaining,從而決定怎么執行函數。參考 世紀之光 的很有趣的說法,就是第一次可以立即執行,第二次開始將在每 wait 時間內只允許執行一次,為什么會第一次立即執行呢,因為大家設置的 wait 一般都不會太大,所以頁面加載過程中一般已經執行了 _.throttle 的實例化,也就是說其 remaining <= 0,而后面如果一直執行函數,那么就開始 0 < remaining <= wait 模式了,
{leading:false,trailing:false}:這種情況下比較有意思的是 previous 這個參數,在實例化 _.throttle 的時候,previous = 0,利用了 !0 === true 的特性使 _.throttle 內部并沒有執行回調函數 func,所以第一次函數調用失敗,在第二次開始 previous = now (now 為第一次調用的時間戳),所以它也分為兩種情況:
{leading:true,trailing:false}:這種情況下是沒有 setTimeout 函數的,因為 leading:true,所以 previous 初始化為 0,意味著第一次執行函數會立即執行,兒后面就要遵循 remaining <= 0 || remaining > wait 才能執行,也就是說只有第一執行完畢后的時間超過了 wait 才能繼續調用函數才能執行(調用是重點),以此類推。
{leading:false,trailing:true}:這種情況由于 leading:false,所以每次 previous 都等于當前調用函數時的時間戳,所以完美的不存在 remaining <= 0 || remaining > wait 的情況,由此只能通過 setTimeout 執行回調,所以遵循通過 setTimeout 函數設定時間為 remaining 毫秒后執行 _.throttle 函數的回調函數 func,用以達到在規定時間 wait 毫秒時執行函數的目的,并且規定 wait 時間內只執行一次函數。
其實總結一下就是大概一下兩種都存在或者只存在其一的情況:
remaining <= 0:立即執行 _.throttle 函數的回調函數 func。
0 < remaining <= wait:通過 setTimeout 函數設定時間為 remaining 毫秒后執行 _.throttle 函數的回調函數 func,用以達到在規定時間 wait 毫秒時執行函數的目的,并且規定 wait 時間內只執行一次函數。
_.debounce = function(func, wait, immediate) { var timeout, result; var later = function(context, args) { timeout = null; if (args) result = func.apply(context, args); }; var debounced = restArgs(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { timeout = _.delay(later, wait, this, args); } return result; }); debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; };
_.debounce 更像是 _.delay 的方言版,當 immediate = true 的時候通過 var callNow = !timeout = false 達到立即執行回調函數 func 的目的,并用 later 函數限制 規定 wait 時間內不允許在調用函數(later 函數內部 context = args = underfind,其實我們知道 var later = function(context, args) 這個條件是為 _.delay(later, wait, this, args) 準備的)。
_.wrap = function(func, wrapper) { return _.partial(wrapper, func); };
_.wrap 的兩個參數理論上都要求是 Function,我們已經知道 _.partial 是用來在 this 上下功夫的,雖然這里和 this 也沒什么太大關系,之所以這里應用了 _.partial 是為了讓 func 作為 wrapper 的第一個參數執行,并且通過 executeBound 函數對函數調用模式和方法調用模式做處理。
_.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; };
_.negate 用來做真值判斷。
_.compose = function() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; };
_.compose 用于將函數執行結果進行傳遞,需要注意的是 var args = arguments; 中的 arguments 和 args[start].apply(this, arguments); 中的 arguments 并不相同就可以了。這個涉及到函數的執行,當每一個函數執行的時候都會形成一個內部的上下文執行環境(傳說叫 ExecutionContext,這個我還沒有考證過),在構建環境的同時生成 arguments 變量和作用域鏈表等等,這里不像敘述了。
_.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; };
_.after 接受兩個參數,Number 參數用來限定 _.after 實例化函數的執行次數,說白了就是只有當第 Number 次執行實例化函數的時候才會繼續執行 func 回調,這個用來處理遍歷 _.each 時某些情況很有用。
_.before = function(times, func) { var memo; return function() { if (--times > 0) { memo = func.apply(this, arguments); } if (times <= 1) func = null; return memo; }; };
_.before,與 _.after 相反,只在規定 Number 參數的次數內以此執行 _.before,超過之后結束。
_.once = _.partial(_.before, 2);
_.once 創建一個只能調用一次的函數。到這里關于函數相關的源碼就結束了,說心里話很多地方看得懂不一定說的懂,說的懂也不一定用的懂,就拿這個 _.once 來講,它只用了 _.partial 和 _.before 來做文章,用 _.before 限定只能執行一次還好理解,那么為什么一定要用 _.partial 坐下處理呢,其目的真的只是為了讓 2 作為 _.before 的第一個參數進行傳遞過去并將 _.once 的傳參作為 arguments[1+] 傳入么,更深一層考慮,_.partial 函數是不是有處理過 _.once 傳遞過來的函數的作用域鏈和 this 相關的情況呢。
_.restArgs = restArgs;
_.restArgs 將 restArgs 函數綁定到 _ 對象上。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79653.html
摘要:組件的選擇命令行工具首先我們需要一個命令行工具來方便的執行命令,這里我們選擇組件,如果不喜歡使用且有能力的人完全可以通過組件自己封裝執行命令函數。 對于一個成熟的項目而言,一定需要一個注釋文檔生成工具,我們有很多可選的開源項目,如jsdoc、yuidocjs 等等,擁有這些強大的工具我們完全可以勝任任何注釋方面的管理了么? 一個成熟的開發者都會知道不管怎么樣的項目都會在不同的開發條件下...
摘要:新出臺的則規定,包括六種原始類型和,還有一種,詳見數據類型和數據結構。用于返回一個由給定對象的所有可枚舉自身屬性的屬性名組成的數組,。接下來判斷數字進行相應的操作,其中有和兩個方法,詳見和。 一直想寫一篇這樣的文章,于是心動不如行動,這里選擇的是 Underscore.js 1.8.3 版本,源碼注釋加在一起1625行。 Underscore.js 1.8.3 http://unde...
摘要:第四個判斷如果是對象執行返回一個斷言函數,用來判定傳入對象是否匹配指定鍵值屬性。都不匹配最后執行,返回傳入的對象的屬性。設置的值并生成函數,等同于,使具有屬性且有值則返回,否則返回,這是一個判斷函數。 在第二小章節里面我按照源碼順序介紹幾個方法,源碼緊接著第一章繼續: var builtinIteratee; builtinIteratee,內置的 Iteratee (迭代器)。...
摘要:接收三個參數分別為回調和,其中與是可選參數。官網釋義排序一個列表組成一個組,并且返回各組中的對象的數量的計數。類似,但是不是返回列表的值,而是返回在該組中值的數目。 繼續前面的內容,前文我們提到了很多方法的講解,其實到這里就已經差不多了,因為大部分代碼其實都是套路,一些基礎函數再靈活變化就可以組成很多實用的功能。 _.sortBy = function(obj, iteratee,...
摘要:傳入值進行判斷以此決定函數,將三個參數包括回調傳入中其中回調函數充當迭代器進行真值檢測,最后。是從一個中隨機返回值,并且返回值受限于這個參數,如果沒有傳入或者傳入了則執行語句,目的是將判斷處理之后返回單一值。 今天繼續上次的內容,之前我們講到了 reduce 的用法,其實我覺得用法倒是其次的關鍵是作者實現 reduce 過程中所靈活用到的函數處理方法,我們只要有心稍加總覺完全可以拿來主...
閱讀 1048·2021-11-22 15:33
閱讀 3357·2021-11-08 13:20
閱讀 1368·2021-09-22 10:55
閱讀 2053·2019-08-29 11:08
閱讀 771·2019-08-26 12:24
閱讀 3068·2019-08-23 17:15
閱讀 2225·2019-08-23 16:12
閱讀 1933·2019-08-23 16:09