免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
從敲下一行JS代碼到這行代碼被執(zhí)行,中間發(fā)生了什么?
我們每天都在寫(xiě)JS,你是否想過(guò),計(jì)算機(jī)是怎么識(shí)別你的這一行代碼,并且執(zhí)行相應(yīng)指令?本篇文章為你講述從敲下一行JS代碼到這行代碼可以被執(zhí)行算出正確的結(jié)果,都經(jīng)歷了什么。

編譯

學(xué)過(guò)計(jì)算器基礎(chǔ)的,即使學(xué)的不好,大概都知道計(jì)算機(jī)跟人能讀懂的語(yǔ)言是不一樣的,它只認(rèn)識(shí)0101的二進(jìn)制數(shù)。
也就是機(jī)器指令碼(machine instruction code )。一開(kāi)始,人們都是用它來(lái)寫(xiě)程序,可以想到最早的程序員有多痛苦。這種二進(jìn)制碼不易被人類理解和記憶,估計(jì)出錯(cuò)太多,最后終于聰明的人類終于發(fā)明了適合自己學(xué)習(xí)記憶各種高級(jí)計(jì)算機(jī)語(yǔ)言,也包括JS。
但是機(jī)器并不能直接理解JS語(yǔ)言,所以這里就需要一個(gè)中介幫忙程序解釋并且將其編譯成機(jī)器指令碼給計(jì)算機(jī)執(zhí)行。這個(gè)過(guò)程就叫編譯。
而我們chrome瀏覽器里的V8引擎就是幫我們做這個(gè)事情的中介。但是并不是只有g(shù)oogle一家在做瀏覽器啊,所以市面上還有很多JS引擎。下面是從網(wǎng)上趴的圖:
而另前端痛苦不堪的瀏覽器兼容問(wèn)題,就是因?yàn)槭褂玫腏S引擎不同,所以能夠理解的JS語(yǔ)法不同,我們就需要寫(xiě)好幾種兼容語(yǔ)法。
所以終極解決兼容問(wèn)題的方法就是:全部瀏覽器都用一種JS引擎,目前v8大有一統(tǒng)天下的趨勢(shì),不過(guò)這個(gè)東西最終能不能實(shí)現(xiàn)今天就不討論了。

編譯原理

無(wú)論是哪種編譯器,原理都差不多。所以我們直接來(lái)看看編譯原理,就知道V8大概是如何工作的了。
編譯一般分為三個(gè)步驟:
  • 詞法分析(laxical Analysis)

詞法分析的意思就是,將代碼塊切分成最小的單位。這些最小單位成為token。比如 var a = 2;可以切分成var,a,=,2
  • 語(yǔ)法分析(Syntatic Analysis)

將詞法單元轉(zhuǎn)換成一個(gè)有層級(jí),代表程序語(yǔ)法結(jié)構(gòu)的樹(shù),這就是我們經(jīng)常說(shuō)的AST,抽象語(yǔ)法樹(shù)。
注意:詞法分析跟語(yǔ)法不是完全獨(dú)立的,而是交錯(cuò)運(yùn)行的。也就是說(shuō),并不是等所有的token都生成之后,才用語(yǔ)法分析器來(lái)處理。一般都是每取得一個(gè)token,就開(kāi)始用語(yǔ)法分析器來(lái)處理了。
下面我們來(lái)看看一個(gè)add函數(shù)會(huì)生成怎樣的語(yǔ)法樹(shù):
  1. function add (a, b) {

  2. return a + b

  3. }


