欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解JavaScript定時機制

 更新時間:2016年10月27日 14:52:26   作者:Fogwind  
這篇文章主要介紹了深入理解JavaScript定時機制,對于學(xué)習(xí)JavaScript有一定的幫助,感興趣的小伙伴們可以參考一下。

本文介紹了JavaScript定時機制,要理解JavaScript的定時機制,就要知道JavaScript的運行機制。

首先聲明,JavaScript是單線程運行(JavaScript引擎線程)事件驅(qū)動。

一、瀏覽器中有多個線程

一款瀏覽器中包含的最基本的線程:

1、JavaScript引擎線程。

2、定時器線程,setInterval和setTimeout會觸發(fā)這個線程。

3、瀏覽器事件觸發(fā)線程,這個線程會觸發(fā)onclick、onmousemove和其它瀏覽器事件。

4、界面渲染線程,負責(zé)渲染瀏覽器界面HTML元素。注意:在JavaScript引擎運行腳本期間,界面渲染線程都是處于掛起狀態(tài)的。也就是說當(dāng)使用JavaScript對界面中的節(jié)點進行操作時,并不會立即體現(xiàn)出來,要等到JavaScript引擎線程空閑時,才會體現(xiàn)出來。(這個最后說)

5、HTTP請求線程(Ajax請求也在其中)。

以上這些線程在瀏覽器內(nèi)核的控制下,相互配合,完成工作(具體我也不知道)。

二、任務(wù)隊列

我們知道JavaScript是單線程的,所有JavaScript代碼都在JavaScript引擎線程中運行。阮一峰老師的文章中叫這個線程為主線程,是一個執(zhí)行棧。(以下內(nèi)容也主要是根據(jù)阮一峰老師的文章理解總結(jié)。)

這些JavaScript代碼我們可以把他們看成一個個的任務(wù),這些任務(wù)有同步任務(wù)和異步任務(wù)之分。同步任務(wù)(比如變量賦值語句,alert語句,函數(shù)聲明語句等等)直接在主線程上按順序執(zhí)行,異步任務(wù)(比如瀏覽器事件觸發(fā)線程觸發(fā)的各種各樣的事件,使用Ajax返回的服務(wù)器響應(yīng)等)按照時間先后順序在任務(wù)隊列(也可以叫做事件隊列、消息隊列)中排隊,等待被執(zhí)行。只要主線程上的任務(wù)執(zhí)行完了,就會去檢查任務(wù)隊列,看有沒有排隊等待的任務(wù),有就讓排隊的任務(wù)進入主線程執(zhí)行。

比如下面的例子:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>定時機制</title>

<style type="text/css">
body{
  margin: 0;
  padding: 0;
  position: relative;
  height: 600px;
}

#test{
  height: 30px;
  width: 30px;
  position: absolute;
  left: 0;
  top: 100px;
  background-color: pink;
}
</style>
</head>
<body>
  <div id="test">
  
  </div>

<script>
  var pro = document.getElementById('test');
  pro.onclick = function() {
    alert('我沒有立即被執(zhí)行。');
  };
  function test() {
    a = new Date();
    var b=0;
   for(var i=0;i<3000000000;i++) {
     b++;
   }
   c = new Date();
   console.log(c-a);
  }

 test();
</script>
</body>
</html>

在這個例子中test()函數(shù)執(zhí)行完大概要8~9秒,所以當(dāng)我們打開這個頁面,在8秒之前點擊粉色方塊,不會立即彈出提示框,而要等到8秒之后才彈出,而且8秒之前點擊幾次粉色框,8秒之后就彈出幾次。

我們打開這個頁面時,主線程先聲明函數(shù)test,再聲明變量pro,然后把p節(jié)點賦值給pro,然后給p節(jié)點添加click事件,并指定回調(diào)函數(shù)(掛起),然后調(diào)用test函數(shù),執(zhí)行其中的代碼。在test函數(shù)中的代碼執(zhí)行期間,我們點擊了p節(jié)點,瀏覽器事件觸發(fā)線程檢測到這個事件,就把這個事件放在了任務(wù)隊列中,以便主線程上的任務(wù)(這里是test函數(shù))執(zhí)行完后,檢查任務(wù)隊列時發(fā)現(xiàn)這個事件并執(zhí)行相應(yīng)的回調(diào)函數(shù)。如果我們多次點擊,這些多次觸發(fā)的事件就按觸發(fā)時間的先后在任務(wù)隊列中排隊(可以再給另外一個元素添加點擊事件,交替點擊不同的元素來驗證)。

下面是總結(jié)的任務(wù)的運行機制:

