JavaScript代码是如何被执行的

基本概念

  • 编译器,解释器
  • 抽象语法树
  • 字节码和机器码

编译器和解释器

计算机不能直接理解高级语言,只能直接理解机器语言,因此必需要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序。根据语言的执行流程,能够把语言分红编译型语言和解释型语言。javascript

编译型语言:程序在执行以前须要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不须要从新翻译,直接使用编译的结果就好了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++、go等.java

解释型语言: 程序不须要编译,程序在运行时才翻译成机器语言(因此执行前须要环境中安装了解释器),每执行一次都要翻译一次。所以效率比较低。效率比较低,依赖解释器,跨平台性好数据库

编译型与解释型,二者各有利弊, 不能一律而论。前者因为程序执行速度快,同等条件下对系统要求较低,所以像开发操做系统、大型应用程序、数据库系统等时都采用它,像C/C++、Pascal/Object Pascal(Delphi)等都是编译语言,而一些网页脚本、服务器脚本及辅助开发接口这样的对速度要求不高、对不一样系统平台间的兼容性有必定要求的程序则一般使用解释性语言,如JavaScript、VBScript、Perl、Python、Ruby、MATLAB 等等。

编译器和解释器

咱们都知道 JavaScript 存在变量提高,在函数做用域内的任何变量的声明都会被提高到顶部而且值为 undefined。
因此JS引擎好像对同一个脚本执行了两次,第一次完成全部声明,而后第二次才执行代码?仍是先编译整个代码而后运行它?这两种都不对。编程

其实变量声明不过只执行上下文的小把戏。在执行任何语句以前,解释器就要从建立执行上下文后已经存在的做用域中找到变量的值。浏览器

抽象语法树

抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每一个节点都表示源代码中的一种结构。之因此说语法是“抽象”的,是由于这里的语法并不会表示出真实语法中出现的每一个细节。好比,嵌套括号被隐含在树的结构中,并无以节点的形式呈现;而相似于 if-condition-then 这样的条件跳转语句,可使用带有两个分支的节点来表示。缓存

字节码和机器码

字节码(Byte-code):是一种包含执行程序、由一序列 op 代码/数据对组成的二进制文件。字节码是一种中间码,它比机器码更抽象。服务器

机器码 (Machine-code):计算机直接使用的程序语言,其语句就是机器指令码,机器指令码是用于指挥计算机应作的操做和操做数地址的一组二进制数。数据结构

JavaScript代码执行过程

  1. 生成AST(抽象语法树)
  2. 生成字节码
  3. 执行代码

生成AST

生成AST的步骤能够拆分红如下两个小步骤:架构

  1. 词法分析:将JavaScript代码解析成一个个词法单元(token)
  2. 语法分析:将词法单元根据必定规则组装成抽象语法树
经过 javascript-ast 网站,能够大概了解 代码生成的 Tokens 以及 AST大体的样子。
  1. 词法分析:将JavaScript代码解析成一个个词法单元(token)

例如let a = 2;,一般会被分解为下面这些词法单元 leta=2; 空格是否会被当作词法单元取决于空格在这门语言中是否会具备意义。编程语言

分解Token示意图

  1. 语法分析:将词法单元根据必定规则组装成 AST
let a = 2;
console.log(a);

咱们能够看到生成的AST结构以下:

AST

高级语言是开发者能够理解的语言,编译器和解释器理解不了。因此不管你使用的是解释型语言仍是编译型语言,在编译过程当中,它们都会生成一个 AST。当生成 AST以后,编译器/解析器后续的工做都要依靠 AST而不是源码。

AST是一个很是重要数据结构,好比Babel的工做原理就是: ES6 的代码解析成 AST -> 将 ES6 的 AST 转换成 ES5 的AST -> 将 ES5的 AST 转成 ES5的代码。Babel的相关文章推荐 深刻浅出 Babel 上篇:架构和原理 + 实战;咱们使用的 Eslint(检查JavaScript编写规范的插件) 的检测流程也是先将源码转换成 AST, 而后利用 AST 来检查代码规范的问题

生成字节码

JavaScript引擎经过解释器来将 AST 转换成字节码,字节码是没法直接执行的,须要将其转为机器码才能直接执行。V8早期的时候,是直接将AST转成机器码的,后来由于 V8 须要消耗大量的内存来存放转换后的机器码,致使严重的内存占用问题。为了解决这个问题,引入 了字节码。字节码是比机器码轻量得多的代码。

字节码是介于 AST 和机器码之间的一种代码。可是与特定类型的机器码无关,字节码须要经过解释器将其转换成机器码后才能执行。

执行代码

生成字节码以后,就到了解释和执行字节码阶段了,

监听热点代码并优化为二进制机器码

解释器会逐条执行字节码,(解释器除了负责生成字节码,还会负责解释执行机器码) 若是发现一段代码重复执行屡次,就会它记为热点代码(HotSpot),V8会将这段热点代码提交给优化编辑器,优化编辑器会在后台将字节码编译为二进制代码,而后在对编译后的二进制代码执行优化操做,并保存下来。保存下来的机器码的做用和缓存很相似,当解释器再次遇到相同的内容时,就能够直接执行保存下来的机器码。

这样代码执行得越久,执行效率就会越快,由于会有愈来愈多的字节码被标记为 热点代码,遇到他们就能够直接执行,而不用转成机器码。

反优化生成的二进制机器码

JavaScript是一种很是灵活的动态语言,对象的结构和属性在运行时任意被改变,而通过优化后的代码只能针对某种固定结构。一旦在执行过程当中,对象的结构被动态修改了,那么优化后的代码会变成无效的代码,这时候优化编辑器就须要执行反优化操做,通过反优化的代码下次执行时就会回退到解释器解释执行。

字节码的执行是须要配合编译器和解释器的(这种技术称为即时编译 JIT)因此以前说 JS是一种解释型语言并不许确。

总结

整个过程以下面流程图所示:

JavaScript代码执行过程

参考

相关文章
相关标签/搜索