1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 6256字,需瀏覽 13分鐘

         ·

        2022-01-14 15:50

        JavaScript 的運(yùn)行原理,是我面試的時(shí)候經(jīng)常會(huì)問到的問題,但是根據(jù)過往的面試結(jié)果來看,這部分能理解的很清楚的不足 20%,大多數(shù)同學(xué)熱衷于去學(xué)習(xí)一些 Vue、React 這樣的框架,以及一些新的 API,卻忽視了語言的根本,這是個(gè)非常不好的現(xiàn)象。

        今天我就帶大家來一起回顧一下,JavaScript 的真正的工作原理,里面不涉及深入的源碼解析,只是希望能夠用最簡(jiǎn)單的描述讓大家弄明白整個(gè)過程,主要分為下面幾個(gè)部分:

        • 解釋型和編譯型語言
        • JavaScript 引擎
        • EcmaScript 和 JavaScript 引擎的關(guān)系
        • 運(yùn)行時(shí)環(huán)境
        • 為啥是單線程
        • 調(diào)用堆棧的執(zhí)行過程
        • JavaScript 語言的解析過程

        解釋型和編譯型語言

        大家可能之前都聽說過,JavaScript 是一種解釋型的編程語言,那么啥叫解釋型語言呢?

        編程語言是用來寫代碼的,代碼是給人看的。計(jì)算機(jī)只看得懂機(jī)器代碼(01010101),看不懂語言代碼。將我們能看得懂的代碼轉(zhuǎn)換為計(jì)算機(jī)可讀的機(jī)器代碼有兩種方式:解釋和編譯。

        編譯型語言

        編譯型語言直接可以轉(zhuǎn)換為計(jì)算機(jī)處理器可以執(zhí)行的機(jī)器代碼,運(yùn)行編譯型語言需要一個(gè) “構(gòu)建” 的步驟,每次更新了代碼你也要重新 “構(gòu)建” 。

        它們會(huì)比解釋語言更快更高效地執(zhí)行。也可以更好的控制硬件,例如內(nèi)存管理和 CPU 使用率。但是,在完成整個(gè)編譯的步驟需要花費(fèi)額外的時(shí)間,生成的二進(jìn)制代碼對(duì)平臺(tái)有一定的依賴性。

        常見的編譯型語言有 C、C ++、Erlang、Haskell、Rust 和 Go。

        解釋型語言

        解釋型語言 是通過一個(gè)解釋器逐行解釋并執(zhí)行程序的每個(gè)命令。

        因?yàn)樵谶\(yùn)行時(shí)翻譯代碼的過程增加了開銷,解釋型語言曾經(jīng)比編譯型語言慢很多。但是,隨著即時(shí)編譯的發(fā)展,這種差距正在縮小。

        但是,解釋型語言更靈活一點(diǎn),并且一般都能動(dòng)態(tài)植入,程序也比較小。另外,因?yàn)槭峭ㄟ^解釋器自己執(zhí)行源程序代碼的,所以代碼本身相對(duì)于平臺(tái)是獨(dú)立的。

        常見的解釋型語言有 PHP、Ruby、Python 和 JavaScript。

        最后再來看看,誰來編譯?誰來解釋?誰來執(zhí)行?

        • 編譯型:編譯器來編譯,系統(tǒng)執(zhí)行。

        • 解釋型:解釋器解釋并執(zhí)行。

        JavaScript 引擎

        JavaScript 是一種解釋型的編程語言,所以源代碼在執(zhí)行之前沒有被編譯成二進(jìn)制代碼。那么計(jì)算機(jī)是怎么理解和執(zhí)行純文本腳本的呢?

        這就是 JavaScript 引擎的工作,也就是我們上面提到的解釋器

        JavaScript 引擎是一個(gè)執(zhí)行 JavaScript 代碼的計(jì)算機(jī)程序?;旧纤鞋F(xiàn)代瀏覽器都內(nèi)置了 JavaScript 引擎。當(dāng)我們的瀏覽器中加載到 JavaScript 文件時(shí),JavaScript 引擎會(huì)從上到下解析(將其轉(zhuǎn)換為機(jī)器碼)并執(zhí)行文件的每一行。

        每個(gè)瀏覽器都有自己的 JavaScript 引擎,其中最著名的引擎是 GoogleV8。

        Google ChromeNode.jsJavaScript 引擎都是 V8。下面還有一些其他的常見引擎:

        • SpiderMonkey:由 Firefox 開發(fā),第一款 JavaScript 引擎,用于Firefox。
        • Chakra:由微軟開發(fā),用于 Microsoft Edge
        • JavaScriptCore:由蘋果開發(fā),用于 webkit 型瀏覽器,比如 Safari

        所有的 JavaScript 引擎都會(huì)包含一個(gè)調(diào)用棧和一個(gè)堆:

        • 內(nèi)存堆 - 這是內(nèi)存分配發(fā)生的地方,是一個(gè)非結(jié)構(gòu)化的內(nèi)存池,它存儲(chǔ)我們應(yīng)用程序需要的所有對(duì)象。
        • 調(diào)用堆棧 - 是我們的代碼實(shí)際執(zhí)行的地方

        EcmaScript 和 JavaScript 引擎的關(guān)系

        ECMAScript 指的是 JavaScript 的語言標(biāo)準(zhǔn)及語言版本,比如 ES6 表示語言(標(biāo)準(zhǔn))的第 6 版。它由一個(gè)推動(dòng) JavaScript 發(fā)展的委員會(huì)制定,這個(gè)委員會(huì)指的是技術(shù)委員會(huì)( Technical Committee )第 39 號(hào),我們一般簡(jiǎn)稱 TC39,由各個(gè)主流瀏覽器廠商的代表以及一些互聯(lián)網(wǎng)大廠構(gòu)成。

        JavaScript 引擎的核心就是實(shí)現(xiàn) ECMAScript 標(biāo)準(zhǔn),此外還提供一些額外的機(jī)制(例如 V8 提供的垃圾回收器)。

        一些最新的 ECMAScript 提案,到達(dá) stage3stage4 后,就會(huì)被 JavaScript 引擎實(shí)現(xiàn),例如 v8 會(huì)把它的一些對(duì)語言標(biāo)準(zhǔn)的實(shí)現(xiàn)更新在它的博客上:https://v8.dev/

        運(yùn)行時(shí)環(huán)境

        JavaScript 引擎并不能孤立運(yùn)行,它需要一個(gè)好的運(yùn)行時(shí)環(huán)境才能發(fā)揮更大的作用,例如 Node.js 就是一個(gè) JavaScript 運(yùn)行時(shí)環(huán)境,各種瀏覽器也是 JavaScript 的運(yùn)行時(shí)環(huán)境。

        這些運(yùn)行時(shí)環(huán)境往往會(huì)提供諸如:事件處理、網(wǎng)絡(luò)請(qǐng)求 API、回調(diào)隊(duì)列或消息隊(duì)列、事件循環(huán) 這樣的附加能力。

        那么 JavaScript 引擎怎么配合這些能力在運(yùn)行時(shí)環(huán)境中發(fā)揮作用呢?我們拿 Chrome 來舉個(gè)例子。

        Chrome 是一個(gè)多進(jìn)程的架構(gòu),我們打開一個(gè)瀏覽器時(shí)會(huì)啟動(dòng)多個(gè)不同的進(jìn)程協(xié)助瀏覽器將頁(yè)面為我們呈現(xiàn)出來:

        • 瀏覽器進(jìn)程:瀏覽器最核心的進(jìn)程,負(fù)責(zé)管理各個(gè)標(biāo)簽頁(yè)的創(chuàng)建和銷毀、頁(yè)面顯示和功能(前進(jìn),后退,收藏等)、網(wǎng)絡(luò)資源的管理,下載等。
        • 插件進(jìn)程:負(fù)責(zé)每個(gè)第三方插件的使用,每個(gè)第三方插件使用時(shí)候都會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的進(jìn)程、這可以避免第三方插件crash影響整個(gè)瀏覽器、也方便使用沙盒模型隔離插件進(jìn)程,提高瀏覽器穩(wěn)定性。
        • GPU進(jìn)程:負(fù)責(zé)3D繪制和硬件加速
        • 渲染進(jìn)程:瀏覽器會(huì)為每個(gè)窗口分配一個(gè)渲染進(jìn)程、也就是我們常說的瀏覽器內(nèi)核,這可以避免單個(gè) page crash 影響整個(gè)瀏覽器。

        我們常說的瀏覽器內(nèi)核,比如 webkit 內(nèi)核,就是瀏覽器的渲染進(jìn)程,從接收下載文件后再到呈現(xiàn)整個(gè)頁(yè)面的過程,由瀏覽器渲染進(jìn)程負(fù)責(zé)。瀏覽器內(nèi)核是多線程的,在內(nèi)核控制下各線程相互配合以保持同步,一個(gè)瀏覽器內(nèi)核通常由以下常駐線程組成:

        • GUI 渲染線程:負(fù)責(zé)渲染瀏覽器界面 HTML 元素,當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時(shí),該線程就會(huì)執(zhí)行。
        • 定時(shí)觸發(fā)器線程:瀏覽器定時(shí)計(jì)數(shù)器并不是由 JavaScript 引擎計(jì)數(shù)的, 因?yàn)?JavaScript 引擎是單線程的, 如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確, 因此通過單獨(dú)線程來計(jì)時(shí)并觸發(fā)定時(shí)是更為合理的方案。
        • 事件觸發(fā)線程:當(dāng)一個(gè)事件被觸發(fā)時(shí)該線程會(huì)把事件添加到待處理隊(duì)列的隊(duì)尾,等待JS引擎的處理。這些事件可以是當(dāng)前執(zhí)行的代碼塊如定時(shí)任務(wù)、也可來自瀏覽器內(nèi)核的其他線程如鼠標(biāo)點(diǎn)擊、AJAX 異步請(qǐng)求等,但由于JS的單線程關(guān)系所有這些事件都得排隊(duì)等待JS引擎處理。
        • 異步http請(qǐng)求線程:XMLHttpRequest 在連接后是通過瀏覽器新開一個(gè)線程請(qǐng)求, 將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件放到 JavaScript 引擎的處理隊(duì)列中等待處理。
        • JavaScript 引擎線程:解釋和執(zhí)行 JavaScript 代碼。

        GUI 渲染線程與 JavaScript 引擎為互斥的關(guān)系,當(dāng) JavaScript 引擎執(zhí)行時(shí) GUI 線程會(huì)被掛起, GUI 更新會(huì)被保存在一個(gè)隊(duì)列中等到引擎線程空閑時(shí)立即被執(zhí)行。

        JavaScript 是一種單線程編程語言,所以在瀏覽器內(nèi)核中只有一個(gè) JavaScript 引擎線程。

        但是,在 JavaScript 的一個(gè)運(yùn)行環(huán)境中,因?yàn)榭赡苡卸鄠€(gè)渲染進(jìn)程,所以可能有多個(gè) JavaScript 引擎線程。

        詳情可以見這篇文章:瀏覽器是如何調(diào)度進(jìn)程和線程的?

        為啥是單線程

        那么,為什么 JavaScript 不設(shè)計(jì)成多個(gè)線程呢?這樣不是效率更高?

        作為瀏覽器腳本語言, JavaScript 的主要用途是與用戶互動(dòng),以及操作 DOM。這決定了它只能是單線程,否則會(huì)帶來很復(fù)雜的同步問題。比如,假定 JavaScript 同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè) DOM 節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)?

        所以,為了避免復(fù)雜性,從一誕生, JavaScript 就是單線程,這已經(jīng)成了這門語言的核心特征,將來也不會(huì)改變。

        那么既然 JavaScript 本身被設(shè)計(jì)為單線程,為何還會(huì)有像 WebWorker 這樣的多線程 API 呢?我們來看一下 WebWorker 的核心特點(diǎn)就明白了:

        • 創(chuàng)建 Worker 時(shí), JS 引擎向?yàn)g覽器申請(qǐng)開一個(gè)子線程(子線程是瀏覽器開的,完全受主線程控制,而且不能操作 DOM
        • JS 引擎線程與 Worker 線程間通過特定的方式通信(postMessage API,需要通過序列化對(duì)象來與線程交互特定的數(shù)據(jù))

        所以 WebWorker 并不違背 JS引擎是單線程的 這一初衷,其主要用途是用來減輕 cpu 密集型計(jì)算類邏輯的負(fù)擔(dān)。

        在單線程上運(yùn)行代碼非常容易,你不必處理多線程環(huán)境中出現(xiàn)的復(fù)雜場(chǎng)景 — 例如死鎖。

        調(diào)用堆棧的執(zhí)行過程

        JavaScript 是一種單線程編程語言,這意味著它有一個(gè)調(diào)用堆棧,一次只能做一件事。

        調(diào)用堆棧是一種數(shù)據(jù)結(jié)構(gòu),它基本上記錄了我們?cè)诔绦蛑械奈恢谩H绻覀儓?zhí)行一個(gè)函數(shù),它放會(huì)放在棧頂。如果我們從一個(gè)函數(shù)返回,其會(huì)從棧頂彈出,這就是調(diào)用堆棧的執(zhí)行過程。下面這個(gè)動(dòng)圖很好的解釋了整個(gè)運(yùn)行過程:

        調(diào)用堆棧中的每個(gè)條目被稱為 堆棧幀。當(dāng)調(diào)用堆棧中的一個(gè) 堆棧幀 需要大量時(shí)間才能被處理時(shí),就會(huì)產(chǎn)生卡頓,因?yàn)闉g覽器沒法做其他事情了。

        JavaScript 代碼的執(zhí)行過程

        我們從宏觀上看到了 JavaScript 調(diào)用堆棧是怎么執(zhí)行的,那么具體到每段代碼上是怎么解析執(zhí)行的呢?

        下面我們就以 V8 為例,來看看一段 JavaScript 代碼的解析執(zhí)行過程。

        上面的圖展示了 V8 大體的工作流程,畫的很復(fù)雜,我們簡(jiǎn)化一下,其實(shí)核心模塊是下面三個(gè):

        • 解析器(Parser):負(fù)責(zé)將 JavaScript 代碼轉(zhuǎn)換成 AST 抽象語法樹。
        • 解釋器(Ignition):負(fù)責(zé)將 AST 轉(zhuǎn)換為字節(jié)碼,并收集編譯器需要的優(yōu)化編譯信息。
        • 編譯器(TurboFan):利用解釋器收集到的信息,將字節(jié)碼轉(zhuǎn)換為優(yōu)化的機(jī)器碼。

        在執(zhí)行 JavaScript 代碼時(shí),首先解析器會(huì)將源碼解析為 AST 抽象語法樹,解釋器會(huì)將 AST 轉(zhuǎn)換為字節(jié)碼,一邊解釋一邊執(zhí)行。然后編譯器根據(jù)解釋器的反饋信息,優(yōu)化并編譯字節(jié)碼,最后生成優(yōu)化的機(jī)器碼,這就是 V8 大體的工作流程。

        詞法分析和語法分析

        我們常常提到的詞法分析和語法分析的過程就是發(fā)生在解析器(Parser)執(zhí)行階段。

        詞法分析就是將字符序列轉(zhuǎn)換為標(biāo)記(token)序列的過程。

        所謂 token ,就是源文件中不可再進(jìn)一步分割的一串字符,類似于英語中單詞,或漢語中的詞。

        一般來說程序語言中的 token 有:常數(shù)(整數(shù)、小數(shù)、字符、字符串等),操作符(算術(shù)操作符、比較操作符、邏輯操作符),分隔符(逗號(hào)、分號(hào)、括號(hào)等),保留字,標(biāo)識(shí)符(變量名、函數(shù)名、類名等)等。

        比如下面這段代碼:

        const?公眾號(hào)?=?'code秘密花園';

        經(jīng)過詞法分析后,會(huì)被轉(zhuǎn)換為下面這些 token

        • const(保留字)
        • 公眾號(hào)(變量名)
        • =(賦值操操作算符)
        • 'code秘密花園'(字符串常數(shù))

        語法分析 將這些 token 根據(jù)語法規(guī)則轉(zhuǎn)換為 AST

        {
        ??"type":?"Program",
        ??"start":?0,
        ??"end":?23,
        ??"body":?[
        ????{
        ??????"type":?"VariableDeclaration",
        ??????"start":?0,
        ??????"end":?23,
        ??????"declarations":?[
        ????????{
        ??????????"type":?"VariableDeclarator",
        ??????????"start":?6,
        ??????????"end":?22,
        ??????????"id":?{
        ????????????"type":?"Identifier",
        ????????????"start":?6,
        ????????????"end":?9,
        ????????????"name":?"公眾號(hào)"
        ??????????},
        ??????????"init":?{
        ????????????"type":?"Literal",
        ????????????"start":?12,
        ????????????"end":?22,
        ????????????"value":?"code秘密花園",
        ????????????"raw":?"'code秘密花園'"
        ??????????}
        ????????}
        ??????],
        ??????"kind":?"const"
        ????}
        ??],
        ??"sourceType":?"module"
        }

        在生成 AST 的同時(shí),還會(huì)為代碼生成執(zhí)行上下文,在解析期間,所有函數(shù)體中聲明的變量和函數(shù)參數(shù),都被放進(jìn)作用域中,如果是普通變量,那么默認(rèn)值是 undefined,如果是函數(shù)聲明,那么將指向?qū)嶋H的函數(shù)對(duì)象。

        字節(jié)碼和機(jī)器碼

        有了 AST 和執(zhí)行上下文,解釋器會(huì)將 AST 轉(zhuǎn)換為字節(jié)碼并執(zhí)行,那么字節(jié)碼和機(jī)器碼的區(qū)別是啥呢?

        • 機(jī)器碼(machine code),學(xué)名機(jī)器語言指令,有時(shí)也被稱為原生碼(Native Code),是電腦的 CPU 可直接解讀的數(shù)據(jù)(計(jì)算機(jī)只認(rèn)識(shí)0和1)。
        • 字節(jié)碼(byte code)是一種包含執(zhí)行程序、由一序列 OP代碼(操作碼)/數(shù)據(jù)對(duì) 組成的二進(jìn)制文件。字節(jié)碼是一種中間碼,它比機(jī)器碼更抽象,需要直譯器轉(zhuǎn)譯后才能成為機(jī)器碼的中間代碼。

        相比機(jī)器碼,字節(jié)碼不僅占用內(nèi)存少,而且生成字節(jié)碼的時(shí)間很快,提升了啟動(dòng)速度。那么機(jī)器碼什么時(shí)候用到呢?我們?cè)谖恼麻_頭提到,隨著即時(shí)編譯的發(fā)展,解釋型語言和編譯型語言的運(yùn)行速度的差距正在縮小。

        同時(shí)采用了解釋執(zhí)行和編譯執(zhí)行這兩種方式,這種混合使用的方式就稱為 JIT (即時(shí)編譯),V8 采用的就是這種技術(shù)。

        在解釋器執(zhí)行字節(jié)碼的過程中,如果發(fā)現(xiàn)有熱點(diǎn)代碼,比如一段代碼被重復(fù)執(zhí)行多次,這種就稱為熱點(diǎn)代碼,那么后臺(tái)的編譯器就會(huì)把該段熱點(diǎn)的字節(jié)碼編譯為高效的機(jī)器碼,然后當(dāng)再次執(zhí)行這段被優(yōu)化的代碼時(shí),只需要執(zhí)行編譯后的機(jī)器碼就可以了,這樣就大大提升了代碼的執(zhí)行效率。

        最后

        當(dāng)然,想要了解更詳細(xì)的執(zhí)行機(jī)制,可以去看看 V8 源碼,這篇文章主要帶大家捋清楚各種概念,讓你能夠知道運(yùn)行一段 JavaScript?背后的工作原理,想要更深入的了解,可以看看下面這些文章:。

        • https://zhuanlan.zhihu.com/p/383959486

        • https://www.zhihu.com/question/268303059
        • https://blog.devgenius.io/how-javascript-works-behind-the-scenes-88c546173f32

        瀏覽 57
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            欧美性爱一区二区 | 欧美日韩精品一区二区天天拍小说 | 91爱搞搞 | 操屄小视频 | xxxxbdsmsexhd捆绑视频 | 亚洲无码黄色电影 | 成人免费看A级毛片 | 91视频免费版污 | 91禁在线看 | 激情乱伦网站 |