異步執(zhí)行的運行機制如下。(同步執(zhí)行也是如此,因為它可以被視為沒有異步任務(wù)的異步執(zhí)行。)

1、所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。

2、主線程之外,還存在一個"任務(wù)隊列"(task queue)。只要異步任務(wù)有了運行結(jié)果,就在"任務(wù)隊列"之中放置一個事件。

3、一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取"任務(wù)隊列",看看里面有哪些事件。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進入執(zhí)行棧,開始執(zhí)行。

4、主線程不斷重復(fù)上面的第三步。

三、事件和回調(diào)函數(shù)

我們在給DOM元素指定事件時,都會指定一個回調(diào)函數(shù),以便事件真的發(fā)生時執(zhí)行相應(yīng)的代碼。

主線程中事件的回調(diào)函數(shù)會被掛起,如果任務(wù)隊列中有正在排隊的相應(yīng)的事件,當(dāng)主線程檢測到時就會執(zhí)行相應(yīng)的回調(diào)函數(shù)。我們也可以說主線程執(zhí)行異步任務(wù),就是在執(zhí)行相應(yīng)的回調(diào)函數(shù)。

四、事件循環(huán)

主線程檢查任務(wù)隊列中事件的過程是循環(huán)不斷的,因此我們可以畫一個事件循環(huán)的圖:

上圖中主線程產(chǎn)生堆和執(zhí)行棧,棧中的任務(wù)執(zhí)行完畢后,主線程檢查任務(wù)隊列中由其他線程傳入的發(fā)生過的事件,檢測到排在最前面的事件,就從掛起的回調(diào)函數(shù)中找出與該事件對應(yīng)的回調(diào)函數(shù),然后在執(zhí)行棧中執(zhí)行,這個過程一直重復(fù)。

五、定時器

結(jié)合以上知識,下面探討JavaScript中的定時器:setTimeout()和setInterval()。

setTimeout(func, t)是超時調(diào)用,間隔一段時間后調(diào)用函數(shù)。這個過程在事件循環(huán)中的過程如下(我的理解):

主線程執(zhí)行完setTimeout(func, t);語句后,把回調(diào)函數(shù)func掛起,同時定時器線程開始計時,當(dāng)計時等于t時,相當(dāng)于發(fā)生了一個事件,這個事件傳入任務(wù)隊列(結(jié)束計時,只計時一次),當(dāng)主線程中的任務(wù)執(zhí)行完后,主線程檢查任務(wù)隊列發(fā)現(xiàn)了這個事件,就執(zhí)行掛起的回調(diào)函數(shù)func。我們指定的時間間隔t只是參考值,真正的時間間隔取決于執(zhí)行完setTimeout(func, t);語句后的代碼所花費的時間,而且是只大不小。(即使我們把t設(shè)為0,也要經(jīng)歷這個過程)。

setInterval(func, t)是間歇調(diào)用,每隔一段時間后調(diào)用函數(shù)。這個過程在事件循環(huán)中的過程與上面的類似,但又有所不同。

setTimeout()是經(jīng)過時間t后定時器線程在任務(wù)隊列中添加一個事件(注意是一個),而setInterval()是每經(jīng)過時間t(一直在計時,除非清除間歇調(diào)用)后定時器線程在任務(wù)隊列中添加一個事件,而不管之前添加的事件有沒有被主線程檢測到并執(zhí)行。(實際上瀏覽器是比較智能的,瀏覽器在處理setInterval的時候,如果發(fā)現(xiàn)已任務(wù)隊列中已經(jīng)有排隊的同一ID的setInterval的間歇調(diào)用事件,就直接把新來的事件 Kill 掉。也就是說任務(wù)隊列中一次只能存在一個來自同一ID的間歇調(diào)用的事件。)

舉個例子,假如執(zhí)行完setInterval(func, t);后的代碼要花費2t的時間,當(dāng)2t時間過后,主線程從任務(wù)隊列中檢測到定時器線程傳入的第一個間歇調(diào)用事件,func開始執(zhí)行。當(dāng)?shù)谝淮蔚膄unc執(zhí)行完畢后,第二次的間歇調(diào)用事件早已傳入任務(wù)隊列,主線程馬上檢測到第二次的間歇調(diào)用事件,func函數(shù)又立即執(zhí)行。這種情況下func函數(shù)的兩次執(zhí)行是連續(xù)發(fā)生的,中間沒有時間間隔。

下面是個例子:

function test() {
    a = new Date();
    var b=0;
   for(var i=0;i<3000000000;i++) {
     b++;
   }
   c = new Date();
   console.log(c-a);
 }
  function test2() {
   var d = new Date().valueOf();
   //var e = d-a;
   console.log('我被調(diào)用的時刻是:'+d+'ms');
   //alert(1);
  }
  setInterval(test2,3000);
  
 test();

