前言
這是underscore.js源碼分析的第六篇,如果你對這個系列感興趣,歡迎點擊
underscore-analysis/ watch一下,隨時可以看到動態更新。
指定調用次數(after, before)下劃線中有非常多很有趣的方法,可以用比較巧妙的方式解決我們日常生活中遇到的問題,比如_.after,_.before,_.defer...等,也許你已經用過他們了,今天我們來深入源碼,一探究竟,他們到底是怎么實現的。
把這兩個方法放在前面也是因為他們倆能夠解決我們工作中至少以下兩個問題
如果你要等多個異步請求完成之后才去執行某個操作fn,那么你可以用_.after,而不必寫多層異步回調地獄去實現需求
有一些應用可能需要進行初始化操作而且僅需要一次初始化就可以,一般的做法是在入口處對某個變量進行判斷,如果為真那么認為已經初始化過了直接return掉,如果為假那么進行參數的初始化工作,并在完成初始化之后設置該變量為真,那么下次進入的時候便不必重復初始化了。
對于問題1
let async1 = (cb) => { setTimeout(() => { console.log("異步任務1結束了") cb() }, 1000) } let async2 = (cb) => { setTimeout(() => { console.log("異步任務2結束了") cb() }, 2000) } let fn = () => { console.log("我是兩個任務都結束了才進行的任務") }
如果要在任務1,和任務2都結束了才進行fn任務,我們一般的寫法是啥?
可能會下面這樣寫
async1(() => { async2(fn) })
這樣確實可以保證任務fn是在前面兩個異步任務都結束之后才進行,但是相信你是不太喜歡回調的寫法的,這里舉的異步任務只有兩個,如果多了起來,恐怕就要蛋疼了。別疼,用下劃線的after函數可以解救你。
fn = _.after(2, fn) async1(fn) async2(fn)
運行截圖
有木有很爽,不用寫成回調地獄的形式了。那么接下來我們看看源碼是怎么實現的。
after源碼實現
_.after = function(times, func) { return function() { // 只有返回的函數被調用times次之后才執行func操作 if (--times < 1) { return func.apply(this, arguments); } }; };
源碼簡單到要死啊,但是就是這么神奇,妥妥地解決了我們的問題1。
對于問題2
let app = { init (name, sex) { if (this.initialized) { return } // 進行參數的初始化工作 this.name = name this.sex = sex // 初始化完成,設置標志 this.initialized = true }, showInfo () { console.log(this.name, this.sex) } } // 傳參數進行應用的初始化 app.init("qianlonog", "boy") app.init("xiaohuihui", "girl") app.showInfo() // qianlonog boy 注意這里打印出來的是第一次傳入的參數
一般需要且只進行一次參數初始化工作的時候,我們可能會像上面那樣做。但是其實如果用下劃線中的before方法我們還可以這樣做。
let app = { init: _.before(2, function (name, sex) { // 進行參數的初始化工作 this.name = name this.sex = sex }) , showInfo () { console.log(this.name, this.sex) } } // 傳參數進行應用的初始化 app.init("qianlonog", "boy") app.init("xiaohuihui", "girl") app.showInfo() // qianlonog boy 注意這里打印出來的是第一次傳入的參數
好玩吧,讓我們看看_.before是怎么實現的。
// 創建一個函數,這個函數調用次數不超過times次 // 如果次數 >= times 則最后一次調用函數的返回值將被記住并一直返回該值 _.before = function(times, func) { var memo; return function() { // 返回的函數每次調用都times減1 if (--times > 0) { // 調用func,并傳入外面傳進來的參數 // 需要注意的是,后一次調用的返回值會覆蓋前一次 memo = func.apply(this, arguments); } // 當調用次數夠了,就將func銷毀設置為null if (times <= 1) func = null; return memo; }; };讓函數具有記憶的功能
在程序中我們經常會要進行一些計算的操作,當遇到比較耗時的操作時候,如果有一種機制,對于同樣的輸入,一定得到相同的輸出,并且對于同樣的輸入,后續的計算直接從緩存中讀取,不再需要將計算程序運行那就非常贊了。
舉例
let calculate = (num, num2) => { let result = 0 let start = Date.now() for (let i = 0; i< 10000000; i++) { // 這里只是模擬耗時的操作 result += num } for (let i = 0; i< 10000000; i++) { // 這里只是模擬耗時的操作 result += num2 } let end = Date.now() console.log(end - start) return result } calculate(1, 2) // 30000000 // log 得到235 calculate(1, 2) // 30000000 // log 得到249
對于上面這個calculate函數,同樣的輸入1, 2,兩次調用的輸出都是一樣的,并且兩次都走了兩個耗時的循環,看看下劃線中的memoize函數,如何為我們省去第二次的耗時操作,直接給出300000的返回值
let calculate = _.memoize((num, num2) => { let start = Date.now() let result = 0 for (let i = 0; i< 10000000; i++) { // 這里只是模擬耗時的操作 result += num } for (let i = 0; i< 10000000; i++) { // 這里只是模擬耗時的操作 result += num2 } let end = Date.now() console.log(end - start) return result }, function () { return [].join.call(arguments, "@") // 這里是為了給同樣的輸入指定唯一的緩存key }) calculate(1, 2) // 30000000 // log 得到 238 calculate(1, 2) // 30000000 // log 啥也沒有打印出,因為直接從緩存中讀取了
源碼實現
_.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; // 注意hasher,如果傳了hasher,就用hasher()執行的結果作為緩存func()執行的結果的key var address = "" + (hasher ? hasher.apply(this, arguments) : key); // 如果沒有在cache中查找到對應的key就去計算一次,并緩存下來 if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); // 返回結果 return cache[address]; }; memoize.cache = {}; return memoize; // 返回一個具有cache靜態屬性的函數 };
相信你已經看懂了源碼實現,是不是很簡單,但是又很實用有趣。
來一下延時(_.delay和_.defer)下劃線中在原生延遲函數setTimeout的基礎上做了一些改造,產生以上兩個函數
_.delay(function, wait, *arguments)
就是延遲wait時間去執行function,function需要的參數由*arguments提供
使用舉例
var log = _.bind(console.log, console) _.delay(log, 1000, "hello qianlongo") // 1秒后打印出 hello qianlongo
源碼實現
_.delay = function(func, wait) { // 讀取第三個參數開始的其他參數 var args = slice.call(arguments, 2); return setTimeout(function(){ // 執行func并將參數傳入,注意apply的第一個參數是null護著undefined的時候,func內部的this指的是全局的window或者global return func.apply(null, args); }, wait); };
不過有點需要注意的是_.delay(function, wait, *arguments)`function中的this指的是window或者global`
_.defer(function, *arguments)
延遲調用function直到當前調用棧清空為止,類似使用延時為0的setTimeout方法。對于執行開銷大的計算和無阻塞UI線程的HTML渲染時候非常有用。 如果傳遞arguments參數,當函數function執行時, arguments 會作為參數傳入
源碼實現
_.defer = _.partial(_.delay, _, 1);
所以主要還是看_.partial是個啥
可以預指定參數的函數_.partial局部應用一個函數填充在任意個數的 參數,不改變其動態this值。和bind方法很相近。你可以在你的參數列表中傳遞_來指定一個參數 ,不應該被預先填充(underscore中文網翻譯)
使用舉例
let fn = (num1, num2, num3, num4) => { let str = `num1=${num1}` str += `num2=${num2}` str += `num3=${num3}` str += `num4=${num4}` return str } fn = _.partial(fn, 1, _, 3, _) fn(2,4)// num1=1num2=2num3=3num4=4
可以看到,我們傳入了_(這里指的是下劃線本身)進行占位,后續再講2和4填充到對應的位置去了。
源碼具體怎么實現的呢?
_.partial = function(func) { // 獲取除了傳進回調函數之外的其他預參數 var boundArgs = slice.call(arguments, 1); var bound = function() { var position = 0, length = boundArgs.length; // 先創建一個和boundArgs長度等長的空數組 var args = Array(length); // 處理占位元素_ for (var i = 0; i < length; i++) { // 如果發現boundArgs中有_的占位元素,就依次用arguments中的元素進行替換boundArgs args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } // 把auguments中的其他元素添加到boundArgs中 while (position < arguments.length) args.push(arguments[position++]); // 最后執行executeBound,接下來看看executeBound是什么 return executeBound(func, bound, this, this, args); }; return bound; };
在上一篇文章如何寫一個實用的bind?
有詳細講解,這里我們再回顧一下
executeBound
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { // 如果調用方式不是new func的形式就直接調用sourceFunc,并且給到對應的參數即可 if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); // 處理new調用的形式 var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; };
先看一下這些參數都?代表什么含義
sourceFunc:原函數,待綁定函數
boundFunc: 綁定后函數
context:綁定后函數this指向的上下文
callingContext:綁定后函數的執行上下文,通常就是 this
args:綁定后的函數執行所需參數
這里其實就是執行了這句,所以關鍵還是如果處理預參數,和后續參數的邏輯
sourceFunc.apply(context, args);管道式函數組合
你也許遇到過這種場景,任務A,任務B,任務C必須按照順序執行,并且A的輸出作為B的輸入,B的輸出作為C的輸入,左后再得到結果。用一張圖表示如下
那么一般的做法是什么呢
let funcA = (str) => { return str += "-A" } let funcB = (str) => { return str += "-B" } let funcC = (str) => { return str += "-C" } funcC(funcB(funcA("hello"))) // "hello-A-B-C"
用下劃線中的compose方法怎么做呢
let fn = _.compose(funcC, funcB, funcA) fn("hello") // "hello-A-B-C"
看起來沒有一般的做法那樣,層層繞進去了,而是以一種非常扁平的方式使用。
同樣我們看看源碼是怎么實現的。
_.compose源碼
_.compose = function() { var args = arguments; // 從最后一個參數開始處理 var start = args.length - 1; return function() { var i = start; // 執行最后一個函數,并得到結果result var result = args[start].apply(this, arguments); // 從后往前一個個調用傳進來的函數,并將上一次執行的結果作為參數傳進下一個函數 while (i--) result = args[i].call(this, result); // 最后將結果導出 return result; }; };給多個函數綁定同樣的上下文(_.bindAll(object, *methodNames))
將多個函數methodNames綁定上下文環境為object
? ? ?,好困,寫文章當真好要時間和精力,到這里已經快寫了3個小時了,夜深,好像躺下睡覺啊!!!啊啊啊,再等等快說完了(希望不會誤人子弟)。
var buttonView = { label : "underscore", onClick: function(){ alert("clicked: " + this.label); }, onHover: function(){ console.log("hovering: " + this.label); } }; _.bindAll(buttonView, "onClick", "onHover"); $("#underscore_button").bind("click", buttonView.onClick);
我們用官網給的例子說一下,默認的jQuery中$(selector).on(eventName, callback)callback中的this指的是當前的元素本身,當時經過上面的處理,會彈出underscore。
_.bindAll源碼實現
_.bindAll = function(obj) { var i, length = arguments.length, key; // 必須要指定需要綁定到obj的函數參數 if (length <= 1) throw new Error("bindAll must be passed function names"); // 從第一個實參開始處理,這些便是需要綁定this作用域到obj的函數 for (i = 1; i < length; i++) { key = arguments[i]; // 調用內部的bind方法進行this綁定 obj[key] = _.bind(obj[key], obj); } return obj; };
內部使用了_.bind進行綁定,如果你對_.bind原生是如何實現的可以看這里如何寫一個實用的bind?
拾遺最后關于underscore.js中function篇章還有兩個函數說一下,另外節流函數throttle以及debounce_會另外多帶帶寫一篇文章介紹,歡迎前往underscore-analysis/ watch一下,隨時可以看到動態更新。
_.wrap(function, wrapper)
將第一個函數 function 封裝到函數 wrapper 里面, 并把函數 function 作為第一個參數傳給 wrapper. 這樣可以讓 wrapper 在 function 運行之前和之后 執行代碼, 調整參數然后附有條件地執行.
直接看源碼實現吧
_.wrap = function(func, wrapper) { return _.partial(wrapper, func); };
還記得前面說的partial吧,他會返回一個函數,內部會執行wrapper,并且func會作為wrapper的一個參數被傳入。
_.negate(predicate)
將predicate函數執行的結果取反。
使用舉例
let fn = () => { return true } _.negate(fn)() // false
看起來好像沒什么軟用,但是。。。。
let arr = [1, 2, 3, 4, 5, 6] let findEven = (num) => { return num % 2 === 0 } arr.filter(findEven) // [2, 4, 6]
如果要找到奇數呢?
let arr = [1, 2, 3, 4, 5, 6] let findEven = (num) => { return num % 2 === 0 } arr.filter(_.negate(findEven)) // [1, 3, 5]
源碼實現
_.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; };
源碼很簡單,就是把你傳進來的predicate函數執行的結果取反一下,但是應用還是蠻多的。
結尾這幾個是underscore庫中function相關的api,大部分已經說完了,如果對你有一點點幫助。
點一個小星星吧???
點一個小星星吧???
點一個小星星吧???
good night ?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83174.html
前言 這是underscore.js源碼分析的第六篇,如果你對這個系列感興趣,歡迎點擊 underscore-analysis/ watch一下,隨時可以看到動態更新。 下劃線中有非常多很有趣的方法,可以用比較巧妙的方式解決我們日常生活中遇到的問題,比如_.after,_.before,_.defer...等,也許你已經用過他們了,今天我們來深入源碼,一探究竟,他們到底是怎么實現的。 showIm...
摘要:類總所周知,不像其他面向對象語言那樣支持類,但是可以通過函數和原型來模擬類。如果你學習過或者其他面向對象語言的話,你會覺得很熟悉。結論下一個版本的會帶來一個更加簡單更加友好的語法來幫助那些從面向對象語言轉過來的開發者的學習。 原文地址:http://www.frontendjournal.com/javascript-es6-learn-important-features-in-a-...
摘要:第一部分介紹了如何使用和開發接口。由于系統變得越來越復雜,人們提出了稱為預處理器和后處理器的工具來管理復雜性。當您第一次得知有預處理器和后處理器時,你很有可能在任何地方已經使用它們。我之前建議的文章,,也涵蓋了預處理器相關的知識。 我記得我剛開始學習前端開發的時候。我看到了很多文章及資料,被學習的資料壓得喘不過氣來,甚至不知道從哪里開始。 本指南列出前端學習路線,并提供了平時收藏的一些...
閱讀 3656·2021-10-12 10:11
閱讀 1020·2021-09-22 15:42
閱讀 3472·2019-08-30 13:06
閱讀 912·2019-08-29 17:05
閱讀 1657·2019-08-29 12:21
閱讀 2386·2019-08-29 11:31
閱讀 1141·2019-08-23 18:37
閱讀 1263·2019-08-23 14:58