最近某次筆試看到了一個比較有意思的LazyMan問題,基於本身的一些基礎作了一些解答,回來結合了一些相關資料,本身從新代碼實現了一遍。javascript
實現一個LazyMan,能夠按照如下方式調用: 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
LazyMan(「Hank」)
調用,而不是new LazyMan(「Hank」)
建立 => 工廠方法返回new對象sleep
須要等待10s => setTimeout
實現sleep
setTimeout
會放到事件列表中排隊,繼續執行後面的代碼,可是題目中sleep
須要阻塞後續操做。 => 考慮將sleep封裝成promise,使用async/await等待sleep,實現阻塞。所以,首先咱們須要taskQueue
記錄事件列表,直到調用完成後再執行taskQueue
裏面的事件。怎麼實現調用完成後纔開始執行taskQueue
的事件呢?
答案:setTimeout
機制。setTimeout(function(){xxx},0)
不是立馬執行,這是由於js是單線程的,有一個事件隊列機制,setTimeout
和setInterval
的回調會插入到延遲時間塞入事件隊列中,排隊執行。promise
class _LazyMan { constructor(name) { this.taskQueue = []; this.name = name; this.timer = null; this.sayHi(); } // 每次調用時清楚timer,上一次設置的執行taskQueue就不會運行。 // 從新設置timer,會在下一次調用完後進入執行。 // 當全部調用結束後,就會順利執行taskQueue隊列裏的事件 next() { clearTimeout(this.timer); this.timer = setTimeout(async () => { // 執行taskQueue隊列裏的事件 for (let i = 0; i < this.taskQueue.length; i++) { await this.taskQueue[i](); } }); return this; } sayHi() { this.taskQueue.push(() => { console.log('Hi! This is ' + this.name); }); return this.next(); } eat(str) { this.taskQueue.push(() => { console.log('Eat ' + str); }); return this.next(); } beforSleep(time) { // unshift插入到事件的第一個 this.taskQueue.unshift(() => this.sleepPromise(time)); return this.next(); } sleep(time) { this.taskQueue.push(() => this.sleepPromise(time)); return this.next(); } // sleep的Promise對象,用於給async/await來阻塞後續代碼執行 sleepPromise(time) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('wake up after ' + time); resolve(); }, time * 1000); }); } } function LazyMan(name) { return new _LazyMan(name); }
調用測試:LazyMan('Herry').beforSleep(1).eat('dinner').sleep(2).eat('check');
輸出:
async