結(jié)果:

為什么8.6秒過后沒有輸出兩個一樣的時刻,原因在上面的內(nèi)容中可以找到。

執(zhí)行例子中的for循環(huán)花費了8601ms,在執(zhí)行for循環(huán)的過程中隊列中只有一個間歇調(diào)用事件在排隊(原因如上所述),當(dāng)8601ms過后,第一個間歇調(diào)用事件進入主線程,對于這個例子來說此時任務(wù)隊列空了,可以再次傳入間歇調(diào)用事件了,所以1477462632228ms這個時刻第二次間歇調(diào)用事件(實際上應(yīng)該是第三次)傳入任務(wù)隊列,由于主線程的執(zhí)行棧已經(jīng)空了,所以主線程立即把對應(yīng)的回調(diào)函數(shù)拿來執(zhí)行,第二次調(diào)用與第一次調(diào)用之間僅僅間隔了320ms(其實8601+320=8920,差不多就等于9秒了)。我們看到第三次調(diào)用已經(jīng)恢復(fù)正常了,因為此時主線程中已經(jīng)沒有其他代碼了,只有一個任務(wù),就是隔一段時間執(zhí)行一次間歇調(diào)用的回調(diào)函數(shù)。

用setTimeout()實現(xiàn)間歇調(diào)用的例子:

function test() {
    a = new Date();
    var b=0;
   for(var i=0;i<3000000000;i++) {
     b++;
   }
   c = new Date();
   console.log(c-a);
  }
 
  function test2(){
   var d = new Date().valueOf();
   console.log('我被調(diào)用的時刻是:'+d+'ms');
   setTimeout(test2,3000);
  }
  setTimeout(test2,3000);
 test();

 結(jié)果:

每兩次調(diào)用的時間間隔基本上是相同。想想為什么?

再看一個例子:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flex布局練習(xí)</title>

<style type="text/css">
body{
  margin: 0;
  padding: 0;
  position: relative;
  height: 600px;
}

#test{
  height: 30px;
  width: 30px;
  position: absolute;
  left: 0;
  top: 100px;
  background-color: pink;
}
</style>
</head>
<body>
  <div id="test">
  
  </div>

<script>
 var p = document.createElement('p');
 p.style.width = '50px';
 p.style.height = '50px';
 p.style.border = '1px solid black';
 
 document.body.appendChild(p);

 alert('ok');
 
</script>
</body>
</html>

這個例子的結(jié)果是提示框先彈出,然后黑色邊框的p元素才出現(xiàn)在頁面中。原因很簡單,就一句話:

在JavaScript引擎運行腳本期間,界面渲染線程都是處于掛起狀態(tài)的。也就是說當(dāng)使用JavaScript對界面中的節(jié)點進行操作時,并不會立即體現(xiàn)出來,要等到JavaScript引擎線程空閑時,才會體現(xiàn)出來。

以上就是我對JavaScript定時機制的理解及總結(jié),如有錯誤,希望看到的大神指正。也希望大家多多支持腳本之家。

相關(guān)文章

  • javascript 內(nèi)置對象及常見API詳細介紹

    javascript 內(nèi)置對象及常見API詳細介紹

    這篇文章主要介紹了javascript 內(nèi)置對象及常見API的相關(guān)資料,這里對內(nèi)置對象進行了詳細的整理,需要的朋友可以參考下
    2016-11-11
  • 深入了解js原型模式

    深入了解js原型模式

    在js中,創(chuàng)建對象的方式有工廠模式和構(gòu)造函數(shù)模式等,但是,構(gòu)造函數(shù)中的每個方法都需要在實例對象中重新創(chuàng)建一遍,不能復(fù)用,就需要使用原型模式來創(chuàng)建對象。下面我們來了解一下吧
    2019-05-05
  • javascript中判斷一個值是否在數(shù)組中并沒有直接使用

    javascript中判斷一個值是否在數(shù)組中并沒有直接使用

    在JS中要判斷一個值是否在數(shù)組中并沒有函數(shù)直接使用,如PHP中就有in_array()這個函數(shù),可以寫一個類似in_array()函數(shù)功能的方法
    2012-12-12
  • caller和callee的區(qū)別介紹及演示結(jié)果

    caller和callee的區(qū)別介紹及演示結(jié)果

    caller返回一個函數(shù)的引用,這個函數(shù)調(diào)用了當(dāng)前的函數(shù);callee放回正在執(zhí)行的函數(shù)本身的引用,它是arguments的一個屬性,感興趣的你可以參考下或許可以幫助到你
    2013-03-03
  • 最新評論