setTimeout

setTimeout是面試常見的考題,一直被虐,可是久病成良醫,總結一下我所瞭解的這兩個函數的特性和用法以及常見的地方面試

基礎用法

  • setTimeout(call,[wait,...args])wait 延時以後將call 函數加入執行隊列中,IE9+ 的瀏覽器能夠附加其餘額外參數做爲 call 的參數傳遞。只執行一次。
  • setTnterval(call,[wait,...args]) ,與setTimeout相比,每間隔wait以後就將call加入執行隊列
  • setTimeoutsetInterval的返回值是一個正整數,主要爲了清除用的(恕我尚未在其餘地方使用過),清除對應的兩個函數clearTimeoutclearInterval,理論上setTimeoutsetInterval共用一個計時器池,因此清除函數能夠混用,可是爲了代碼可讀仍是作區分。

用setTimeout模擬實現setInterval

經過在內部封裝一個函數,而後在setTimeout 中遞歸的調用實現,美中不足的是沒法中止,也就是定時器id沒法獲取到。ajax

爲何要用setTimeout模擬setInterval?api

每一個setTimeout產生的任務會直接push到任務隊列中;而setInterval在每次把任務push到任務隊列前,都要進行一下判斷(看上次的任務是否仍在隊列中)。這樣某些間隔會被跳過promise

於是咱們通常用setTimeout模擬setInterval,來規避掉上面的缺點。瀏覽器

function func1(call, wait) {
  var done = function() {
    call()
    setTimeout(() => {
      done()
    }, wait)
  }
  setTimeout(done, wait)
}

func1(() => {
  console.log("hello")
}, 1000)
複製代碼

間隔爲一秒連續打印1~10

所謂閉包就是在函數A返回值是B函數,B函數使用了A函數的內部變量,B函數就造成了閉包,當A函數執行結束以後B函數中使用的A函數變量沒有跟着銷燬,還存在與內存中供B使用。閉包

常見考題app

可使用let 產生塊級做用域來處理異步

for (let i = 1; i <= 10; i++) {
    setTimeout(() => {
      console.log(i)
    }, 1000 * i)
  }
複製代碼

事件循環

這個主要是理解程序的執行順序。程序執行分爲宏任務和微任務,setTimeoutsetInterval產生的是宏任務會在下一次事件循環中執行。函數

  • 首先執行主線程,遇到ajax網路請求、用戶與瀏覽器交互事件(點擊,滾動等)、setTimeout等須要外部宿主(主要是瀏覽器)封裝的api執行的任務都會建立一個宏任務,在下一次也買你渲染以後執行。
  • 在每一次宏任務中又會產生微任務,這些微任務會根據添加到微任務隊列的順序在當前宏任務執行結束以後執行。

exampleui

console.log(1)

setTimeout(() => {
  console.log(2)
}, 1000)

new Promise((resolve, reject) => {
  console.log(3)
  resolve()
}).then(() => {
  console.log(4)
})

console.log(5)

setTimeout(() => {
  console.log(6)
})
// 1 3 5 4 6 2
複製代碼
  • 主線程開始執行,打印1
  • 遇到setTimeout交給瀏覽器計時器線程執行 等待 1秒以後將任務加入宏任務隊列
  • promise建立的時候的回調函數直接執行,打印3,.then是微任務加入微任務隊列
  • 打印 5
  • 又是setTimeout 可是延時比以前的底,先加入宏任務隊列
  • 第一個宏任務完成,執行微任務,只有一個打印4
  • 而後挨個執行宏任務隊列 打印 6 和 2

setTimeout 中的this

因爲setTimeout中會產生一個宏任務,下一次執行的時候執行棧發生了改變,因此這裏的this很容易搞混。

這個理解和函數中的this一致,誰調用是誰。setTimeout返回執行的時候沒有對象調用它就是window,因此打印1。用箭頭函數能夠避免這個問題

var x = 1;
var obj = {
  x: 2,
  y: function(){
    console.log(this.x);
  }
};
setTimeout(obj.y,1000);  // 1
複製代碼

用來切割耗時任務

只要是爲了屢次渲染頁面,可讓其餘任務穿插在每次也買你渲染之間執行,不至於頁面卡頓

要求:動態建立一個表格,一共10000行,每行10個單元格

/** * 常規的 */
var tbody = document.getElementsByTagName('tbody')[0];
var allLines = 10000;
// 每次渲染的行數
console.time('wd');
for(var i=0; i<allLines; i++){
    var tr = document.createElement('tr');

    for(var j=0; j<10; j++){
        var td = document.createElement('td');

        td.appendChild(document.createTextNode(i+','+j));
        tr.appendChild(td);
    }

    tbody.appendChild(tr);
}
console.timeEnd('wd'); // 總共耗時180ms, 瀏覽器已經給出警告![Violation] 'setTimeout' handler took 53ms
/** * setTimeout 切割 */
var tbody = document.getElementsByTagName('tbody')[0];
var allLines = 10000;
// 每次渲染的行數
var everyTimeCreateLines = 80;
// 當前行
var currentLine = 0;
setTimeout(function renderTable(){
    console.time('wd');
    for(var i=currentLine; i<currentLine+everyTimeCreateLines && i<allLines; i++){
        var tr = document.createElement('tr');

        for(var j=0; j<10; j++){
            var td = document.createElement('td');

            td.appendChild(document.createTextNode(i+','+j));
            tr.appendChild(td);
        }

        tbody.appendChild(tr);
    }
    console.timeEnd('wd');

    currentLine = i;

    if(currentLine < allLines){
        setTimeout(renderTable,0);
    }
},0);
// 此次異步按批次建立,沒有耗時的警告。由於控制了每次代碼在50ms內運行。實際上每80行耗時約10ms左右。這就不會引發頁面卡頓等問題。
複製代碼
相關文章
相關標籤/搜索