博客 / 詳情

返回

【面試系列】LazyMan的ES6實現

最近某次筆試看到了一個比較有意思的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
 
以此類推。

思路分析

看到這個題目,首先注意到一些關鍵點聯想到對應的方案點。

  1. LazyMan(“Hank”)調用,而不是new LazyMan(“Hank”)創建 => 工廠方法返回new對象
  2. 鏈式調用實現 => 每次調用返回this
  3. sleep需要等待10s => setTimeout實現sleep
  4. setTimeout會放到事件列表中排隊,繼續執行後面的代碼,但是題目中sleep需要阻塞後續操作。 => 考慮將sleep封裝成promise,使用async/await等待sleep,實現阻塞。
  5. sleepFirst每次在最開始執行,考慮將sleepFirst插入到事件第一個執行。

因此,首先我們需要taskQueue記錄事件列表,直到調用完成後再執行taskQueue裏面的事件。怎麼實現調用完成後才開始執行taskQueue的事件呢?
答案:setTimeout機制。setTimeout(function(){xxx},0)不是立馬執行,這是因為js是單線程的,有一個事件隊列機制,setTimeoutsetInterval的回調會插入到延遲時間塞入事件隊列中,排隊執行。

源碼展示

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');
輸出:
image.png

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.