瀏覽器環(huán)境概述

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

JavaScript代碼只有嵌入網(wǎng)頁(yè)洋腮,才能在用戶瀏覽網(wǎng)頁(yè)時(shí)運(yùn)行。

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

  • <script>標(biāo)簽:代碼嵌入網(wǎng)頁(yè)
  • <script>標(biāo)簽:加載外部腳本
  • 事件屬性:代碼寫入HTML元素的事件處理屬性,比如onclick或者onmouseover
  • URL協(xié)議:URL支持以javascript:協(xié)議的方式库糠,執(zhí)行JavaScript代碼

后兩種方法用得很少伙狐,常用的是前兩種方法。由于內(nèi)容(HTML代碼)和行為代碼(JavaScript)應(yīng)該分離曼玩,所以第一種方法應(yīng)當(dāng)謹(jǐn)慎使用鳞骤。

script標(biāo)簽:代碼嵌入網(wǎng)頁(yè)

通過(guò)<script>標(biāo)簽,可以直接將JavaScript代碼嵌入網(wǎng)頁(yè)黍判。

<script>
  console.log('Hello World');
</script>

<script>標(biāo)簽有一個(gè)type屬性豫尽,用來(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標(biāo)簽:加載外部腳本

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

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

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

<script charset="utf-8" src="example.js"></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屬性壶运,寫入該外部腳本的Hash簽名耕突,用來(lái)驗(yàn)證腳本的一致性凌摄。

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

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

事件屬性

某些HTML元素的事件屬性(比如onclickonmouseover)浪藻,可以寫入JavaScript代碼捐迫。當(dāng)指定事件發(fā)生時(shí),就會(huì)調(diào)用這些代碼爱葵。

<div onclick="alert('Hello')"></div>

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

URL協(xié)議

URL支持javascript:協(xié)議暇韧,調(diào)用這個(gè)URL時(shí),就會(huì)執(zhí)行JavaScript代碼浓瞪。

<a href="javascript:alert('Hello')"></a>

瀏覽器的地址欄也可以執(zhí)行javascipt:協(xié)議。將javascript:alert('Hello')放入地址欄巧婶,按回車鍵乾颁,就會(huì)跳出提示框。

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

<a href="javascript:new Date().toLocaleTimeString();">
  What time is it?
</a>

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

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

<a href="javascript:console.log(new Date().toLocaleTimeString())">
What time is it?
</a>

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

javascript:協(xié)議的常見用途是書簽?zāi)_本Bookmarklet俐载。由于瀏覽器的書簽保存的是一個(gè)網(wǎng)址蟹略,所以javascript:網(wǎng)址也可以保存在里面,用戶選擇這個(gè)書簽的時(shí)候遏佣,就會(huì)在當(dāng)前頁(yè)面執(zhí)行這個(gè)腳本挖炬。為了防止書簽替換掉當(dāng)前文檔,可以在腳本最后返回void 0状婶。

script標(biāo)簽

工作原理

瀏覽器加載JavaScript腳本茅茂,主要通過(guò)<script>標(biāo)簽完成捏萍。正常的網(wǎng)頁(yè)加載流程是這樣的。

  1. 瀏覽器一邊下載HTML網(wǎng)頁(yè)空闲,一邊開始解析
  2. 解析過(guò)程中令杈,發(fā)現(xiàn)<script>標(biāo)簽
  3. 暫停解析,網(wǎng)頁(yè)渲染的控制權(quán)轉(zhuǎn)交給JavaScript引擎
  4. 如果<script>標(biāo)簽引用了外部腳本碴倾,就下載該腳本逗噩,否則就直接執(zhí)行
  5. 執(zhí)行完畢,控制權(quán)交還渲染引擎跌榔,恢復(fù)往下解析HTML網(wǎng)頁(yè)

