定時器(setTimeout)的秘密

美美噠武功山

原文地址:→傳送門

寫在前面

setTimeout()是大家再熟悉不過的定時器,但平時對定時器的了解甚少,于是想看看setTimeout()的原理機制。

setTimeout()基礎(chǔ)

setTimeout()函數(shù)用來指定某個函數(shù)或某段代碼,在多少毫秒之后執(zhí)行。它返回一個整數(shù),表示定時器的編號,以后可以用來取消這個定時器。

var timeer = setTimeout(function|code,delay);

注:其中為code時需要以字符串形式傳入,一般都是使用傳入function的形式。

setTimeout(func,delay,arg1,arg2···)

setTimeout()可傳入多個參數(shù),第三個參數(shù)起是作為回調(diào)函數(shù)的參數(shù)傳入。

        function add(a,b){
            console.log(a+b);
        }
        setTimeout(add,4000,20,30);  //20和30作為add的參數(shù)傳入 結(jié)果為50

HTML5規(guī)定setTimeout()的最短時間間隔是4ms,對于不處在當前窗口的頁面,瀏覽器會將時間間隔擴大到1000ms,若筆記本電腦處于電池供電狀態(tài),chrome及IE9以上版本,會將時間間隔切換到系統(tǒng)定時器,約15.6ms。

setTimeout()中回調(diào)函數(shù)的指向

setTimeout()回調(diào)函數(shù)調(diào)用的是某個對象的方法,則其中this指向的是setTimeout所處的環(huán)境而并非指向該對象環(huán)境。

可使用ES5中的bind()方法解決this指向,即將對象環(huán)境綁定到要執(zhí)行的回調(diào)函數(shù)上即可。

        var name = 'lily';
        function Person(name){
            this.name = name;
            this.printName = function(){
                console.log(this.name);
            }
        }

        var person01 = new Person('jike');
        
         //此處的this.name為window下的name,所以是'lily'
        window.setTimeout(person01.printName,1000); 
        
        //此處將printName綁定在person01上,所以this.name的值為'jike'
        window.setTimeout(person01.printName.bind(person01),2000);  
        
        //將printName中的this.name與person01限定在同一個作用域中,所以結(jié)果是'jike'
        window.setTimeout(function(){person01.printName();},3000); 
        
        //借助ES6中的箭頭函數(shù)也可以解決問題
        window.setTimeout(() => {person01.printName},1000);   

setTimeout運行機制

setTimeout()和setInterval()的運行機制是,將指定的代碼移出本次執(zhí)行,等到下一輪Event Loop時,再檢查是否到了指定時間。如果到了,就執(zhí)行對應的代碼;如果不到,就等到再下一輪Event Loop時重新判斷。這意味著,setTimeout()指定的代碼,必須等到本次執(zhí)行的所有代碼都執(zhí)行完,才會執(zhí)行。

例如:

task1();
setTimeout(otherTask,1000);
task2();

其中task1和task2為立即執(zhí)行任務(wù),otherTask被指定在1s后執(zhí)行,此時otherTask被添加到任務(wù)隊列的尾部,要等當前的腳本中Event Loop的任務(wù)隊列全部執(zhí)行完成后,才開始執(zhí)行otherTask,但如果task1和task2很耗時,前面的任務(wù)超過1s還未結(jié)束,此時otherTask只能等task1和task2運行結(jié)束,才會執(zhí)行otherTask。

setTimeout(func,0)的運用

當延遲時間設(shè)為0時,即當前任務(wù)隊列一結(jié)束就立即執(zhí)行func。(換個角度說就是同步任務(wù)的任務(wù)隊列結(jié)束后盡早執(zhí)行。)

        task01();
        setTimeout(function(){
            console.log('hello!');  //在task01和task02結(jié)束后立即打印hello!
        },0)
        task02();

0ms在實際上是達不到的,根據(jù)HTML5標準,setTimeout()推遲執(zhí)行的時間最少是4ms,如果小于這個值,會被自動增加到4,同時也是為了防止多個setTimeout(func,0)語句連續(xù)執(zhí)行,造成性能問題。

  • setTimeout(func,0)可以調(diào)整事件的發(fā)生順序。

clearTimeout()

setTimeout()和setInterval()函數(shù),都返回一個表示計數(shù)器編號的整數(shù)值,將該整數(shù)傳入clearTimeout()和clearInterval()函數(shù),就可以取消對應的定時器。兩種定時器用的同一個編號池。

setTimeout()和setInterval()函數(shù)返回的整數(shù)值都是連續(xù)的,因此可以利用循環(huán)來清除所有的定時器。

防抖動(debounce)

該方法用于防止某個函數(shù)在短時間內(nèi)被密集調(diào)用,具體來具體來說,debounce方法返回一個新版的該函數(shù),這個新版函數(shù)調(diào)用后,只有在指定時間內(nèi)沒有新的調(diào)用,才會執(zhí)行,否則就重新計時。

實際中不要設(shè)置太多個setTimeout()和setInterval(),它們耗費CPU,理想做法是將要延遲執(zhí)行的代碼都放在同一個函數(shù)里,然后支隊這個函數(shù)使用setTimeout()和setInterval()。

setInterval()

setInterval()使用原理機制與setTimeout()一樣,區(qū)別就是setInterval()是每隔多少ms執(zhí)行一次回調(diào)函數(shù)。也可以傳入大于兩個參數(shù)。

setInterval()指定的是“開始執(zhí)行”之間的間隔,并不考慮每次任指定的是“開始執(zhí)行”之間的間隔,并不考慮每次任,比如,setInterval()指定每100ms執(zhí)行一次,每次執(zhí)行需要5ms,那么第一次執(zhí)行后95ms后,第二次執(zhí)行就會開始。如果某次執(zhí)行需要105ms,即超過了delay時間,則執(zhí)行結(jié)束后下一次執(zhí)行就會立即開始。

setInterval()的最短間隔是10ms,小于10ms的時間間隔會被調(diào)整到10ms

setInterval()具有累積效應,如果某個操作特別耗時,超過了setInterval()的時間間隔,排在后面的操作就會被累積起來,然后在很短的時間內(nèi)連續(xù)觸發(fā),這可能會造成性能問題。

資源參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容