生成的樹(shù)太長(zhǎng)了,截圖不完整,可以在AST Exploer看到最終的AST。
可以看到這就是這段函數(shù)的樹(shù)形展示,如果你沒(méi)看懂,可以看這篇文章。這里就不具體解釋每個(gè)FunctionDeclaration Identifier BlockStatement的意思了。
AST可是所有編譯器以及轉(zhuǎn)換器的基礎(chǔ)核心,我們常用的babel轉(zhuǎn)碼過(guò)程就是先將ES6的代碼編成AST,然后轉(zhuǎn)換成ES5的AST,最后由這個(gè)AST還原出ES5代碼。有興趣的可以看這篇文章,這篇文章是將LISP-style代碼的轉(zhuǎn)成C-style代碼,不過(guò)原理都一樣。
可以說(shuō)基于AST,你可以隨意玩轉(zhuǎn)各種編程語(yǔ)言的相互轉(zhuǎn)換。
構(gòu)建語(yǔ)法樹(shù),還有一層作用,就是發(fā)現(xiàn)語(yǔ)法錯(cuò)誤。當(dāng)JS解析器發(fā)現(xiàn)無(wú)法構(gòu)造這個(gè)抽象語(yǔ)法樹(shù)的時(shí)候,就會(huì)報(bào)語(yǔ)法錯(cuò)誤,并結(jié)束整個(gè)代碼塊的解析。而對(duì)于一些強(qiáng)類型語(yǔ)言(也就是一開(kāi)始就要定義這個(gè)變量是什么類型,后面都不能改變),在構(gòu)建出語(yǔ)法樹(shù)之后,還會(huì)有類型檢查。但是對(duì)于JS這種弱類型語(yǔ)言,就沒(méi)有這一步。當(dāng)然TypeScipt為我們提供了類型檢查,并且可以將我們的typeScript代碼編譯成JS。
  • 代碼生成(Code Genaration)

最后一步就是將AST轉(zhuǎn)成計(jì)算機(jī)可以識(shí)別的機(jī)器指令碼。
V8引擎的編譯過(guò)程基本就是上面這個(gè)過(guò)程,但是它多了一步生成字節(jié)碼的過(guò)程。首先用解析器生成AST,然后用解釋器Ignition根據(jù)語(yǔ)法樹(shù)生成字節(jié)碼,最后再用TurboFan將字節(jié)碼生成機(jī)器指令碼
為什么要先轉(zhuǎn)成字節(jié)碼,是因?yàn)橹苯由蓹C(jī)器指令碼太占內(nèi)存了。
整個(gè)過(guò)程就是這么簡(jiǎn)單了。

V8 為什么那么快

JS的編譯過(guò)程發(fā)生在執(zhí)行前的那段時(shí)間,所以對(duì)JS引擎的性能要求特別高,因?yàn)檫@直接影響到頁(yè)面加載的速度。
那么V8是如何做到的呢?
1、腳本流(script streaming)
以前的chrome里,網(wǎng)絡(luò)拿到數(shù)據(jù)之后,必須經(jīng)過(guò)chrome主線程轉(zhuǎn)發(fā)到流解析器。但是,當(dāng)網(wǎng)絡(luò)數(shù)據(jù)到達(dá)之后,主線程有可能被其他事情占住,比如HTML解析,布局,其他JS執(zhí)行。這樣這些數(shù)據(jù)就沒(méi)辦法被即使解析。
從Chrome 75開(kāi)始,V8可以將腳本直接從網(wǎng)絡(luò)流傳輸?shù)搅鹘馕銎髦校鵁o(wú)需等待chrome主線程。
這意味著腳本一旦開(kāi)始加載,V8就會(huì)在單獨(dú)的線程上解析。這樣下載腳本完成后幾乎立即完成解析,從而縮短頁(yè)面加載時(shí)間。
2、字節(jié)碼緩存
首次訪問(wèn)頁(yè)面的時(shí)候,JS代碼會(huì)被編譯成字節(jié)碼。當(dāng)再次訪問(wèn)同一個(gè)頁(yè)面的時(shí)候,會(huì)直接復(fù)用首次解析出來(lái)的字節(jié)碼。
這樣就省去了下載,解析,編譯的步驟??梢允筩hrome節(jié)省大約40%的時(shí)間。
3、內(nèi)聯(lián)
如果一個(gè)函數(shù)內(nèi)部調(diào)用其他函數(shù),那么編譯器會(huì)直接函數(shù)中將要執(zhí)行的內(nèi)容放到主函數(shù)里。
  1. function add(a, b) {

  2. return a + b;

  3. }

  4. function calculateTwoPlusFive() {

  5. var sum;

  6. for(var i = 0; i <= 1000000000; i++) {

  7. sum =add(2+5);

  8. }

  9. }

  10. var start = newDate();

  11. calculateTwoPlusFive();

  12. var end = newDate();

  13. var timeTaken = end.valueOf() - start.valueOf();

  14. console.log('Took '+ timeTaken + 'ms');

內(nèi)聯(lián)屬性會(huì)將這個(gè)代碼編譯成
  1. function add(a, b) {

  2. return a + b;

  3. }

  4. function calculateTwoPlusFive() {

  5. var sum;

  6. for(var i=0;i<=1000000000;i++){

  7. sum = 2+ 5;

  8. }

  9. }

  10. var start = newDate();

  11. calculateTwoPlusFive();

  12. var end = newDate();

  13. var timeTaken = end.valueOf() - start.valueOf();

  14. console.log('Took '+ timeTaken + 'ms');

我把這段代碼放在safari上跑需要1454ms,而chrome只需要453ms,基本只有三分之一。
4、隱藏類
對(duì)于C++/Java,訪問(wèn)指令可以在編譯階段生成。
因?yàn)樗鼈兊拿恳粋€(gè)變量都有指定的類型。所以一個(gè)對(duì)象包含什么成員,這些成員是什么類型,在對(duì)象中的偏移量都可以在編譯階段就確定了。那么在CPU執(zhí)行的時(shí)候就輕松了,要訪問(wèn)這個(gè)對(duì)象中的某個(gè)變量的時(shí)候,直接用對(duì)象的首地址加偏移量就可以訪問(wèn)到。
但是JS是動(dòng)態(tài)語(yǔ)言,運(yùn)行的時(shí)候不僅可以隨意換類型,還可以動(dòng)態(tài)添加刪除屬性。所以訪問(wèn)對(duì)象屬性完全得運(yùn)行的時(shí)候才能決定。
如果JS引擎每次都需要進(jìn)行動(dòng)態(tài)查詢,會(huì)造成大量的性能損耗。所以V8引入了隱藏類機(jī)制。在初始化對(duì)象時(shí)候,會(huì)給他創(chuàng)建一個(gè)隱藏類,而后增刪屬性都會(huì)在創(chuàng)建一個(gè)隱藏類或者查找之前已經(jīng)創(chuàng)建好的類。
那么這些類里的成員對(duì)于這個(gè)類來(lái)說(shuō)就是固定的。所以他們的偏移量對(duì)于這個(gè)類來(lái)說(shuō)也是固定的,那么在后續(xù)再次調(diào)用的時(shí)候就能很快的定位到他的位置。
  1. functionPerson(name, age) {

  2. this.name = name;

  3. this.age = age;

  4. }

  5. var daisy = newPerson('daisy', 22);

  6. var alice = newPerson('alice', 20);

  7. daisy.email = 'daisy@qq.com';

  8. daisy.job = 'engineer';

  9. alice.job = 'engineer';

  10. alice.email = 'alice@qq.com';

對(duì)于這段代碼,它的隱藏類的生成過(guò)程如下:
首先兩個(gè)new Person()的時(shí)候,生成的隱藏類為C0,因?yàn)榇藭r(shí)沒(méi)有任何屬性。當(dāng)執(zhí)行this.name = name;的時(shí)候多了一個(gè)屬性,于是又生成了C1。后面同理,到C2生成的時(shí)候,daisy跟alice的隱藏類都是一樣的,就是C2,此時(shí)有兩個(gè)屬性。
但是后面由于動(dòng)態(tài)添加屬性的順序不同,就造成了屬性在類中的偏移量不同,也會(huì)生成不同的隱藏類。這樣就沒(méi)辦法共享隱藏類,導(dǎo)致浪費(fèi)資源生成新的隱藏類。
所以我們動(dòng)態(tài)賦值的時(shí)候,盡量保證順序也是一致的。
5、熱點(diǎn)函數(shù)會(huì)被直接編譯成機(jī)器碼
v8在運(yùn)行的時(shí)候,會(huì)采集JS代碼運(yùn)行數(shù)據(jù)。當(dāng)發(fā)現(xiàn)某個(gè)函數(shù)被頻繁調(diào)用,那么就會(huì)將它標(biāo)記成熱點(diǎn)函數(shù),并且認(rèn)為他是一個(gè)類型穩(wěn)定的函數(shù)。這時(shí)候會(huì)將它生成更為高效的機(jī)器碼。
但是在后面的運(yùn)行中,萬(wàn)一類型發(fā)生變化,V8又要回退到字節(jié)碼。
比如:
  1. function add(a, b){

  2. return a + b

  3. }

  4. // 這里使用add的時(shí)候一直傳入number類型

  5. for(var i=0; i<10000; ++i){

  6. add(i, i);

  7. }

  8. // 最后卻傳了string,會(huì)使得性能受損

  9. add('a', 'b');