加載外部腳本時(shí)异雁,瀏覽器會(huì)暫停頁(yè)面渲染,等待腳本下載并執(zhí)行完成后僧须,再繼續(xù)渲染纲刀。原因是JavaScript可以修改DOM(比如使用document.write方法),所以必須把控制權(quán)讓給它担平,否則會(huì)導(dǎo)致復(fù)雜的線程競(jìng)賽的問(wèn)題示绊。

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

為了避免這種情況取胎,較好的做法是將<script>標(biāo)簽都放在頁(yè)面底部展哭,而不是頭部。這樣即使遇到腳本失去響應(yīng)闻蛀,網(wǎng)頁(yè)主體的渲染也已經(jīng)完成了匪傍,用戶至少可以看到內(nèi)容,而不是面對(duì)一張空白的頁(yè)面觉痛。

如果某些腳本代碼非常重要析恢,一定要放在頁(yè)面頭部的話,最好直接將代碼嵌入頁(yè)面秧饮,而不是連接外部腳本文件映挂,這樣能縮短加載時(shí)間。

將腳本文件都放在網(wǎng)頁(yè)尾部加載盗尸,還有一個(gè)好處柑船。在DOM結(jié)構(gòu)生成之前就調(diào)用DOM,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>

另一種解決方法是锐极,使用<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è)面底部栋猖,就可以完全按照正常的方式寫,上面兩種方式都不需要汪榔。

<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)順序決定,這是為了保證腳本之間的依賴關(guān)系不受到破壞枢舶。當(dāng)然懦胞,加載這兩個(gè)腳本都會(huì)產(chǎn)生“阻塞效應(yīng)”,必須等到它們都加載完成凉泄,瀏覽器才會(huì)繼續(xù)頁(yè)面渲染躏尉。

Gecko和Webkit引擎在網(wǎng)頁(yè)被阻塞后,會(huì)生成第二個(gè)線程解析文檔后众,下載外部資源胀糜,但是不會(huì)修改DOM,網(wǎng)頁(yè)還是處于阻塞狀態(tài)蒂誉。

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

此外轧抗,對(duì)于來(lái)自同一個(gè)域名的資源恩敌,比如腳本文件、樣式表文件鸦致、圖片文件等潮剪,瀏覽器一般最多同時(shí)下載六個(gè)(IE11允許同時(shí)下載13個(gè))。如果是來(lái)自不同域名的資源分唾,就沒(méi)有這個(gè)限制抗碰。所以,通常把靜態(tài)文件放在不同的域名之下绽乔,以加快下載速度弧蝇。

defer屬性

為了解決腳本文件下載阻塞網(wǎng)頁(yè)渲染的問(wèn)題,一個(gè)方法是加入defer屬性折砸。

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

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

defer的運(yùn)行流程如下两芳。

  1. 瀏覽器開始解析HTML網(wǎng)頁(yè)
  2. 解析過(guò)程中,發(fā)現(xiàn)帶有defer屬性的script標(biāo)簽
  3. 瀏覽器繼續(xù)往下解析HTML網(wǎng)頁(yè)去枷,同時(shí)并行下載script標(biāo)簽中的外部腳本
  4. 瀏覽器完成解析HTML網(wǎng)頁(yè)怖辆,此時(shí)再執(zhí)行下載的腳本

有了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è)方法是加入async屬性啥繁。

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

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

  1. 瀏覽器開始解析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è)愁茁,開始執(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)有依賴關(guān)系,就使用async屬性疾棵,如果腳本之間有依賴關(guān)系戈钢,就使用defer屬性。如果同時(shí)使用asyncdefer屬性是尔,后者不起作用殉了,瀏覽器行為由async屬性決定。

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

除了靜態(tài)的script標(biāo)簽拟枚,還可以動(dòng)態(tài)生成script標(biāo)簽薪铜,然后加入頁(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í)行。

我們可以把上面的寫法劈猪,封裝成一個(gè)函數(shù)昧甘。

