JavaScript學習筆記 ——HTML界面解析順序、JavaScript定時機制

    之前學習的JavaScript知識總體來說還是太淺了,一些代碼甚至看不太懂,更別提去寫出質量高的JavaScript代碼。這次的筆記主要是根據《JavaScript高級程序設計》和《JavaScript Dom編程藝術》中的知識來闡述最近學到的知識和自己的見解。


    第一章首先講述的是JavaScript的發展,在《JavaScript高級程序設計》第三版出版時還沒有誕生ES6的語法,現在ES6卻已經是使用非常普遍。

    文檔對象模型(DOM)將 HTML 文檔表達爲樹結構,每一個頁面的組成部分都是一個DOM節點,開發者通過DOM可以訪問甚至是操作HTML 元素的對象和屬性。


書上有這樣一段話:

包含在元素內部的JavaScript代碼將從上至下依次解釋…解釋器會解釋一個函數的定義,然後將該定義保存在自己的環境中。在解釋器對<script>元素內部的所有代碼求值完畢之前,頁面中其餘內容都不會被瀏覽器加載或顯示。

先看看代碼:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>1</title>
  </head>
  <script>
    let changeToRed = function() {
      console.log("33333333");
      document.getElementById("red").style.color = "red";
    };

    let changeToGreen = function() {
      console.log("44444444");
      document.getElementById("green").style.color = "green";
    };
    console.log("1111111");
    setTimeout(changeToRed, 5000);
    setTimeout(changeToGreen,3000)
    console.log("222222");
  </script>
  <body>
    <p>這是正常文字</p>
    <p id="red">這是紅色的文字</p>
    <p id="green">這是綠色的文字</p>
    <p>這是最後文字</p>
  </body>
</html>

上面的代碼,我對它預期的執行順序是:

界面: 這是正常文字(黑色) --> 這是紅色的文字(先是黑色,5秒後變紅色) --> 這是綠色的文字(先是黑色,再過3秒後變綠色)–> 這是最後文字

控制檯:1111111 --> 33333333 --> 44444444 --> 22222222

實際上:

界面: 這是正常文字(黑色) , 這是紅色的文字(黑色) 、 這是綠色的文字(黑色)、這是最後文字(黑色)

–> 這是綠色的文字 約3秒後變綠色 --> 這是紅色的文字再過約2秒後變紅色;

控制檯:1111111 、2222222 --(約3秒後)–> 44444444 --(約2秒後)–> 3333333

可能是我對前面那段話還有JavaScript的定時機制和頁面解析順序有點誤解。

帶着問題繼續探索。


爲了進行對比驗證,不妨再運行如下代碼:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>2</title>
    <script type="text/javascript">
      let changeToRed = function() {
      console.log("33333333");
      document.getElementById("red").style.color = "red";
    };

    let changeToGreen = function() {
      console.log("44444444");
      document.getElementById("green").style.color = "green";
    };
    setTimeout(changeToRed, 5000);
    console.log("55555555");
    </script>
  </head>
  <script>
    console.log("1111111");
    setTimeout(changeToGreen,3000)
    console.log("222222");
  </script>
  <body>
    <p>這是正常文字</p>
    <p id="red">這是紅色的文字</p>
    <p id="green">這是綠色的文字</p>
    <p>這是最後文字</p>
  </body>
</html>

這段代碼的運行結果爲:

界面: 這是正常文字(黑色) , 這是紅色的文字(黑色) 、 這是綠色的文字(黑色)、這是最後文字(黑色)

–> 這是綠色的文字3秒後變綠色 --> 這是紅色的文字再過約2秒後變紅色;

控制檯:55555555、1111111 、2222222 --(約3秒後)–> 44444444 --(約2秒後)–> 3333333

這個時候我們可以明確的是,對於<html>標籤內的代碼,會從上到下解析。對於<script>標籤內的代碼,也是按順序從上到下進行解釋。那麼頁面渲染和JavaScript代碼的執行的順序又是怎麼樣的呢?

再看下面的代碼:

<!DOCTYPE html>
<html lang="en">
  <head onload="console.log('66666666');">
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>3</title>
  </head>
  <script>
    let changeToRed = function() {
      console.log("33333333");
      document.getElementById("red").style.color = "red";
    };
    let changeToGreen = function() {
      console.log("44444444");
      document.getElementById("green").style.color = "green";
    };
    console.log("1111111");
    setTimeout(changeToGreen,3000)
    console.log("222222");   
    setTimeout(changeToRed, 5000);
    console.log("55555555");
  </script>
  <body onload="console.log('77777777');">
    <p>這是正常文字</p>
    <p id="red">這是紅色的文字</p>
    <p id="green">這是綠色的文字</p>
    <p>這是最後文字</p>
  </body>
