首頁  >  文章  >  web前端  >  詳解JavaScript是如何運作的

詳解JavaScript是如何運作的

青灯夜游
青灯夜游轉載
2019-11-26 15:24:472467瀏覽

詳解JavaScript是如何運作的

什麼是JavaScript?

讓我們來確認JavaScript的定義:JavaScript 是一門解釋型的動態語言。

解釋型語言是相對於編譯型語言存在的,原始碼不是直接編譯為目標程式碼,而是轉成中間程式碼,再由解譯器對中間程式碼進行解釋運行。

主流程式語言有編譯型(如 C )、解釋型(如 JavaScript)、和半解釋半編譯(如 Java)這幾大類型。

【相關課程推薦:JavaScript影片教學

#程式碼是怎麼運作的?

首先我們來了解程式碼是怎麼運作的。

我們知道,程式碼是由CPU執行的,而目前的CPU並不能直接執行諸如if…else之類的語句,它只能執行二進位指令。但是二進位指令對人類實在是太不友善了:我們很難快速準確的判斷一個二進位指令1000010010101001代表什麼?所以科學家發明彙編語言。

組合語言

組合語言其實就是二進位指令的助記符。

假設10101010代表讀取記憶體操作,記憶體位址是10101111,暫存器位址是11111010,那麼完整的操作101010101010111111110110 就代表讀取某個記憶體位址的值並載入到暫存器,而組譯語言並沒有改變這個操作方式,它只是二進位指令的對應:

LD:10101010 
id:10101111
R:11111010

這樣上述指令就可以表達為LD id R ,大大增強了程式碼的可讀性。

但是這樣還不夠友好,CPU只能執行三地址表達式,和人的思考方式、語言模式相距甚遠。所以偉大的科學家又發明了高階語言。

高級語言

“程式碼是寫給人看的,不是寫給機器看的,只是順便電腦可以執行而已。”

高階語言之所以稱之為“高級”,就是因為它更符合我們的思維和閱讀習慣。 if…else這種語句看起來要比1010101010舒服的多了。但是電腦並不能直接執行高階語言,所以還需要把高階語言轉換成組合語言/機器指令才能執行。這個過程就是編譯。

JavaScript 需要編譯嗎?

JavaScript毫無疑問是高階語言,所以它肯定需要編譯後才能執行。但為什麼我們又稱之為解釋型語言呢?它和編譯型語言、半解釋半編譯型語言又有什麼差別呢?我們先從編譯說起。

編譯

之前我們已經了解編譯的概念,下面我們來聊聊平台:同樣一份C 程式碼在Windows上會編譯成.obj文件,而在Linux上則產生.o文件,兩者不能通用。這是因為一個可執行檔除了程式碼外還需要作業系統 API、記憶體、執行緒、進程等系統資源,而不同的作業系統其實作也不盡相同。例如我們熟悉的I/O多路復用(事件驅動的靈魂),在Windows上的實作方案就是IOCP方案,在Linux上是epoll。所以針對不同的平台,編譯型語言需要分別編譯,甚至需要分別寫,而且產生的可執行檔其格式並不相同。

跨平台

Java在此之上更進一步,它透過引入字節碼實現了跨平台運行:無論是在什麼作業系統上.java檔案編譯出的都是.class檔案(這就是字節碼文件,一種中間形態的目標碼)。然後Java對不同的系統提供不同的Java虛擬機器用來解釋執行字節碼檔案。解釋執行並不產生目標程式碼,但其最終還是要轉為彙編/二進位指令來給電腦執行的。

假如我們自己完全獨立的新寫一個簡單的作業系統,那它能不能運行Java呢?很顯然是不能的,因為並沒有這個系統對應的JVM。所以Java的跨平台、任何其他語言的跨平台,都是有限制的。

Java採用半解釋半編譯的好處就是大幅提升了開發效率,然而相應的則降低了程式碼的執行效率,畢竟虛擬機器是有效能損失的。

解釋執行

JavaScript則更進一步。它是完全的解釋執行,或稱為即時編譯。它不會有中間程式碼生成,也不會有目標程式碼生成。這個過程通常由宿主環境(如瀏覽器、Node.js)包辦。

編譯過程

#

现在我们确认了,即使是解释执行的语言,也是需要编译的。那么代码是如何编译的呢?我们来简单了解一下。

词法分析

词法分析会把语句分解成词法单元,即Token。

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

这个函数会被词法分析器识别为function square(n){return,,n ,*n}并且给它们加上标注,代表这是一个变量还是一个操作。

语法分析

这个过程会把Token转化成抽象语法树(AST):

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

优化及代码生成