(function() {
  var scripts = document.getElementsByTagName('script')[0];
  function load(url) {
    var script = document.createElement('script');
    script.async = true;
    script.src = url;
    scripts.parentNode.insertBefore(script, scripts);
  }
  load('//apis.google.com/js/plusone.js');
  load('//platform.twitter.com/widgets.js');
  load('//s.thirdpartywidget.com/widget.js');
}());

上面代碼中,async屬性設(shè)為true战得,是因?yàn)榧虞d的腳本沒(méi)有互相依賴關(guān)系充边。而且,這樣就不會(huì)造成堵塞常侦。

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

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);
}

此外聋亡,動(dòng)態(tài)嵌入還有一個(gè)地方需要注意肘习。動(dòng)態(tài)嵌入必須等待CSS文件加載完成后,才會(huì)去下載外部腳本文件杀捻。靜態(tài)加載就不存在這個(gè)問(wèn)題井厌,script標(biāo)簽指定的外部腳本文件,都是與CSS文件同時(shí)并發(fā)下載的致讥。

加載使用的協(xié)議

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

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

上面的example.js默認(rèn)就是采用HTTP協(xié)議下載垢袱,如果要采用HTTPS協(xié)議下載墓拜,必需寫明(假定服務(wù)器支持)。

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

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

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

瀏覽器的組成

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

渲染引擎

渲染引擎的主要作用是涌韩,將網(wǎng)頁(yè)代碼渲染為用戶視覺(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合成一棵渲染樹(render tree)
  3. 布局:計(jì)算出渲染樹的布局(layout)
  4. 繪制:將渲染樹繪制到屏幕

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

重流和重繪

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

頁(yè)面生成以后庄涡,腳本操作和樣式表操作量承,都會(huì)觸發(fā)重流(reflow)和重繪(repaint)。用戶的互動(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)的子樹上面英遭,最小化所耗費(fèi)的代價(jià),而不會(huì)全局重新生成網(wǎng)頁(yè)亦渗。

作為開發(fā)者挖诸,應(yīng)該盡量設(shè)法降低重繪的次數(shù)和成本。比如法精,盡量不要變動(dòng)高層的DOM元素多律,而以底層DOM元素的變動(dòng)代替;再比如亿虽,重繪table布局和flex布局菱涤,開銷都會(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或者寫入DOM攻走,盡量寫在一起,不要混雜
  • 緩存DOM信息
  • 不要一項(xiàng)一項(xiàng)地改變樣式此再,而是使用CSS class一次性改變樣式
  • 使用document fragment操作DOM
  • 動(dòng)畫時(shí)使用absolute定位或fixed定位昔搂,這樣可以減少對(duì)其他元素的影響
  • 只在必要時(shí)才顯示元素
  • 使用window.requestAnimationFrame(),因?yàn)樗梢园汛a推遲到下一次重流時(shí)執(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);

JavaScript引擎

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

JavaScript是一種解釋型語(yǔ)言,也就是說(shuō)猴抹,它不需要編譯带族,由解釋器實(shí)時(shí)運(yùn)行。這樣的好處是運(yùn)行和修改都比較方便蟀给,刷新頁(yè)面就可以重新解釋蝙砌;缺點(diǎn)是每次運(yùn)行都要調(diào)用解釋器,系統(tǒng)開銷較大跋理,運(yùn)行速度慢于編譯型語(yǔ)言择克。

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

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

  1. 讀取代碼道偷,進(jìn)行詞法分析(Lexical analysis),將代碼分解成詞元(token)记劈。
  2. 對(duì)詞元進(jìn)行語(yǔ)法分析(parsing)勺鸦,將代碼整理成“語(yǔ)法樹”(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懈息,縮寫JIT),即字節(jié)碼只在運(yùn)行時(shí)編譯摹恰,用到哪一行就編譯哪一行辫继,并且把編譯結(jié)果緩存(inline cache)怒见。通常,一個(gè)程序被經(jīng)常用到的姑宽,只是其中一小部分代碼遣耍,有了緩存的編譯結(jié)果,整個(gè)程序的運(yùn)行速度就會(huì)顯著提升炮车。不同的瀏覽器有不同的編譯策略舵变。有的瀏覽器只編譯最經(jīng)常用到的部分,比如循環(huán)的部分瘦穆;有的瀏覽器索性省略了字節(jié)碼的翻譯步驟棋傍,直接編譯成機(jī)器碼,比如chrome瀏覽器的V8引擎难审。

字節(jié)碼不能直接運(yùn)行瘫拣,而是運(yùn)行在一個(gè)虛擬機(jī)(Virtual Machine)之上,一般也把虛擬機(jī)稱為JavaScript引擎告喊。因?yàn)镴avaScript運(yùn)行時(shí)未必有字節(jié)碼麸拄,所以JavaScript虛擬機(jī)并不完全基于字節(jié)碼,而是部分基于源碼黔姜,即只要有可能拢切,就通過(guò)JIT(just in time)編譯器直接把源碼編譯成機(jī)器碼運(yùn)行,省略字節(jié)碼步驟秆吵。這一點(diǎn)與其他采用虛擬機(jī)(比如Java)的語(yǔ)言不盡相同淮椰。這樣做的目的,是為了盡可能地優(yōu)化代碼纳寂、提高性能主穗。下面是目前最常見的一些JavaScript虛擬機(jī):

