溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

JavaScript是怎么運(yùn)行的

發(fā)布時(shí)間:2021-11-20 11:57:59 來(lái)源:億速云 閱讀:120 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要講解了“JavaScript是怎么運(yùn)行的”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“JavaScript是怎么運(yùn)行的”吧!

什么是JavaScript?

我們來(lái)確認(rèn)一下JavaScript的定義:JavaScript 是一門(mén)解釋型的動(dòng)態(tài)語(yǔ)言。

解釋型語(yǔ)言是相對(duì)于編譯型語(yǔ)言存在的,源代碼不是直接編譯為目標(biāo)代碼,而是轉(zhuǎn)成中間代碼,再由解釋器對(duì)中間代碼進(jìn)行解釋運(yùn)行。

主流編程語(yǔ)言有編譯型(如 C++)、解釋型(如 JavaScript)、和半解釋半編譯(如 Java)這幾大類(lèi)型。

代碼是怎么運(yùn)行的?

首先我們來(lái)了解一下代碼是怎么運(yùn)行的。

我們知道,代碼是由CPU執(zhí)行的,而目前的CPU并不能直接執(zhí)行諸如if…else之類(lèi)的語(yǔ)句,它只能執(zhí)行二進(jìn)制指令。但是二進(jìn)制指令對(duì)人類(lèi)實(shí)在是太不友好了:我們很難快速準(zhǔn)確的判斷一個(gè)二進(jìn)制指令1000010010101001代表什么?所以科學(xué)家們發(fā)明匯編語(yǔ)言。

匯編語(yǔ)言

匯編語(yǔ)言實(shí)際上就是二進(jìn)制指令的助記符。

假設(shè)10101010代表讀取內(nèi)存操作,內(nèi)存地址是10101111,寄存器地址是11111010,那么完整的操作101010101010111111111010就代表讀取某個(gè)內(nèi)存地址的值并裝載到寄存器,而匯編語(yǔ)言并沒(méi)有改變這種操作方式,它只是二進(jìn)制指令的映射:

LD:10101010 id:10101111R:11111010

這樣上述指令就可以表達(dá)為LD id R ,大大增強(qiáng)了代碼的可讀性。

但是這樣還不夠友好,CPU只能執(zhí)行三地址表達(dá)式,和人的思考方式、語(yǔ)言模式相距甚遠(yuǎn)。所以偉大的科學(xué)家們又發(fā)明了高級(jí)語(yǔ)言。

高級(jí)語(yǔ)言

“代碼是寫(xiě)給人看的,不是寫(xiě)給機(jī)器看的,只是順便計(jì)算機(jī)可以執(zhí)行而已。”

高級(jí)語(yǔ)言之所以稱(chēng)之為“高級(jí)”,就是因?yàn)樗臃衔覀兊乃季S和閱讀習(xí)慣。if…else這種語(yǔ)句看起來(lái)要比1010101010舒服的多了。但是計(jì)算機(jī)并不能直接執(zhí)行高級(jí)語(yǔ)言,所以還需要把高級(jí)語(yǔ)言轉(zhuǎn)化為匯編語(yǔ)言/機(jī)器指令才能執(zhí)行。這個(gè)過(guò)程就是編譯。

JavaScript 需要編譯嗎?

JavaScript毫無(wú)疑問(wèn)是高級(jí)語(yǔ)言,所以它肯定是需要編譯后才能執(zhí)行。但為什么我們又稱(chēng)之為解釋型語(yǔ)言呢?它和編譯型語(yǔ)言、半解釋半編譯型語(yǔ)言又有什么區(qū)別呢?我們先從編譯說(shuō)起。

編譯

之前我們已經(jīng)了解編譯的概念,下面我們來(lái)聊聊平臺(tái):同樣一份C++代碼在Windows上會(huì)編譯成.obj文件,而在Linux上則生成.o文件,兩者不能通用。這是因?yàn)橐粋€(gè)可執(zhí)行文件除了代碼外還需要操作系統(tǒng) API、內(nèi)存、線(xiàn)程、進(jìn)程等系統(tǒng)資源,而不同的操作系統(tǒng)其實(shí)現(xiàn)也不盡相同。比如我們熟悉的I/O多路復(fù)用(事件驅(qū)動(dòng)的靈魂),在Windows上的實(shí)現(xiàn)方案是IOCP方案,在Linux上是epoll。所以針對(duì)不同的平臺(tái),編譯型語(yǔ)言需要分別編譯,甚至需要分別編寫(xiě),而且生成的可執(zhí)行文件其格式并不相同。

跨平臺(tái)

