摘要:最后畫幾張粗糙的圖,簡單描述一下這個執行的過程因為是鏈式調用,所以在鏈上的都會入棧然后執行,額,執行棧少畫了和。。。
前言:昨天在群里討(jin)論(chui)技(niu)術(pi),有位老鐵發了一道他面的某公司面試題,順手保存了。今早花了一點時間把這題做了出來,發現挺有意思的,決定在今天認真工(hua)作(shui)前,與大家分享我的解題方案和思考過程。
題目如下(可以自己先思考一會,沒準可以想出比我更好的方法):
小眼一撇,這幾個需求都是要實現鏈式調用,而鏈式調用最常見的是 jQuery,還有就是我們非常熟悉的 Promise。
jQuery中鏈式調用的原理是在函數的末尾return this(即返回這個對象自身),使得對象可以繼續調用自身的函數從而達到支持鏈式調用。
知道了這個套路之后,接下來我們可以按照這個套路飛快的先寫出符合第一個小需求的函數。
const LazyMan = function (name) { console.log(`Hi i am ${name}`); } LazyMan("Tony") // Hi i am Tony
雖然只有短短三行代碼,但沒有報一點錯,而且運行起來飛快的,完美的實現了第一個小需求。
某路人:“等等,,不就是一個簡單的函數,套路用在哪呢?”
嘖嘖,被你發現了,小伙子不錯嘛,好,現在就用鏈式調把第二個小需求實現了:
const LazyMan = function (name) { console.log(`Hi i am ${name}`); class F { sleep(timeout) { setTimeout(function () { console.log(`等待了${timeout}秒`); return this; }, timeout) }; eat(food) { console.log(`I am eating ${food}`); return this; } } return new F(); } LazyMan("Tony").sleep(10).eat("lunch")
丟瀏覽器里面跑一下,一段紅條條蹦了出來
Uncaught TypeError: Cannot read property "eat" of undefined
納尼,eat為什么會在undefined上調用,我不是在sleep中返回了this么!?是不是 Chrome 又偷偷更新,加了一個新 bug,,,
不過 google 工程師應該沒有這么不靠譜吧。難道是我寫錯了?
掃一遍代碼,發現return this是在setTimeout中的處理函數返回的,而不是sleep返回的,小改一下。
// ... sleep(timeout) { setTimeout(function () { console.log(`等待了${timeout}秒....`); }, timeout) return this; }; // ...
再跑一下,沒有紅條條了,嘿。
但仔細一看,跟需求中的順序不一致,我們現在的輸出是這樣的:
Hi i am Tony I am eating lunch 等待了10秒
emmmmm,看來,現在得拿出一點 JavaScript 硬本事了。
JavaScript 中有同步任務和異步任務,同步任務就是按照我們編寫順序推入執行棧,一步一步執行;而setTimeout屬于異步任務,在瀏覽器中是由定時觸發器線程負責,這個線程會進行計時,當計時完成后將這個事件的handler推入到任務隊列中,任務隊列中的任務需要等待執行棧中為空時把隊列中的任務丟入執行棧中進行執行(從這里也可以知道handler并不能準時執行)。
(隨手畫了一張草圖,有點丑,不過應該不影響我想要表達的意思)
如果不太了解,可以參考這篇文章 這一次,徹底弄懂 JavaScript 執行機制 ,寫的非常易懂了
知道了這個知識后,然并卵,它不能幫我們寫出所需要的代碼。。。
在空氣安靜了數十分鐘后,我還是毫無頭緒,只好拿起杯子,準備起身去倒杯水壓壓驚,突然猶有一道閃電擊到了我一般,腦海中浮現了 vue 中實現nextTick這一方法實現的代碼,代碼雖模糊不清(我根本記不清楚了),但我造這應該可以幫助我解決點什么問題。so,我放下杯子,熟練的打開某 hub,在里面找到了nextTick的實現代碼(在這里next-tick.js)。
快速從第一行到最后一行掃了一遍,可以獲取到的東東是:它用一個callbacks數組存儲需要執行的函數,然后利用micro task和macro task的優先級特性,從而可以在 DOM 渲染之前執行callbacks中的回調。emmmmm,跟我現在的需求好像扯不上什么關系,并不能給什么幫助。不過我也可以把需要執行的函數加入一個數組中,在最后執行它。說干就干,可以快速寫出如下代碼:
const LazyMan = function (name) { console.log(`Hi i am ${name}`); function _eat(food){ console.log(`I am eating ${food}`); } const callbacks = []; class F { sleep(timeout) { setTimeout(function () { console.log(`等待了${timeout}秒....`); callbacks.forEach(cb=>cb()) }, timeout); return this; }; eat(food) { callbacks.push(_eat.bind(null,food)); return this; } } return new F(); }; LazyMan("Tony").sleep(10).eat("lunch") // Hi i am Tony // 等待了10秒.... // I am eating lunch
執行完,輸出跟需求一模一樣,嘿嘿嘿。
接著按照第三個小需求執行一下,結果如下:
//... LazyMan("Tony").eat("lunch").sleep(10).eat("dinner") // Hi i am Tony // 等待了10秒 // I am eating lunch // I am eating dinner //...
沒有報錯,很好,但順序又錯了。。。這可不好辦。
眼看著空氣又要安靜下來了,我不能干耗著,決定使用一些常用套路了,比如加個flag,區分是否是需要在 sleep 之后執行的方法,改寫后如下:
const LazyMan = function (name) { console.log(`Hi i am ${name}`); function _eat(food) { console.log(`I am eating ${food}`); } const callbacks = []; let isNeedSleep = false; class F { sleep(timeout) { setTimeout(function () { console.log(`等待了${timeout}秒`); callbacks.forEach(cb => cb()) }, timeout); isNeedSleep = true; return this; }; eat(food) { if (isNeedSleep) { callbacks.push(_eat.bind(null, food)); } else { _eat.call(null, food); } return this; } } return new F(); };
跑一下,跟第三個小需求輸出一模一樣,嘿嘿嘿,小菜一碟。
到最后這個小需求中,鏈式調用中多了一個sleepFirst,其效果是會將sleep提至鏈式調用的最前端來執行,也就是說sleepFirst的優先級最高。
容我思考一下: 能夠根據優先級來操作的數據結構,在我所知的范圍內只有優先隊列,而優先隊列可以用數組來實現,so,是不是說可以用數組來實現優先級callbacks的調用,即用嵌套數組。答曰:你想的沒有錯啦。
擼起袖子繼續干,于是數分鐘后有了下面這個函數
const LazyMan = function (name) { console.log(`Hi i am ${name}`); function _eat(food) { console.log(`I am eating ${food}`); } const callbackQueue = []; let index = 0; class F { sleep(timeout) { const _callbacks = callbackQueue.shift(); _callbacks && _callbacks.forEach(cb => cb()); setTimeout(function () { console.log(`等待了${timeout}秒....`); const _callbacks = callbackQueue.shift(); _callbacks && _callbacks.forEach(cb => cb()) }, timeout); index ++; return this; }; eat(food) { if(!callbackQueue[index]) callbackQueue[index] = []; callbackQueue[index].push(_eat.bind(null, food)); return this; }; sleepFirst(timeout){ setTimeout(function () { console.log(`等待了${timeout}秒....`); const _callbacks = callbackQueue.shift(); _callbacks && _callbacks.forEach(cb => cb()) }, timeout); index ++; return this; } } return new F(); };
我的想法是 每經過一次sleep后,index會+1,表示有新的一組callback,當執行eat時,判斷是否存在當前index對應的數組,不存在則創建一個對應的空數組,然后把對應需要調用的函數添加入這個數組中,最后把這個數組存到callbackQueue中,當添加完成后,會按照順序一步一步從callbackQueue中取出并執行。
雖然我思路這思路應該是對的,但我還是隱隱約約感覺到了里面蘊含的紅條條,先丟瀏覽器中跑一下試試。
結果如下:
Hi i am Tony I am eating lunch I am eating dinner 等待了5秒.... 等待了10秒....
果然,沒有按照所需的順序執行,因為這里還是沒有能夠處理sleepFirst優先級的這個根本問題。。。
等等。。。我剛剛說了啥,"優先級",咱們往上翻,我前面好像提到過這個詞!
沒錯,vue中的nextTick中就用到了,我們可以參考它,利用Event Loop中micro task和macro task執行的優先級來解決這個問題。
const LazyMan = function (name) { console.log(`Hi i am ${name}`); function _eat(food) { console.log(`I am eating ${food}`); } const callbackQueue = []; let index = 0; class F { sleep(timeout) { setTimeout(() => { const _callbacks = callbackQueue.shift(); _callbacks && _callbacks.forEach(cb => cb()); setTimeout(function () { console.log(`等待了${timeout}秒....`); const _callbacks = callbackQueue.shift(); _callbacks && _callbacks.forEach(cb => cb()) }, timeout); }) index++; return this; }; eat(food) { if (!callbackQueue[index]) callbackQueue[index] = []; callbackQueue[index].push(_eat.bind(null, food)); return this; }; sleepFirst(timeout) { Promise.resolve().then(() => { const _callbacks = callbackQueue.shift(); setTimeout(function () { console.log(`等待了${timeout}秒....`); _callbacks && _callbacks.forEach(cb => cb()) }, timeout); }) index++; return this; } } return new F(); };
丟瀏覽器執行一下,完全冇問題,丟 node 中也一樣,歐耶,完美。
最后畫幾張粗糙的圖,簡單描述一下這個執行的過程:
因為是鏈式調用,所以在鏈上的都會入棧然后執行,額,執行棧少畫了 sleep 和 sleepFirst。。。
Hi i am Tony
其中 setTimeout 的 handler 為宏任務,加入marco task隊列中;Promise.resolve().then的回調為微任務,加入micro task隊列中
然后執行棧被清空,micro task中未清空的任務加入執行棧中被執行,
因為其中有一個 setTimeout,所以把其 handler 加入macro task中
前面的微任務執行完就出棧了,這時候macro task中第一個任務入執行棧中進行執行
這個時候如果有 callbacks 就會執行
因為函數內部又有一個 setTimeout,于是把它的 handler 加入macro task中
然后清空執行棧,繼續執行下一個宏任務
等待了5秒.... I am eating lunch I am eating dinner
執行棧為空,把最后一個宏任務丟進棧中執行
等待了10秒.... I am eating junk food
最后總結一下,這道題的難點是能否想到用event loop來解決,如果能往這方向去想了,做起來就很簡單了。
還有平時不怎么動筆的(比如我),一開始寫起文章來就會如鯁在喉,許多內容都寫漏了。所以平時有時間就要多動動筆,寫寫文章,但也不是說東拼西湊一篇,而是真的要有自己的思考和感悟。
最最最后給各位看官老爺多添加一個小需求練練手:
LazyMan("Tony").eat("lunch").eat("dinner").sleepFirst(5).sleep(10).eat("junk food").eat("healthy food") // Hi i am Tony // 等待了5秒 // I am eating lunch // I am eating dinner // 等待了10秒 // I am eating junk food // I am eating healthy food
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101460.html
摘要:想必面試題刷的多的同學對下面這道題目不陌生,能夠立即回答出輸出個,可是你真的懂為什么嗎為什么是輸出為什么是輸出個這兩個問題在我腦邊縈繞。同步任務都好理解,一個執行完執行下一個。本文只是我對這道面試題的一點思考,有誤的地方望批評指正。 想必面試題刷的多的同學對下面這道題目不陌生,能夠立即回答出輸出10個10,可是你真的懂為什么嗎?為什么是輸出10?為什么是輸出10個10?這兩個問題在我腦...
摘要:一篇文章和一道面試題最近,有篇名為張圖幫你一步步看清和的執行順序的文章引起了我的關注。作者用一道年今日頭條的前端面試題為引子,分步講解了最終結果的執行原因。從字面意思理解,讓我們等等。當前的最新版本,在這里的執行順序上,的確存在有問題。 一篇文章和一道面試題 最近,有篇名為 《8張圖幫你一步步看清 async/await 和 promise 的執行順序》 的文章引起了我的關注。 作者用...
摘要:然后最外層這個函數會返回一個新對象,對象里面有一個屬性,名為,而這個屬性的值是一個匿名函數,它會返回。 最近看到一條有意思的閉包面試題,但是看到原文的解析,我自己覺得有點迷糊,所以自己重新做一下這條題目。 閉包面試題原題 function fun(n, o) { // ① console.log(o); return { // ② fun: function(m) ...
摘要:對于這種會退出的情況,數組顯然不能像鏈表一樣直接斷開,因此采用標記法先生成一個長度為的布爾型數組,用填充。中對整個進行遍歷才能得到此時數組中的數量。 文中的速度測試部分,時間是通過簡單的 System.currentTimeMillis() 計算得到的, 又由于 Java 的特性,每次測試的結果都不一定相同, 對于低數量級的情況有 ± 20 的浮動,對于高數量級的情況有的能有 ± 10...
摘要:中的算法附道面試常見算法題解決方法和思路關注每日一道面試題詳解面試過程通常從最初的電話面試開始,然后是現場面試,檢查編程技能和文化契合度。值得記住的數組方法有和。一個好的解決方案是使用內置的方法。 JavaScript中的算法(附10道面試常見算法題解決方法和思路) 關注github每日一道面試題詳解 Introduction 面試過程通常從最初的電話面試開始,然后是現場面試,檢查編程...
閱讀 2041·2023-04-25 15:11
閱讀 3461·2021-09-23 11:57
閱讀 1372·2021-07-26 23:38
閱讀 1319·2019-08-30 15:54
閱讀 635·2019-08-30 15:53
閱讀 3245·2019-08-26 13:36
閱讀 986·2019-08-26 12:01
閱讀 2863·2019-08-23 16:21