參考鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毙芜,一起剝皮案震驚了整個(gè)濱河市忽媒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腋粥,老刑警劉巖晦雨,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異隘冲,居然都是意外死亡闹瞧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門展辞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)奥邮,“玉大人,你說(shuō)我怎么就攤上這事纵竖∧眨” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵靡砌,是天一觀的道長(zhǎng)已脓。 經(jīng)常有香客問(wèn)我,道長(zhǎng)通殃,這世上最難降的妖魔是什么度液? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮画舌,結(jié)果婚禮上堕担,老公的妹妹穿的比我還像新娘。我一直安慰自己曲聂,他們只是感情好霹购,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著朋腋,像睡著了一般齐疙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旭咽,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天贞奋,我揣著相機(jī)與錄音,去河邊找鬼穷绵。 笑死轿塔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仲墨。 我是一名探鬼主播勾缭,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼目养!你這毒婦竟也來(lái)了漫拭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤混稽,失蹤者是張志新(化名)和其女友劉穎采驻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匈勋,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡礼旅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洽洁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痘系。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖饿自,靈堂內(nèi)的尸體忽然破棺而出汰翠,到底是詐尸還是另有隱情龄坪,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布复唤,位于F島的核電站健田,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏佛纫。R本人自食惡果不足惜妓局,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一呈宇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧存炮,春花似錦、人聲如沸蜈漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至衣形,卻和暖如春驼侠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谆吴。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笋熬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓胳螟,卻偏偏與公主長(zhǎng)得像筹吐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丘薛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 問(wèn)答題47 /72 常見瀏覽器兼容性問(wèn)題與解決方案? 參考答案 (1)瀏覽器兼容問(wèn)題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補(bǔ)...
    _Yfling閱讀 13,748評(píng)論 1 92
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,070評(píng)論 25 707
  • 98B期/137期/149期/161期/170B期 英德市可立克電子有限公司 【日精進(jìn)打卡第365天】 【知~學(xué)習(xí)...
    樂(lè)然閱讀 173評(píng)論 0 1
  • 工作的繁忙,避免不了的就出差滩报。但是關(guān)于出差播急,你喜歡嗎售睹? 剛?cè)肼殘?chǎng),坐在辦公室里昌妹,很羨慕那些能夠出差的同事,感覺(jué)出差...
    七光年28閱讀 769評(píng)論 0 0
  • 最初選擇這本書是因?yàn)樗脑u(píng)價(jià)極高,多個(gè)公眾號(hào)都強(qiáng)烈推薦逢防,于是乎,我就入手了忘朝。 封皮上寫著“國(guó)內(nèi)第一部精神病人訪談手...
    扶搖而上九萬(wàn)里閱讀 252評(píng)論 0 0