同理,下面兩段代碼可以猜猜誰(shuí)的執(zhí)行效率高?
  1. // 片段 1

  2. var person = {

  3. add: function(a, b){

  4. return a + b;

  5. }

  6. };

  7. obj.name = 'li';

  8. // 片段 2

  9. var person = {

  10. add: function(a, b){

  11. return a + b;

  12. }

  13. name: 'li'

  14. };

答案是2。結(jié)合前面知識(shí),我們可以知道,方法一中動(dòng)態(tài)添加屬性會(huì)生成一個(gè)新的隱藏類。如果add函數(shù)此時(shí)已經(jīng)被轉(zhuǎn)成機(jī)器碼,那么對(duì)于方法一來(lái)說(shuō),就沒(méi)辦法復(fù)用了。因?yàn)轭惗际切碌牧恕?/span>
所以函數(shù)內(nèi)部參數(shù)類型越穩(wěn)定,V8的效率越高。

總結(jié)一下

從敲下一段JS代碼到它最終被計(jì)算機(jī)理解并執(zhí)行,中間經(jīng)歷了詞法分析,語(yǔ)法分析,生成機(jī)器碼,執(zhí)行機(jī)器碼。
當(dāng)然這個(gè)編譯的過(guò)程是很復(fù)雜的,尤其js還是動(dòng)態(tài)語(yǔ)言,對(duì)于js引擎的性能要求就很高了。V8做了很多事情來(lái)提升瀏覽器的性能,其中包括但不限于:
  • 腳本流

下載的同時(shí)就已經(jīng)在解析,節(jié)省時(shí)間
  • 字節(jié)碼緩存

訪問(wèn)同一個(gè)頁(yè)面的時(shí)候直接復(fù)用之前的字節(jié)碼,不在重新編譯生成
  • 內(nèi)聯(lián)

將主函數(shù)中調(diào)用的函數(shù),直接換成將要執(zhí)行的語(yǔ)句
  • 隱藏類

通過(guò)隱藏類快速定位到動(dòng)態(tài)加入的屬性
注意:動(dòng)態(tài)加入的屬性順序不一樣,會(huì)造成生成不同的隱藏類,我們動(dòng)態(tài)賦值同一個(gè)構(gòu)造函數(shù)對(duì)象的時(shí)候,盡量保證順序也是一致的。
  • 熱點(diǎn)函數(shù)編譯成機(jī)器碼

將常用的函數(shù)直接一步到位編成機(jī)器碼。
注意:常用的函數(shù)傳入的類型保持固定。并且對(duì)象的屬性越穩(wěn)定,越有利于性能。

參考文檔

https://juejin.im/post/5ada727c518825670b33a584 https://the-super-tiny-compiler.glitch.me/intro https://blog.csdn.net/weixin34184561/article/details/87999100 https://segmentfault.com/a/1190000016231512#comment-area https://blog.csdn.net/weixin34184561/article/details/87999100 
https://s0v80dev.icopy.site/blog/v8-release-78 https://blog.csdn.net/weixin_34184561/article/details/87999100 https://juejin.im/post/5c36fc33518825253b5e94e3

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
JS 編譯器都做了啥?
V8 Design Elements(翻譯)
V8 JS引擎
Web前端筆試115道題(帶答案及解析)
在safekodo在線將 js編譯為AST語(yǔ)法樹(shù)
14條最佳JS代碼編寫(xiě)技巧
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服