前幾天在稀土上看到一篇面試的帖子,裏面微信有一道題是lazyman的實現,具體要作的事情就是javascript
LazyMan(「Hank」) //Hi! This is Hank! LazyMan(「Hank」).sleep(10).eat(「dinner」) // Hi! This is Hank! // 等待10秒.. // Wake up after 10 // Eat dinner~ LazyMan(「Hank」).eat(「dinner」).eat(「supper」) // Hi This is Hank! // Eat dinner~ // Eat supper~ LazyMan(「Hank」).sleepFirst(5).eat(「supper」) // 等待5秒 // Wake up after 5 // Hi This is Hank! // Eat supper
這道題考察的確定不是實現這個函數的能力問題,應該是流程控制的問題。解決思路應該是將全部的人如都存放到一個數組中,並在全部的方法執行完以後一次性的輸出,實現的代碼以下:java
function _LazyMan(name) { this.tasks = []; var self = this; var fn =(function(n){ var name = n; return function(){ console.log("Hi! This is " + name + "!"); self.next(); } })(name); this.tasks.push(fn); setTimeout(function(){ self.next(); }, 0); // 在下一個事件循環啓動任務 } /* 事件調度函數 */ _LazyMan.prototype.next = function() { var fn = this.tasks.shift(); fn && fn(); } _LazyMan.prototype.eat = function(name) { var self = this; var fn =(function(name){ return function(){ console.log("Eat " + name + "~"); self.next() } })(name); this.tasks.push(fn); return this; // 實現鏈式調用 } _LazyMan.prototype.sleep = function(time) { var self = this; var fn = (function(time){ return function() { setTimeout(function(){ console.log("Wake up after " + time + "s!"); self.next(); }, time * 1000); } })(time); this.tasks.push(fn); return this; } _LazyMan.prototype.sleepFirst = function(time) { var self = this; var fn = (function(time) { return function() { setTimeout(function() { console.log("Wake up after " + time + "s!"); self.next(); }, time * 1000); } })(time); this.tasks.unshift(fn); return this; } /* 封裝 */ function LazyMan(name){ return new _LazyMan(name); }
我本身在思考解決方法的時候最讓我困惑的就是若是判斷Lazyman對象的方法被調用結束了?我甚至爲了這個問題坐過了站。。。後來當我看到這段代碼的時候,發現一個簡單的setTimeout
就解決了這個問題,爲何呢???面試
這張圖是MDN對Event Loop的解釋,這張圖上分爲三個部分,分別是隊列、棧、和堆。咱們在理解lazyman的過程當中須要知道的就是隊列和棧。首先咱們來說這個棧:數組
在js中,每當有函數被執行的時候都會在當前的執行堆棧中建立一個新的堆棧幀,並放到棧頂。這個堆棧幀中包含當前執行的函數的參數和局部變量。(有沒有感受很熟悉,沒錯,這就是咱們理解做用域鏈的時候的那個棧)而當咱們的函數執行完以後,這個堆棧幀就會從當前棧中移除。微信
隊列就是JS中用來處理異步事件的隊列,每當有新的異步事件發生,就會添加一個新的消息到隊列的尾部。當以前提到的棧爲空時,JS就會來處理隊列中的消息。併發
舉個例子來講就是:異步
var a = function() { setTimeout(function(){console.log(1)},0) } var b = function(){ a() console.log(2) } // 2 // 1
這裏須要注意的有函數
就算你不在函數中使用setTimeout
,而是在全局環境中使用,setTimeout
也是在正常的同步代碼執行完以後執行,這是由於還有宿主環境在。oop
setTimeOut
是通過一段時間以後直接向隊列中加入一個消息,而普通的http請求是等到有返回結果了纔會將消息加入到隊列中。this
普通的異步事件若是沒有事件監聽器的話是不會操做隊列的,消息是直接被忽視掉。
Lazyman中的`setTimeout不是單單的在函數中執行,而是在對象鏈式調用中執行。由於是鏈式調用,因此代碼一直在同一個做用於中執行,也就是說當前的堆棧幀一直沒有被移出棧。上面的代碼中就是利用了這個特色解決了如何判斷對象調用結束的問題。