Java在此之上更進(jìn)一步,它通過(guò)引入字節(jié)碼實(shí)現(xiàn)了跨平臺(tái)運(yùn)行:無(wú)論是在什么操作系統(tǒng)上.java文件編譯出的都是.class文件(這就是字節(jié)碼文件,一種中間形態(tài)的目標(biāo)代碼)。然后Java對(duì)不同的系統(tǒng)提供不同的Java虛擬機(jī)用于解釋執(zhí)行字節(jié)碼文件。解釋執(zhí)行并不生成目標(biāo)代碼,但其最終還是要轉(zhuǎn)為匯編/二進(jìn)制指令來(lái)給計(jì)算機(jī)執(zhí)行的。

假如我們自己完全獨(dú)立的新寫(xiě)一個(gè)簡(jiǎn)單的操作系統(tǒng),那么它能不能運(yùn)行Java呢?很顯然是不能的,因?yàn)椴](méi)有這個(gè)系統(tǒng)相應(yīng)的JVM。所以Java的跨平臺(tái)、任何其他語(yǔ)言的跨平臺(tái),都是有局限性的。

Java采用半解釋半編譯的好處就是大大提升了開(kāi)發(fā)效率,然而相應(yīng)的則降低了代碼的執(zhí)行效率,畢竟虛擬機(jī)是有性能損失的。

解釋執(zhí)行

JavaScript則更進(jìn)一步。它是完全的解釋執(zhí)行,或者叫做即時(shí)編譯。它不會(huì)有中間代碼生成,也不會(huì)有目標(biāo)代碼生成。這個(gè)過(guò)程通常由宿主環(huán)境(如瀏覽器、Node.js)包辦。

編譯過(guò)程

現(xiàn)在我們確認(rèn)了,即使是解釋執(zhí)行的語(yǔ)言,也是需要編譯的。那么代碼是如何編譯的呢?我們來(lái)簡(jiǎn)單了解一下。

詞法分析

詞法分析會(huì)把語(yǔ)句分解成詞法單元,即Token。

function square(n){ return n*n;}

這個(gè)函數(shù)會(huì)被詞法分析器識(shí)別為function square,(n,),{,return,,n ,*n ,}并且給它們加上標(biāo)注,代表這是一個(gè)變量還是一個(gè)操作。

語(yǔ)法分析

這個(gè)過(guò)程會(huì)把Token轉(zhuǎn)化成抽象語(yǔ)法樹(shù)(AST):

{ type:'function',    id:{        type:'id'        name:'square'    },    params:[        {            type:'id',            name:'n'        }    ]    ...}

優(yōu)化及代碼生成

在這一步編譯器會(huì)做一些優(yōu)化工作,比如刪除多余運(yùn)算、刪除未用賦值、合并部分變量等等操作,最后生成目標(biāo)代碼。

由于即時(shí)編譯型語(yǔ)言的編譯通常發(fā)生在運(yùn)行前幾微秒,所以編譯器來(lái)不及做太多的優(yōu)化工作。這也是相比編譯型語(yǔ)言,早期JavaScript性能孱弱的原因之一。不過(guò)就現(xiàn)在而言,益于 V8 引擎(相比早期的JavaScript的引擎轉(zhuǎn)換成字節(jié)碼或解釋執(zhí)行,Node.js可以用 V8 提供的 JS2C 工具將 JavaScript 轉(zhuǎn)譯為 C++代碼),JavaScript 和其他語(yǔ)言性能上的差距已經(jīng)不足為道了。

鏈接及裝載

目標(biāo)代碼基本不能獨(dú)立運(yùn)行。應(yīng)用程序一般都會(huì)由多個(gè)部分(模塊)組成 ,比如C++中一個(gè)簡(jiǎn)單的輸出就要引入標(biāo)準(zhǔn)庫(kù) iostream

#include <iostream>using namespace std;int main(){        cout << "Happy Hacking!\n";        return 0;}

編譯器需要把多份目標(biāo)代碼(庫(kù))鏈接起來(lái)才能生成可執(zhí)行文件。至此,我們簡(jiǎn)單的了解了編譯過(guò)程。但實(shí)際上編譯比我們所講的要復(fù)雜得多,在此就不在展開(kāi)了。

什么是動(dòng)態(tài)語(yǔ)言,動(dòng)態(tài)類(lèi)型?

我們還知道,JavaScript是動(dòng)態(tài)語(yǔ)言。那么什么是動(dòng)態(tài)語(yǔ)言?

