最近某次筆試看到了一個比較有意思的LazyMan問題,基於自己的一些基礎做了一些解答,回來結合了一些相關資料,自己重新代碼實現了一遍。
問題描述
實現一個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
以此類推。
思路分析
看到這個題目,首先注意到一些關鍵點聯想到對應的方案點。
LazyMan(“Hank”)調用,而不是new LazyMan(“Hank”)創建 => 工廠方法返回new對象- 鏈式調用實現 => 每次調用返回this
sleep需要等待10s =>setTimeout實現sleepsetTimeout會放到事件列表中排隊,繼續執行後面的代碼,但是題目中sleep需要阻塞後續操作。 => 考慮將sleep封裝成promise,使用async/await等待sleep,實現阻塞。- sleepFirst每次在最開始執行,考慮將sleepFirst插入到事件第一個執行。
因此,首先我們需要taskQueue記錄事件列表,直到調用完成後再執行taskQueue裏面的事件。怎麼實現調用完成後才開始執行taskQueue的事件呢?
答案:setTimeout機制。setTimeout(function(){xxx},0)不是立馬執行,這是因為js是單線程的,有一個事件隊列機制,setTimeout和setInterval的回調會插入到延遲時間塞入事件隊列中,排隊執行。
源碼展示
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');
輸出: