JavaScript 瀏覽器模型概述

2023-03-20 15:46 更新

JavaScript 是瀏覽器的內(nèi)置腳本語(yǔ)言。也就是說(shuō),瀏覽器內(nèi)置了 JavaScript 引擎,并且提供各種接口,讓 JavaScript 腳本可以控制瀏覽器的各種功能。一旦網(wǎng)頁(yè)內(nèi)嵌了 JavaScript 腳本,瀏覽器加載網(wǎng)頁(yè),就會(huì)去執(zhí)行腳本,從而達(dá)到操作瀏覽器的目的,實(shí)現(xiàn)網(wǎng)頁(yè)的各種動(dòng)態(tài)效果。

本章開(kāi)始介紹瀏覽器提供的各種 JavaScript 接口。首先,介紹 JavaScript 代碼嵌入網(wǎng)頁(yè)的方法。

代碼嵌入網(wǎng)頁(yè)的方法 

網(wǎng)頁(yè)中嵌入 JavaScript 代碼,主要有四種方法。

  • <script>元素直接嵌入代碼。
  • <script>標(biāo)簽加載外部腳本
  • 事件屬性
  • URL 協(xié)議

script 元素嵌入代碼 

<script>元素內(nèi)部可以直接寫(xiě)入 JavaScript 代碼。

<script>
  var x = 1 + 5;
  console.log(x);
</script>

<script>標(biāo)簽有一個(gè)type屬性,用來(lái)指定腳本類(lèi)型。對(duì) JavaScript 腳本來(lái)說(shuō),type屬性可以設(shè)為兩種值。

  • text/javascript:這是默認(rèn)值,也是歷史上一貫設(shè)定的值。如果你省略type屬性,默認(rèn)就是這個(gè)值。對(duì)于老式瀏覽器,設(shè)為這個(gè)值比較好。
  • application/javascript:對(duì)于較新的瀏覽器,建議設(shè)為這個(gè)值。
<script type="application/javascript">
  console.log('Hello World');
</script>

由于<script>標(biāo)簽?zāi)J(rèn)就是 JavaScript 代碼。所以,嵌入 JavaScript 腳本時(shí),type屬性可以省略。

如果type屬性的值,瀏覽器不認(rèn)識(shí),那么它不會(huì)執(zhí)行其中的代碼。利用這一點(diǎn),可以在<script>標(biāo)簽之中嵌入任意的文本內(nèi)容,只要加上一個(gè)瀏覽器不認(rèn)識(shí)的type屬性即可。

<script id="mydata" type="x-custom-data">
  console.log('Hello World');
</script>

上面的代碼,瀏覽器不會(huì)執(zhí)行,也不會(huì)顯示它的內(nèi)容,因?yàn)椴徽J(rèn)識(shí)它的type屬性。但是,這個(gè)<script>節(jié)點(diǎn)依然存在于 DOM 之中,可以使用<script>節(jié)點(diǎn)的text屬性讀出它的內(nèi)容。

document.getElementById('mydata').text
//   console.log('Hello World');

script 元素加載外部腳本 

<script>標(biāo)簽也可以指定加載外部的腳本文件。

<script src="https://www.example.com/script.js" rel="external nofollow"  rel="external nofollow" ></script>

如果腳本文件使用了非英語(yǔ)字符,還應(yīng)該注明字符的編碼。

<script charset="utf-8" src="https://www.example.com/script.js" rel="external nofollow"  rel="external nofollow" ></script>

所加載的腳本必須是純的 JavaScript 代碼,不能有HTML代碼和<script>標(biāo)簽。

加載外部腳本和直接添加代碼塊,這兩種方法不能混用。下面代碼的console.log語(yǔ)句直接被忽略。

<script charset="utf-8" src="example.js">
  console.log('Hello World!');
</script>

為了防止攻擊者篡改外部腳本,script標(biāo)簽允許設(shè)置一個(gè)integrity屬性,寫(xiě)入該外部腳本的 Hash 簽名,用來(lái)驗(yàn)證腳本的一致性。

<script src="/assets/application.js"
  integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs=">
</script>

上面代碼中,script標(biāo)簽有一個(gè)integrity屬性,指定了外部腳本/assets/application.js的 SHA256 簽名。一旦有人改了這個(gè)腳本,導(dǎo)致 SHA256 簽名不匹配,瀏覽器就會(huì)拒絕加載。

事件屬性 

網(wǎng)頁(yè)元素的事件屬性(比如onclickonmouseover),可以寫(xiě)入 JavaScript 代碼。當(dāng)指定事件發(fā)生時(shí),就會(huì)調(diào)用這些代碼。

<button id="myBtn" onclick="console.log(this.id)">點(diǎn)擊</button>

上面的事件屬性代碼只有一個(gè)語(yǔ)句。如果有多個(gè)語(yǔ)句,使用分號(hào)分隔即可。

URL 協(xié)議 

URL 支持javascript:協(xié)議,即在 URL 的位置寫(xiě)入代碼,使用這個(gè) URL 的時(shí)候就會(huì)執(zhí)行 JavaScript 代碼。

<a href="javascript:console.log('Hello')">點(diǎn)擊</a>

瀏覽器的地址欄也可以執(zhí)行javascript:協(xié)議。將javascript:console.log('Hello')放入地址欄,按回車(chē)鍵也會(huì)執(zhí)行這段代碼。

如果 JavaScript 代碼返回一個(gè)字符串,瀏覽器就會(huì)新建一個(gè)文檔,展示這個(gè)字符串的內(nèi)容,原有文檔的內(nèi)容都會(huì)消失。

<a href="javascript: new Date().toLocaleTimeString();">點(diǎn)擊</a>

上面代碼中,用戶(hù)點(diǎn)擊鏈接以后,會(huì)打開(kāi)一個(gè)新文檔,里面有當(dāng)前時(shí)間。

如果返回的不是字符串,那么瀏覽器不會(huì)新建文檔,也不會(huì)跳轉(zhuǎn)。

<a href="javascript: console.log(new Date().toLocaleTimeString())">點(diǎn)擊</a>

上面代碼中,用戶(hù)點(diǎn)擊鏈接后,網(wǎng)頁(yè)不會(huì)跳轉(zhuǎn),只會(huì)在控制臺(tái)顯示當(dāng)前時(shí)間。

javascript:協(xié)議的常見(jiàn)用途是書(shū)簽?zāi)_本 Bookmarklet。由于瀏覽器的書(shū)簽保存的是一個(gè)網(wǎng)址,所以javascript:網(wǎng)址也可以保存在里面,用戶(hù)選擇這個(gè)書(shū)簽的時(shí)候,就會(huì)在當(dāng)前頁(yè)面執(zhí)行這個(gè)腳本。為了防止書(shū)簽替換掉當(dāng)前文檔,可以在腳本前加上void,或者在腳本最后加上void 0

<a href="javascript: void new Date().toLocaleTimeString();">點(diǎn)擊</a>
<a href="javascript: new Date().toLocaleTimeString();void 0;">點(diǎn)擊</a>

上面這兩種寫(xiě)法,點(diǎn)擊鏈接后,執(zhí)行代碼都不會(huì)網(wǎng)頁(yè)跳轉(zhuǎn)。

script 元素 

工作原理 

瀏覽器加載 JavaScript 腳本,主要通過(guò)<script>元素完成。正常的網(wǎng)頁(yè)加載流程是這樣的。

  1. 瀏覽器一邊下載 HTML 網(wǎng)頁(yè),一邊開(kāi)始解析。也就是說(shuō),不等到下載完,就開(kāi)始解析。
  2. 解析過(guò)程中,瀏覽器發(fā)現(xiàn)<script>元素,就暫停解析,把網(wǎng)頁(yè)渲染的控制權(quán)轉(zhuǎn)交給 JavaScript 引擎。
  3. 如果<script>元素引用了外部腳本,就下載該腳本再執(zhí)行,否則就直接執(zhí)行代碼。
  4. JavaScript 引擎執(zhí)行完畢,控制權(quán)交還渲染引擎,恢復(fù)往下解析 HTML 網(wǎng)頁(yè)。

加載外部腳本時(shí),瀏覽器會(huì)暫停頁(yè)面渲染,等待腳本下載并執(zhí)行完成后,再繼續(xù)渲染。原因是 JavaScript 代碼可以修改 DOM,所以必須把控制權(quán)讓給它,否則會(huì)導(dǎo)致復(fù)雜的線(xiàn)程競(jìng)賽的問(wèn)題。

如果外部腳本加載時(shí)間很長(zhǎng)(一直無(wú)法完成下載),那么瀏覽器就會(huì)一直等待腳本下載完成,造成網(wǎng)頁(yè)長(zhǎng)時(shí)間失去響應(yīng),瀏覽器就會(huì)呈現(xiàn)“假死”狀態(tài),這被稱(chēng)為“阻塞效應(yīng)”。

為了避免這種情況,較好的做法是將<script>標(biāo)簽都放在頁(yè)面底部,而不是頭部。這樣即使遇到腳本失去響應(yīng),網(wǎng)頁(yè)主體的渲染也已經(jīng)完成了,用戶(hù)至少可以看到內(nèi)容,而不是面對(duì)一張空白的頁(yè)面。如果某些腳本代碼非常重要,一定要放在頁(yè)面頭部的話(huà),最好直接將代碼寫(xiě)入頁(yè)面,而不是連接外部腳本文件,這樣能縮短加載時(shí)間。

腳本文件都放在網(wǎng)頁(yè)尾部加載,還有一個(gè)好處。因?yàn)樵?DOM 結(jié)構(gòu)生成之前就調(diào)用 DOM 節(jié)點(diǎn),JavaScript 會(huì)報(bào)錯(cuò),如果腳本都在網(wǎng)頁(yè)尾部加載,就不存在這個(gè)問(wèn)題,因?yàn)檫@時(shí) DOM 肯定已經(jīng)生成了。

<head>
  <script>
    console.log(document.body.innerHTML);
  </script>
</head>
<body>
</body>

上面代碼執(zhí)行時(shí)會(huì)報(bào)錯(cuò),因?yàn)榇藭r(shí)document.body元素還未生成。

一種解決方法是設(shè)定DOMContentLoaded事件的回調(diào)函數(shù)。

<head>
  <script>
    document.addEventListener(
      'DOMContentLoaded',
      function (event) {
        console.log(document.body.innerHTML);
      }
    );
  </script>
</head>

上面代碼中,指定DOMContentLoaded事件發(fā)生后,才開(kāi)始執(zhí)行相關(guān)代碼。DOMContentLoaded事件只有在 DOM 結(jié)構(gòu)生成之后才會(huì)觸發(fā)。

另一種解決方法是,使用<script>標(biāo)簽的onload屬性。當(dāng)<script>標(biāo)簽指定的外部腳本文件下載和解析完成,會(huì)觸發(fā)一個(gè)load事件,可以把所需執(zhí)行的代碼,放在這個(gè)事件的回調(diào)函數(shù)里面。

<script src="jquery.min.js" onload="console.log(document.body.innerHTML)">
</script>

但是,如果將腳本放在頁(yè)面底部,就可以完全按照正常的方式寫(xiě),上面兩種方式都不需要。

<body>
  <!-- 其他代碼  -->
  <script>
    console.log(document.body.innerHTML);
  </script>
</body>

如果有多個(gè)script標(biāo)簽,比如下面這樣。

<script src="a.js"></script>
<script src="b.js"></script>

瀏覽器會(huì)同時(shí)并行下載a.jsb.js,但是,執(zhí)行時(shí)會(huì)保證先執(zhí)行a.js,然后再執(zhí)行b.js,即使后者先下載完成,也是如此。也就是說(shuō),腳本的執(zhí)行順序由它們?cè)陧?yè)面中的出現(xiàn)順序決定,這是為了保證腳本之間的依賴(lài)關(guān)系不受到破壞。當(dāng)然,加載這兩個(gè)腳本都會(huì)產(chǎn)生“阻塞效應(yīng)”,必須等到它們都加載完成,瀏覽器才會(huì)繼續(xù)頁(yè)面渲染。

解析和執(zhí)行 CSS,也會(huì)產(chǎn)生阻塞。Firefox 瀏覽器會(huì)等到腳本前面的所有樣式表,都下載并解析完,再執(zhí)行腳本;Webkit則是一旦發(fā)現(xiàn)腳本引用了樣式,就會(huì)暫停執(zhí)行腳本,等到樣式表下載并解析完,再恢復(fù)執(zhí)行。

此外,對(duì)于來(lái)自同一個(gè)域名的資源,比如腳本文件、樣式表文件、圖片文件等,瀏覽器一般有限制,同時(shí)最多下載6~20個(gè)資源,即最多同時(shí)打開(kāi)的 TCP 連接有限制,這是為了防止對(duì)服務(wù)器造成太大壓力。如果是來(lái)自不同域名的資源,就沒(méi)有這個(gè)限制。所以,通常把靜態(tài)文件放在不同的域名之下,以加快下載速度。

defer 屬性 

為了解決腳本文件下載阻塞網(wǎng)頁(yè)渲染的問(wèn)題,一個(gè)方法是對(duì)<script>元素加入defer屬性。它的作用是延遲腳本的執(zhí)行,等到 DOM 加載生成后,再執(zhí)行腳本。

<script src="a.js" defer></script>
<script src="b.js" defer></script>

上面代碼中,只有等到 DOM 加載完成后,才會(huì)執(zhí)行a.jsb.js。

defer屬性的運(yùn)行流程如下。

  1. 瀏覽器開(kāi)始解析 HTML 網(wǎng)頁(yè)。
  2. 解析過(guò)程中,發(fā)現(xiàn)帶有defer屬性的<script>元素。
  3. 瀏覽器繼續(xù)往下解析 HTML 網(wǎng)頁(yè),同時(shí)并行下載<script>元素加載的外部腳本。
  4. 瀏覽器完成解析 HTML 網(wǎng)頁(yè),此時(shí)再回過(guò)頭執(zhí)行已經(jīng)下載完成的腳本。

有了defer屬性,瀏覽器下載腳本文件的時(shí)候,不會(huì)阻塞頁(yè)面渲染。下載的腳本文件在DOMContentLoaded事件觸發(fā)前執(zhí)行(即剛剛讀取完</html>標(biāo)簽),而且可以保證執(zhí)行順序就是它們?cè)陧?yè)面上出現(xiàn)的順序。

對(duì)于內(nèi)置而不是加載外部腳本的script標(biāo)簽,以及動(dòng)態(tài)生成的script標(biāo)簽,defer屬性不起作用。另外,使用defer加載的外部腳本不應(yīng)該使用document.write方法。

async 屬性 

解決“阻塞效應(yīng)”的另一個(gè)方法是對(duì)<script>元素加入async屬性。

<script src="a.js" async></script>
<script src="b.js" async></script>

async屬性的作用是,使用另一個(gè)進(jìn)程下載腳本,下載時(shí)不會(huì)阻塞渲染。

  1. 瀏覽器開(kāi)始解析 HTML 網(wǎng)頁(yè)。
  2. 解析過(guò)程中,發(fā)現(xiàn)帶有async屬性的script標(biāo)簽。
  3. 瀏覽器繼續(xù)往下解析 HTML 網(wǎng)頁(yè),同時(shí)并行下載<script>標(biāo)簽中的外部腳本。
  4. 腳本下載完成,瀏覽器暫停解析 HTML 網(wǎng)頁(yè),開(kāi)始執(zhí)行下載的腳本。
  5. 腳本執(zhí)行完畢,瀏覽器恢復(fù)解析 HTML 網(wǎng)頁(yè)。

async屬性可以保證腳本下載的同時(shí),瀏覽器繼續(xù)渲染。需要注意的是,一旦采用這個(gè)屬性,就無(wú)法保證腳本的執(zhí)行順序。哪個(gè)腳本先下載結(jié)束,就先執(zhí)行那個(gè)腳本。另外,使用async屬性的腳本文件里面的代碼,不應(yīng)該使用document.write方法。

defer屬性和async屬性到底應(yīng)該使用哪一個(gè)?

一般來(lái)說(shuō),如果腳本之間沒(méi)有依賴(lài)關(guān)系,就使用async屬性,如果腳本之間有依賴(lài)關(guān)系,就使用defer屬性。如果同時(shí)使用asyncdefer屬性,后者不起作用,瀏覽器行為由async屬性決定。

腳本的動(dòng)態(tài)加載 

<script>元素還可以動(dòng)態(tài)生成,生成后再插入頁(yè)面,從而實(shí)現(xiàn)腳本的動(dòng)態(tài)加載。

['a.js', 'b.js'].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  document.head.appendChild(script);
});

這種方法的好處是,動(dòng)態(tài)生成的script標(biāo)簽不會(huì)阻塞頁(yè)面渲染,也就不會(huì)造成瀏覽器假死。但是問(wèn)題在于,這種方法無(wú)法保證腳本的執(zhí)行順序,哪個(gè)腳本文件先下載完成,就先執(zhí)行哪個(gè)。

如果想避免這個(gè)問(wèn)題,可以設(shè)置async屬性為false。

['a.js', 'b.js'].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

上面的代碼不會(huì)阻塞頁(yè)面渲染,而且可以保證b.jsa.js后面執(zhí)行。不過(guò)需要注意的是,在這段代碼后面加載的腳本文件,會(huì)因此都等待b.js執(zhí)行完成后再執(zhí)行。

如果想為動(dòng)態(tài)加載的腳本指定回調(diào)函數(shù),可以使用下面的寫(xiě)法。

function loadScript(src, done) {
  var js = document.createElement('script');
  js.src = src;
  js.onload = function() {
    done();
  };
  js.onerror = function() {
    done(new Error('Failed to load script ' + src));
  };
  document.head.appendChild(js);
}

加載使用的協(xié)議 

如果不指定協(xié)議,瀏覽器默認(rèn)采用 HTTP 協(xié)議下載。

<script src="example.js"></script>

上面的example.js默認(rèn)就是采用 HTTP 協(xié)議下載,如果要采用 HTTPS 協(xié)議下載,必需寫(xiě)明。

<script src="https://example.js" rel="external nofollow" ></script>

但是有時(shí)我們會(huì)希望,根據(jù)頁(yè)面本身的協(xié)議來(lái)決定加載協(xié)議,這時(shí)可以采用下面的寫(xiě)法。

<script src="http://example.js" rel="external nofollow" ></script>

瀏覽器的組成 

瀏覽器的核心是兩部分:渲染引擎和 JavaScript 解釋器(又稱(chēng) JavaScript 引擎)。

渲染引擎 

渲染引擎的主要作用是,將網(wǎng)頁(yè)代碼渲染為用戶(hù)視覺(jué)可以感知的平面文檔。

不同的瀏覽器有不同的渲染引擎。

  • Firefox:Gecko 引擎
  • Safari:WebKit 引擎
  • Chrome:Blink 引擎
  • IE: Trident 引擎
  • Edge: EdgeHTML 引擎

渲染引擎處理網(wǎng)頁(yè),通常分成四個(gè)階段。

  1. 解析代碼:HTML 代碼解析為 DOM,CSS 代碼解析為 CSSOM(CSS Object Model)。
  2. 對(duì)象合成:將 DOM 和 CSSOM 合成一棵渲染樹(shù)(render tree)。
  3. 布局:計(jì)算出渲染樹(shù)的布局(layout)。
  4. 繪制:將渲染樹(shù)繪制到屏幕。

以上四步并非嚴(yán)格按順序執(zhí)行,往往第一步還沒(méi)完成,第二步和第三步就已經(jīng)開(kāi)始了。所以,會(huì)看到這種情況:網(wǎng)頁(yè)的 HTML 代碼還沒(méi)下載完,但瀏覽器已經(jīng)顯示出內(nèi)容了。

重流和重繪 

渲染樹(shù)轉(zhuǎn)換為網(wǎng)頁(yè)布局,稱(chēng)為“布局流”(flow);布局顯示到頁(yè)面的這個(gè)過(guò)程,稱(chēng)為“繪制”(paint)。它們都具有阻塞效應(yīng),并且會(huì)耗費(fèi)很多時(shí)間和計(jì)算資源。

頁(yè)面生成以后,腳本操作和樣式表操作,都會(huì)觸發(fā)“重流”(reflow)和“重繪”(repaint)。用戶(hù)的互動(dòng)也會(huì)觸發(fā)重流和重繪,比如設(shè)置了鼠標(biāo)懸停(a:hover)效果、頁(yè)面滾動(dòng)、在輸入框中輸入文本、改變窗口大小等等。

重流和重繪并不一定一起發(fā)生,重流必然導(dǎo)致重繪,重繪不一定需要重流。比如改變?cè)仡伾粫?huì)導(dǎo)致重繪,而不會(huì)導(dǎo)致重流;改變?cè)氐牟季郑瑒t會(huì)導(dǎo)致重繪和重流。

大多數(shù)情況下,瀏覽器會(huì)智能判斷,將重流和重繪只限制到相關(guān)的子樹(shù)上面,最小化所耗費(fèi)的代價(jià),而不會(huì)全局重新生成網(wǎng)頁(yè)。

作為開(kāi)發(fā)者,應(yīng)該盡量設(shè)法降低重繪的次數(shù)和成本。比如,盡量不要變動(dòng)高層的 DOM 元素,而以底層 DOM 元素的變動(dòng)代替;再比如,重繪table布局和flex布局,開(kāi)銷(xiāo)都會(huì)比較大。

var foo = document.getElementById('foobar');

foo.style.color = 'blue';
foo.style.marginTop = '30px';

上面的代碼只會(huì)導(dǎo)致一次重繪,因?yàn)闉g覽器會(huì)累積 DOM 變動(dòng),然后一次性執(zhí)行。

下面是一些優(yōu)化技巧。

  • 讀取 DOM 或者寫(xiě)入 DOM,盡量寫(xiě)在一起,不要混雜。不要讀取一個(gè) DOM 節(jié)點(diǎn),然后立刻寫(xiě)入,接著再讀取一個(gè) DOM 節(jié)點(diǎn)。
  • 緩存 DOM 信息。
  • 不要一項(xiàng)一項(xiàng)地改變樣式,而是使用 CSS class 一次性改變樣式。
  • 使用documentFragment操作 DOM
  • 動(dòng)畫(huà)使用absolute定位或fixed定位,這樣可以減少對(duì)其他元素的影響。
  • 只在必要時(shí)才顯示隱藏元素。
  • 使用window.requestAnimationFrame(),因?yàn)樗梢园汛a推遲到下一次重繪之前執(zhí)行,而不是立即要求頁(yè)面重繪。
  • 使用虛擬 DOM(virtual DOM)庫(kù)。

下面是一個(gè)window.requestAnimationFrame()對(duì)比效果的例子。

// 重流代價(jià)高
function doubleHeight(element) {
  var currentHeight = element.clientHeight;
  element.style.height = (currentHeight * 2) + 'px';
}

all_my_elements.forEach(doubleHeight);

// 重繪代價(jià)低
function doubleHeight(element) {
  var currentHeight = element.clientHeight;

  window.requestAnimationFrame(function () {
    element.style.height = (currentHeight * 2) + 'px';
  });
}

all_my_elements.forEach(doubleHeight);

上面的第一段代碼,每讀一次 DOM,就寫(xiě)入新的值,會(huì)造成不停的重排和重流。第二段代碼把所有的寫(xiě)操作,都累積在一起,從而 DOM 代碼變動(dòng)的代價(jià)就最小化了。

JavaScript 引擎 

JavaScript 引擎的主要作用是,讀取網(wǎng)頁(yè)中的 JavaScript 代碼,對(duì)其處理后運(yùn)行。

JavaScript 是一種解釋型語(yǔ)言,也就是說(shuō),它不需要編譯,由解釋器實(shí)時(shí)運(yùn)行。這樣的好處是運(yùn)行和修改都比較方便,刷新頁(yè)面就可以重新解釋?zhuān)蝗秉c(diǎn)是每次運(yùn)行都要調(diào)用解釋器,系統(tǒng)開(kāi)銷(xiāo)較大,運(yùn)行速度慢于編譯型語(yǔ)言。

為了提高運(yùn)行速度,目前的瀏覽器都將 JavaScript 進(jìn)行一定程度的編譯,生成類(lèi)似字節(jié)碼(bytecode)的中間代碼,以提高運(yùn)行速度。

早期,瀏覽器內(nèi)部對(duì) JavaScript 的處理過(guò)程如下:

  1. 讀取代碼,進(jìn)行詞法分析(Lexical analysis),將代碼分解成詞元(token)。
  2. 對(duì)詞元進(jìn)行語(yǔ)法分析(parsing),將代碼整理成“語(yǔ)法樹(shù)”(syntax tree)。
  3. 使用“翻譯器”(translator),將代碼轉(zhuǎn)為字節(jié)碼(bytecode)。
  4. 使用“字節(jié)碼解釋器”(bytecode interpreter),將字節(jié)碼轉(zhuǎn)為機(jī)器碼。

逐行解釋將字節(jié)碼轉(zhuǎn)為機(jī)器碼,是很低效的。為了提高運(yùn)行速度,現(xiàn)代瀏覽器改為采用“即時(shí)編譯”(Just In Time compiler,縮寫(xiě) JIT),即字節(jié)碼只在運(yùn)行時(shí)編譯,用到哪一行就編譯哪一行,并且把編譯結(jié)果緩存(inline cache)。通常,一個(gè)程序被經(jīng)常用到的,只是其中一小部分代碼,有了緩存的編譯結(jié)果,整個(gè)程序的運(yùn)行速度就會(huì)顯著提升。

字節(jié)碼不能直接運(yùn)行,而是運(yùn)行在一個(gè)虛擬機(jī)(Virtual Machine)之上,一般也把虛擬機(jī)稱(chēng)為 JavaScript 引擎。并非所有的 JavaScript 虛擬機(jī)運(yùn)行時(shí)都有字節(jié)碼,有的 JavaScript 虛擬機(jī)基于源碼,即只要有可能,就通過(guò) JIT(just in time)編譯器直接把源碼編譯成機(jī)器碼運(yùn)行,省略字節(jié)碼步驟。這一點(diǎn)與其他采用虛擬機(jī)(比如 Java)的語(yǔ)言不盡相同。這樣做的目的,是為了盡可能地優(yōu)化代碼、提高性能。下面是目前最常見(jiàn)的一些 JavaScript 虛擬機(jī):

參考鏈接 


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)