定時器和ajax

JS中關於定時器, 有兩種: setTimeoutsetInterval,區別在於單次與循環.
衆所周知, JS運行在單線程的環境中, 也就沒有什麼並行處理的說法. 既然是單線程, 那麼肯定有個隊列(FIFO)來排列代碼的執行順序, 要不然一擁而上去搶購, 那程序肯定 crash.
先來看一段代碼:

for(var i=0; i<5; i++){
    setTimeout(function(){
        console.log(i);
    }, 0);
}

不妨想一下, 這段代碼的執行結果是多少. 年初去攜程面試的時候, 面試官給的題. 當時太青澀, 只會吹牛, 卻不會庖丁解牛, 自然被pass咯. 當時我給出的回答是0, 1, 2, 3, 4. 面試官呵呵一笑, 打印後是5, 5, 5, 5, 5.
這裏寫圖片描述
如上圖所述, console.log(i)這條語句, 只是在時隔特定時間後, 才插入JS進程時間的隊列, 並不是立刻執行.
打個比方: 你要去一個很火的飯店(已滿座)吃飯, 到前臺後, 肯定是先領號碼, 當服務員叫到你的號碼之後, 你才能進去找空座坐下, 此時你仍然不能立刻吃飯, 還得等廚師(只有一個)做你的菜, 菜做完, 端上桌, 你才能吃飯.
飯店===JS進程時間線
號碼===特定時間(即定時器的第二個參數)
坐定===回調函數被插入時間線隊列, 並等待執行
做菜===隊列中排在前面的操作執行完畢
吃飯===執行本次定時器的回調操作

關於定時器要記住的最重要的事情是: 指定的時間間隔表示何時將定時器的代碼添加到隊列, 而不是何時執行代碼. - - -《JavaScript高級程序設計》(第三版) Page610

如果弄懂上面這句話, 那道面試題就很好理解了. 在循環當中, 肯定首要處理循環, 所以每次循環後, 定時器中的代碼都被放入隊列中, 等待循環執行完畢, 再執行隊列中的console. 而循環執行完畢時, 此時i===5, 所以輸出5個5 .
如果仍然要利用定時器輸出0, 1, 2, 3, 4, 可以對上面代碼做如下改造:

for(var i=0; i<5; i++){
    (function(arg){
        setTimeout(function(){
            console.log(arg);
        }, 0);
    })(i);
}

這就利用了閉包函數的特性了, 立即執行.

有了上面的基礎後, 再說個跟ajax有關的, 請看代碼:

var xhr = new XMLHttpRequest();
xhr.open("get", "js/jquery.min.js", true);
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if (xhr.status == 200){
            console.log("ajax");
        }
    }
}
xhr.send();

setTimeout(function(){
    console.log("定時器");
}, 0);

打印順序是什麼呢?
ajax
定時器
?
但是很”不講道理”的, 順序卻是:

定時器
ajax

其實這是合理的, xhr.send();這個方法的確在定時器前面執行, 但是xhr.onreadystatechange這是一個回調函數, 也就是說請求後, 不管成功失敗, 都會進這個方法. 而發送請求時, 卻是走的網絡通信那一塊了, 跟JS代碼基本沒有關係, 所以此時JS代碼處於空閒狀態, 就會執行隊列中的方法. 等到請求完畢, 才執行xhr.onreadystatechange方法. 所以順序自然是定時器在前, ajax在後.

setInterval
循環定時器就比較複雜了, 之後有時間再寫吧.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章