通常來(lái)說(shuō),這是指在運(yùn)行時(shí)代碼可以根據(jù)某些條件改變自身結(jié)構(gòu)的語(yǔ)言。比如JavaScript在運(yùn)行時(shí)新的函數(shù)、對(duì)象、甚至代碼都可以被引進(jìn)(eval);又比如Objective-C,它也可以在運(yùn)行時(shí)修改對(duì)象,但它不能動(dòng)態(tài)創(chuàng)建類(lèi),也沒(méi)有 eval 方法。那Objective-C算是動(dòng)態(tài)語(yǔ)言嗎?所以我認(rèn)為,動(dòng)態(tài)語(yǔ)言是個(gè)程度的問(wèn)題,我們不必在這個(gè)概念上太過(guò)糾結(jié),可以更多的關(guān)注其應(yīng)用。APP中常用的熱更新功能就是基于動(dòng)態(tài)語(yǔ)言特性而得以實(shí)現(xiàn)的。

JavaScript又是一門(mén)動(dòng)態(tài)類(lèi)型的語(yǔ)言,動(dòng)態(tài)類(lèi)型又是什么?動(dòng)態(tài)類(lèi)型的定義倒是很明確:數(shù)據(jù)類(lèi)型不是在編譯階段確定,而是在運(yùn)行時(shí)確定。

那么 TypeScript 是什么類(lèi)型的語(yǔ)言呢?它有靜態(tài)類(lèi)型檢查,它是靜態(tài)語(yǔ)言嗎?實(shí)際上它只是 JavaScript 的一個(gè)方言。TypeScript 最終還是要轉(zhuǎn)譯為 JavaScript 才能執(zhí)行(tsc),就如同我們使用babel 把 ES6 代碼轉(zhuǎn)譯為 ES5 一樣。這個(gè)過(guò)程嚴(yán)格上來(lái)說(shuō)不是編譯。

TypeScript 最大的優(yōu)勢(shì)就是靜態(tài)類(lèi)型檢查和類(lèi)型推斷,這是 JavaScript 嚴(yán)重缺失的能力。但實(shí)際上如果我們忽略IDE 給的報(bào)錯(cuò)提示強(qiáng)行運(yùn)行 TS 代碼,也還是有幾率能夠成功跑起來(lái)的。

錯(cuò)誤

剛剛我們提到報(bào)錯(cuò),不妨再擴(kuò)展說(shuō)一說(shuō)錯(cuò)誤。通常來(lái)說(shuō)錯(cuò)誤分為以下幾種:

編譯時(shí)錯(cuò)誤鏈接時(shí)錯(cuò)誤運(yùn)行時(shí)錯(cuò)誤

是不是和編譯過(guò)程能夠嚴(yán)格對(duì)應(yīng)起來(lái)?

編譯時(shí)錯(cuò)誤

編譯時(shí)錯(cuò)誤分為:

語(yǔ)法錯(cuò)誤

var str ='s ;

這就是典型的語(yǔ)法錯(cuò)誤,這種代碼無(wú)法生成AST,在詞法分析階段就會(huì)報(bào)錯(cuò)。通常我們這么寫(xiě)代碼,IDE 就會(huì)報(bào)錯(cuò)。這是IDE的優(yōu)化工作,和詞法分析相關(guān)。

類(lèi)型錯(cuò)誤

編譯器會(huì)檢查我們聲明的變量和函數(shù)的類(lèi)型,JavaScript中我們非常熟悉的Type Error:undefined is not object就是此類(lèi)錯(cuò)誤。

鏈接時(shí)錯(cuò)誤

在鏈接階段發(fā)生的異常。這種情況 JavaScript 中比較少見(jiàn),在編譯型語(yǔ)言中比較常見(jiàn)。

運(yùn)行時(shí)錯(cuò)誤

這是最難排查的錯(cuò)誤了,舉例來(lái)說(shuō):

int pider(int a,int b){    return a/b;}

上面的代碼在編輯編譯、鏈接階段都沒(méi)問(wèn)題,也能夠正常的生成可執(zhí)行文件。但是一旦如此使用pider(1,0)就會(huì)報(bào)錯(cuò)了,這就是典型的運(yùn)行時(shí)錯(cuò)誤。通常來(lái)說(shuō)運(yùn)行時(shí)錯(cuò)誤都是程序不夠健壯導(dǎo)致的。

JavaScript中最常見(jiàn)的十個(gè)錯(cuò)誤:

下圖是某錯(cuò)誤處理平臺(tái)收集統(tǒng)計(jì)的JavaScript Top10 錯(cuò)誤,其中7個(gè)TypeError,1個(gè) ReferenceError:

顯然這 8 種問(wèn)題,我們都能用 TypeScript 在編碼早期及時(shí)應(yīng)對(duì)。

感謝各位的閱讀,以上就是“JavaScript是怎么運(yùn)行的”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)JavaScript是怎么運(yùn)行的這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI