[Javascript] 實現setInterval函數

問題

常常使用Javascript的同窗必定對setInterval很是熟悉,當使用setInterval(callback, timer)時,每通過timer毫秒時間,系統都將調用一次callback。請問全局若是沒有提供setInterval函數,該如何本身實現這一功能?javascript

方案一:循環或遞歸(錯誤解法)

最簡單的思路即是經過簡單的循環或者遞歸,每次檢查時間戳是否已經超過上次觸發給定函數的時間加上間隔時間,若是已經超過便再次觸發函數,並重置計時器至當前時間。java

const setInterval1 = (func, interval) => {
    let startTime = Date.now();
    const config = { shouldStop: false };
    while (!config.shouldStop) {
        if (Date.now() - startTime >= interval) {
            func();
            startTime = Date.now();
        }
    }
    return config;
}

const myClearInterval = config => { config.shouldStop = true; }

然而這樣的解法有一個致命問題,咱們將setInterval1變成一個阻塞函數,主線程會卡死在這個無限循環或者遞歸中,致使以後的代碼或者事件沒法執行。想了解詳細緣由的請戳: 併發模型與事件循環JavaScript:完全理解同步、異步和事件循環(Event Loop)node

方案二:使用setTimeout

setTimeout的好處在於,它是在消息隊列裏面添加一個待執行的消息,因此並不會堵塞主線程。更方便的在於,因爲setTimeout自帶定時器功能,咱們甚至不用本身去維護一個時間戳。咱們能夠經過不斷遞歸調用setTimeout來實現setInterval的效果web

const setInterval2 = (func, interval) => {
    const config = { shouldStop: false }
    const loop = () => {
        if (!config.shouldStop) {
            func();
            setTimeout(loop, interval);
        }
    }
    setTimeout(loop, interval);
    return config;
}

const myClearInterval = config => { config.shouldStop = true; }

方案三:使用requestAnimationFrame

然而使用setTimeout有違這道題的初衷,由於setTimeout在本質上和setInterval是相似的,多少有些做弊的嫌疑。那有沒有別的非阻塞方案呢?在瀏覽器環境中,咱們有requestAnimationFrame(),而在nodejs環境中,咱們有setImmediate()。以requestAnimationFrame爲例,這將保證咱們的代碼只會在每一幀render以前被遞歸一次,從而避免了阻塞其餘代碼。segmentfault

const setInterval3 = (func, interval) => {
    let startTime = Date.now();
    const config = { shouldStop: false }
    const check = () => {
        if (!config.shouldStop) {
            if (Date.now() - startTime > interval) {
                func();
                startTime = Date.now();
            }
            if(typeof window === 'undefined') {
                setImmediate(check);
            } else {
                window.requestAnimationFrame(check)
            }
        }
    }
    check();
    return config;
}

const myClearInterval = config => { config.shouldStop = true; }

方案四:使用Web Worker

requestAnimationFrame能確保咱們在每幀顯示前被調用一次,從而檢計時器是否到期,可是若是被執行的函數計算量極大,致使幀內沒法完成時,該如何保證給定函數能按時執行呢?顯然,此時只依靠主線程來確保計時程序和給定程序都能準確執行,有點困難,可是若是將計時程序放入另外一線程中,而主程序只負責監聽定時器事件和執行給定程序,是否是會好一些呢?因此咱們這裏利用瀏覽器提供的Web Worker API來實現多線程。請注意這裏因爲沒有調用另外一個腳本,咱們經過blob和object url的方式將咱們的定時器程序check傳入Web Worker中。瀏覽器

const setInterval4 = (func, interval) => {
    if (typeof window !== 'undefined' && window.Worker && window.Blob) {
        const check = new Blob(["(", function(){
            self.onmessage = function(e) {
              const interval = e.data;
            let startTime = Date.now();
            while(true) {
                if (Date.now() - startTime >= interval) {
                  startTime = Date.now();
                self.postMessage(Date.now());
              }
            }
          }
        }.toString(), ")()"], { type: "text/javascript" });
        const worker = new Worker(window.URL.createObjectURL(check));
        worker.onmessage = func;
        worker.postMessage(interval);
                return worker;
    } else {
        console.log('Your environment is not supported');
    }
}

const myClearInterval = config => { config.terminate() }
相關文章
相關標籤/搜索