</html>

這段代碼的運行結果爲:

界面: 這是正常文字(黑色) , 這是紅色的文字(黑色) 、 這是綠色的文字(黑色)、這是最後文字(黑色)

–> 這是綠色的文字3秒後變綠色 --> 這是紅色的文字再過約2秒後變紅色;

控制檯:1111111 、2222222 、55555555、77777777 --(約3秒後)–> 44444444 --(約2秒後)–> 3333333

可以看到,JavaScript代碼中直接輸出在控制檯的語句總是最先執行。你也可能會說:“當然啊!因爲你的<script> 放在了<body> 前面呀!…”

我測試了一下,把<script> 代碼換到<body> 標籤之後,或者是<body> 標籤內部,甚至是<head> ,結果都是一樣的,所以,瀏覽器加載界面時,總是會先去解釋<script> 中的代碼。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>4</title>
  </head>
    <body onload="let temp = '22222222'">
  </body>
  <script>
      console.log(temp);
    </script>
</html> 

運行:Uncaught ReferenceError: temp is not defined at 4.html:12

上面這兩個例子則更直接地證實了之前的結論。

    但是,對於之前那一段代碼,爲什麼同樣在<script> 中輸出到控制檯的33333333和44444444卻在加載<body> 之後才執行呢?爲什麼執行完變綠的樣式之後,只隔了2秒而不是5秒就執行變紅樣式了呢?

不妨先來看看JavaScript的定時器機制。


JavaScript定時器與執行

JavaScript可以通過setTimeout()來實現定時操作。

setTimeout(code,millisec,lang):在指定的毫秒數之後執行函數或計算表達式。

參數 描述
code 必需。等待執行的 JavaScript 代碼。
millisec 必需。在執行代碼前需等待的毫秒數。
lang 可選。腳本語言可以是:JScript /VBScript/ JavaScript。

    JavaScript的定時器不是一個線程,因爲JavaScript是運行於單線程環境中的,而定時器只是計劃代碼在未來的某個時間執行。可以這麼理解:瀏覽器負責進行排序,指派某段代碼在某個時間點應該按照什麼樣的順序執行。對於指定的時間間隔,它表示的是何時將定時器的代碼添加到執行隊列,而不是何時執行代碼。

    在其他地方看到了很多解釋關於瀏覽器進程的一些理解,其中包括瀏覽器進程是多線程,包括了GUI渲染線程、JS引擎線程、事件觸發線程、定時觸發器線程、異步http請求線程等主要線程。但我更傾向於《JavaScript高級程序設計》這本書中的說法(或許是比較權威吧)。

    再結合前面的代碼,可以這麼理解:瀏覽器首先把解析JavaScript中的代碼放到任務列表,另外,渲染界面(即解析`` 等其中的代碼)也被放入隊列。隊列中的任務都要等待JavaScript進程空閒之後才能執行,並且是按照順序執行。 首先是解析JavaScript代碼,當解析到setTimeout(changeToGreen,3000); 這一行時,本應等待3秒之後執行這行代碼,但是JavaScript進程這個時候是空閒狀態,因此會繼續向下執行解析,解析完之後,如果還是空閒,就執行完全部的JavaScript代碼,並且開始渲染頁面。而在3秒鐘之後,將這個任務放入任務隊列。當解析到setTimeout(changeToRed, 5000); 也一樣,在5秒後將這個任務放入隊列。解析JavaScript代碼和渲染頁面的速度很快,所以幾乎是從加載HTML頁面開始,第3秒後放入任務隊列,此時任務隊列早已爲空,JavaScript進程會立刻執行這一任務。


總結

  • 對於<html>標籤內的代碼,會從上到下解析。

    首先是<header>標籤中的代碼,head標籤中可能會包含對外部文件的引用,從開始運行就會下載這些被引用的外部文件。然後解析<body> 標籤中的代碼,如果此時``中引用的外部文件沒有下載完,將會繼續下載。

  • 對於<script>標籤內的代碼,其按照從上之下的順序解釋。`` 標籤可以出現在HTML頁面的任何位置。瀏覽器將控制權交給JavaScript的解釋器,如果<script> 標籤引用了外部腳本文件,就下載該腳本,否則就直接執行,執行完畢後將控制權返回,瀏覽器繼續渲染頁面。由於是單線程,在渲染界面的時候不會執行JS代碼,在執行JS代碼的時候也不會渲染界面。

    歡迎批評指正!

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