在这一步编译器会做一些优化工作,比如删除多余运算、删除未用赋值、合并部分变量等等操作,最后生成目标代码。

由于即时编译型语言的编译通常发生在运行前几微秒,所以编译器来不及做太多的优化工作。这也是相比编译型语言,早期JavaScript性能孱弱的原因之一。不过就现在而言,益于 V8 引擎(相比早期的JavaScript的引擎转换成字节码或解释执行,Node.js可以用 V8 提供的 JS2C 工具将 JavaScript 转译为 C++代码),JavaScript 和其他语言性能上的差距已经不足为道了。

链接及装载

目标代码基本不能独立运行。应用程序一般都会由多个部分(模块)组成 ,比如C++中一个简单的输出就要引入标准库 iostream

#include <iostream>
using namespace std;
int main(){    
    cout <p>编译器需要把多份目标代码(库)链接起来才能生成可执行文件。至此,我们简单的了解了编译过程。但实际上编译比我们所讲的要复杂得多,在此就不在展开了。</p>
<p><strong><span style="font-size: 18px;">什么是动态语言,动态类型?</span></strong></p>
<p>我们还知道,JavaScript是动态语言。那么什么是动态语言?</p>
<p>通常来说,这是指在运行时代码可以根据某些条件改变自身结构的语言。比如JavaScript在运行时新的函数、对象、甚至代码都可以被引进(eval);又比如Objective-C,它也可以在运行时修改对象,但它不能动态创建类,也没有 eval 方法。那Objective-C算是动态语言吗?所以我认为,动态语言是个程度的问题,我们不必在这个概念上太过纠结,可以更多的关注其应用。APP中常用的热更新功能就是基于动态语言特性而得以实现的。</p>
<p>JavaScript又是一门动态类型的语言,动态类型又是什么?动态类型的定义倒是很明确:数据类型不是在编译阶段确定,而是在运行时确定。</p>
<p>那么 TypeScript 是什么类型的语言呢?它有静态类型检查,它是静态语言吗?实际上它只是 JavaScript 的一个方言。TypeScript 最终还是要转译为 JavaScript 才能执行(tsc),就如同我们使用babel 把 ES6 代码转译为 ES5 一样。这个过程严格上来说不是编译。</p>
<p>TypeScript 最大的优势就是静态类型检查和类型推断,这是 JavaScript 严重缺失的能力。但实际上如果我们忽略IDE 给的报错提示强行运行 TS 代码,也还是有几率能够成功跑起来的。</p>
<p><span style="font-size: 18px;"><strong>错误</strong></span></p>
<p>刚刚我们提到报错,不妨再扩展说一说错误。通常来说错误分为以下几种:</p>
<ul>
<li>编译时错误</li>
<li>链接时错误</li>
<li>运行时错误</li>
</ul>
<p>是不是和编译过程能够严格对应起来?</p>
<p><strong>编译时错误</strong></p>
<p>编译时错误分为:</p>
<ul>
<li>
<p>语法错误</p>
<pre class="brush:php;toolbar:false">var str ='s ;

这就是典型的语法错误,这种代码无法生成AST,在词法分析阶段就会报错。通常我们这么写代码,IDE 就会报错。这是IDE的优化工作,和词法分析相关。

  • 类型错误

    编译器会检查我们声明的变量和函数的类型,JavaScript中我们非常熟悉的Type Error:undefined is not object就是此类错误。

  • 链接时错误

    在链接阶段发生的异常。这种情况 JavaScript 中比较少见,在编译型语言中比较常见。

    运行时错误

    这是最难排查的错误了,举例来说:

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

    上面的代码在编辑编译、链接阶段都没问题,也能够正常的生成可执行文件。但是一旦如此使用pider(1,0)就会报错了,这就是典型的运行时错误。通常来说运行时错误都是程序不够健壮导致的。

    JavaScript中最常见的十个错误:

    下图是某错误处理平台收集统计的JavaScript Top10 错误,其中7个TypeError,1个 ReferenceError:

    詳解JavaScript是如何運作的

    顯然這 8 種問題,我們都能用 TypeScript 在編碼早期及時應對。

    結論

    現在我們已經了解JavaScript是如何運作的。但了解這些能夠幫我們寫出更好的程式碼嗎?

    答案是肯定的。且不說TypeScript能夠幫助我們完善類型檢查和類型推斷,JavaScript的作用域、this也是和編譯過程強相關的;而目前主流的小程式框架都能夠支援一套程式碼、多個平台,相信讀完本文後,你大致也了解了這些技術背後的原理。
    Happy Hacking!

    本文來自 js教學 欄目,歡迎學習!

    以上是詳解JavaScript是如何運作的的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除