深入學習setTimeOut

<html>
<script>
var timeoutTimes = 0;
function timeout() {
    timeoutTimes++;
    console.log("count: " + timeoutTimes);
    if (timeoutTimes < 10) {
        setTimeout(timeout, 500);
    }
}
// timeout();
var intervalTimes = 0;
function interval() {
    intervalTimes++;
    console.log("count: " + intervalTimes);
    if (intervalTimes >= 10) {
       clearInterval(interv);
    }
}
// Jerry comment it out on 2017-02-04 10:41PM after dinner 初八
// var interv = setInterval(interval, 500);        
</script>

<script>
var start = new Date();  
var end = 0;  
 
setTimeout(function() {   
  console.log(new Date() - start);  
},  500);  
/*
可事實卻是出乎你的意料,打印結果是這樣的(也許你打印出來會不一樣,但肯定會大於1000毫秒):

究其原因,這是因爲 JavaScript是單線程執行的。也就是說,在任何時間點,有且只有一個線程在運行
JavaScript程序,無法同一時候運行多段代碼。




再來看看瀏覽器下的JavaScript。

瀏覽器的內核是多線程的,它們在內核控制下相互配合以保持同步,一個瀏覽器至少實現三個常駐線程:JavaScript引擎線程,GUI渲染線程,瀏覽器事件觸發線程。

  ● JavaScript引擎是基於事件驅動單線程執行的,JavaScript引擎一直等待着任務隊列中任務的到來,然後加以處理,瀏覽器無論什麼時候都只有一個JavaScript線程在運行JavaScript程序。
  ● GUI渲染線程負責渲染瀏覽器界面,當界面需要重繪(Repaint)或由於某種操作引發迴流(Reflow)時,該線程就會執行。但需要注意,GUI渲染線程與JavaScript引擎是互斥的,當JavaScript引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JavaScript引擎空閒時立即被執行。
  ● 事件觸發線程,當一個事件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JavaScript引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeout、也可來自瀏覽器內核的其他線程如鼠標點擊、Ajax異步請求等,但由於JavaScript的單線程關係,所有這些事件都得排隊等待JavaScript引擎處理(當線程中沒有執行任何同步代碼的前提下才會執行異步代碼)。

到這裏,我們再來回顧一下最初的例子:

var start = new Date();  
 
var end = 0;  
 
setTimeout(function() {   
 
  console.log(new Date() - start);  
 
},  500);  
 
while (new Date() - start <= 1000) {}

雖然setTimeout的延時時間是500毫秒,可是由於while循環的存在,只有當間隔時間大於1000毫秒時,纔會跳出while循環,也就是說,在1000毫秒之前,while循環都在佔據着JavaScript線程。也就是說,只有等待跳出while後,線程纔會空閒下來,纔會去執行之前定義的setTimeout。

最後 ,我們可以總結出,setTimeout只能保證在指定的時間後將任務(需要執行的函數)插入任務隊列中等候,但是不保證這個任務在什麼時候執行。一旦執行javascript的線程空閒出來,自行從隊列中取出任務然後執行它。

因爲javascript線程並沒有因爲什麼耗時操作而阻塞,所以可以很快地取出排隊隊列中的任務然後執行它,也是這種隊列機制,給我們製造一個異步執行的假象。


*/

while (new Date() - start <= 1000) {}
</script>
<script>
/*
本意是立刻執行調用函數,但事實上,上面的代碼並不是立即執行的,這是因爲setTimeout有一個最小執行時間,當指定的時間小於該時間時,瀏覽器會用最小允許的時間作爲setTimeout的時間間隔,也就是說即使我們把setTimeout的延遲時間設置爲0,被調用的程序也沒有馬上啓動。

不同的瀏覽器實際情況不同,IE8和更早的IE的時間精確度是15.6ms。不過,隨着HTML5的出現,在高級版本的瀏覽器(Chrome、ie9+等),定義的最小時間間隔是不得低於4毫秒,如果低於這個值,就會自動增加,並且在2010年及之後發佈的瀏覽器中採取一致。

所以說,當我們寫爲 setTimeout(fn,0) 的時候,實際是實現插隊操作,要求瀏覽器“儘可能快”的進行回調,但是實際能多快就完全取決於瀏覽器了。

那setTimeout(fn, 0)有什麼用處呢?其實用處就在於我們可以改變任務的執行順序!因爲瀏覽器會在執行完當前任務隊列中的任務,再執行setTimeout隊列中積累的的任務。

通過設置任務在延遲到0s後執行,就能改變任務執行的先後順序,延遲該任務發生,使之異步執行。

當你往兩個表單輸入內容時,你會發現未使用setTimeout函數的只會獲取到輸入前的內容,而使用setTimeout函數的則會獲取到輸入的內容。

這是爲什麼呢?

因爲當按下按鍵的時候,JavaScript 引擎需要執行 keydown 的事件處理程序,然後更新文本框的 value 值,這兩個任務也需要按順序來,事件處理程序執行時,更新 value值(是在keypress後)的任務則進入隊列等待,所以我們在 keydown 的事件處理程序裏是無法得到更新後的value的,而利用 setTimeout(fn, 0),我們把取 value 的操作放入隊列,放在更新 value 值以後,這樣便可獲取出文本框的值。

未使用setTimeout函數,執行順序是:onkeydown => onkeypress => onkeyup

使用setTimeout函數,執行順序是:onkeydown => onkeypress => function => onkeyup

雖然我們可以使用keyup來替代keydown,不過有一些問題,那就是長按時,keyup並不會觸發。

長按時,keydown、keypress、keyup的調用順序:

keydown
 
keypress
 
keydown
 
keypress
 
...
 
keyup

也就是說keyup只會觸發一次,所以你無法用keyup來實時獲取值。

*/

function onloadfun(){
    document.querySelector('#one').onkeydown = function() {   
    document.querySelector('#one1').innerHTML = this.value;   
};   
 
document.querySelector('#second').onkeydown = function() {   
  setTimeout(function() {   
    document.querySelector('#second1').innerHTML = document.querySelector('#second').value;   }, 0);
};
}

/*
setTimeout不止兩個參數

我們都知道,setTimeout的第一個參數是要執行的回調函數,第二個參數是延遲時間(如果省略,會由瀏覽器自動設置。在IE,FireFox中,第一次配可能給個很大的數字,100ms上下,往後會縮小到最小時間間隔,Safari,chrome,opera則多爲10ms上下。)

其實,setTimeout可以傳入第三個參數、第四個參數….,它們表示神馬呢?其實是用來表示第一個參數(回調函數)傳入的參數。

setTimeout(function(a, b){   
  console.log(a);   // 3
  console.log(b);   // 4
},0, 3, 4);
*/
</script>

<script>
/*
Jerry 2017-03-17 16:58PM */ 
var timerStart1 = now();
setTimeout(function () {
  console.log('第一個setTimeout從call SetTimeout到真正被執行:', now() - timerStart1);

  var timerStart2 = now();
  setTimeout(function () {
    console.log('第二個setTimeout從call SetTimeout到真正被執行:', now() - timerStart2);
  }, 100);

  heavyTask(3123);  // 耗時任務
}, 100);

var loopStart = now();
heavyTask(1781); // 耗時任務
console.log('heavyTask耗費時間:', now() - loopStart);

function heavyTask(duration) {
  var s = now();
  while(now() - s < duration) {
  }
}

function now () {
  return new Date();
}

</script>
<body onload="onloadfun()">
    <p>one</p>
    <input id="one"></input>

    <span id="one1"></span>

    <p>two</p>
    <input id="second"></input>

    <span id="second1"></span>
</body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章