JavaScript 是如何执行代码的

准备知识

在讲正文以前,咱们须要先了解几个角色和概念:segmentfault

  • 引擎: 引擎爸爸的工做:负责整个 JavaScript 程序的编译及执行过程
  • 编译器: 引擎的好朋友,负责词法、语法分析及代码生成等脏活累活
  • 做用域: 引擎的另外一个好朋友,负责建立并维护全部的声明(变量,函数),并实施一套严格的规则,规定了如何查找变量,也就是肯定当前执行代码对变量的访问权限。
若是想深刻学习做用域的相关知识请查看《JavaScript深刻之做用域》

咱们常常说 JavaScript 是一门解释型语言,区别于编译型语言,可是实际上 JavaScript 是一门编译语言。但与传统的编译语言不一样, 它不是提早编译的, 编译结果也不能在分布式系统中进行移植。分布式

事实上,任何 JavaScript 代码片断在执行前都要进行编译,而后作好执行它的准备,一般编译后就会立刻执行。函数

咱们如下面这段代码为例来讲明 JavaScript 究竟是如何执行代码的。学习

var a = 2;
function m() {
    console.log('m');
}
m();

JavaScript 是如何执行代码的

上面咱们说了 JavaScript 在执行代码前是先进行编译的,上述代码虽然只有三块(变量声明、函数声明、函数调用),可是对于引擎来讲却至关于四个指令:spa

  • var afunction m() {}:编译阶段执行;(全部的变量和函数声明都是在编译阶段执行的
  • a = 2m();:执行阶段执行;

下面咱们将详细介绍一下在编译和执行阶段具体是如何处理的。code

编译阶段

若是不了解编译原理的相关知识(词法分析、语法分析、AST、代码生成),请查看 《编译原理之基础篇》
  1. 编译器首先会将这段程序分解成词法单元(词法分析),而后将词法单元解析成 AST(语法分析),而后开始根据 AST 生成机器指令(代码生成)。
  2. 在代码生成阶段:
    当编译器遇到 var a 时,编译器会询问做用域是否已经存在一个该名称的标识符,若是存在,编译器则忽略该指令,继续编译;不然它会要求做用域在当前做用域的集合中声明一个命名为 a 的标识符(变量)。
    同理,在编译器遇到 function m() {} 时,也会去询问做用域是否存在命名为 m 的函数声明,若是存在,编译器则忽略该指令,继续编译;不然它会要求做用域在当前做用域的集合中声明一个命名为 m 的函数。
  3. 编译器为引擎生成运行时所需代码以后,引擎开始执行代码。

执行阶段

引擎在执行代码时:blog

遇到 a = 2 时,会询问做用域,在当前做用域是否存在一个叫做 a 的标识符,若是是,做用域就会将其返还给引擎,引擎则会使用这个标识符;若是否,引擎会继续查找该变量(询问当前做用域的父级做用域,依次类推,直到顶层(全局做用域))。ip

若是引擎最终找到了 a ,就会将 2 赋值给它。作用域

不然在严格模式下,引擎会抛出一个 ReferenceError 异常(同做用域判别失败相关,做用域中没有找到想要的标识符);get

在非严格模式下,若是在顶层(全局做用域) 中也没法找到目标标识符,全局做用域中就会隐式建立一个具备该名称的标识符, 并将其返还给引擎。

总结:变量的赋值操做会执行两个动做,首先编译器会在当前做用域中声明一个变量(若是以前没有声明过),而后再运行时引擎会在做用域中查找该变量,若是可以找到就会对它赋值。


当引擎遇到m();时,查找 m 标识符的过程同上。不一样点是:在找不到 m 是,在非严格模式下,不会隐式建立一个具备该名称的标识符,在下面【执行过程当中引擎是如何查找变量的】中会解释缘由。

还有一个须要注意的点是,当找到 m 标识符后,引擎会开始执行该函数。此时若是该标识符表明的是一个函数,那么函数能够正常执行;可是若是标识符表明的是一个变量,那么就会抛出TypeError 异常 (表明做用域判别成功了, 可是对结果的操做是非法或不合理的),以下图所示:
image.png
此时 a 是一个变量,因此当尝试执行它时,会报错 a 不是一个函数。~~~~

执行过程当中引擎是如何查找变量的

上面咱们留了一个疑问,就是为何在找不到函数标识符的时候,全局做用域不会像变量声明同样也隐式建立一个,下面咱们就来看看是为何。

引擎在执行代码时,会经过查找标识符 a 和 m 来判断它是否已经声明过。查找的过程由做用域进行协助,可是引擎是怎么查找的呢?引擎查找变量有两种方式,分别是:

  • LHS 查询
    若是查找的目的是对变量进行赋值,则使用 LHS 查询(告诉做用域我须要对 a 变量进行 LHS 引用,你见过它嘛?)
    不成功的 LHS 引用会致使自动隐式建立一个全局变量(非严格模式),严格模式下抛出ReferenceError 异常
  • RHS 查询
    若是查找的目的是获取变量的值,则使用 RHS 查询(告诉做用域我须要对 a 变量进行 RHS 引用,你见过它嘛?)
    不成功的 RHS 查询会抛出 ReferenceError 异常

思考题

请看下面这个例子,其中 RHS 共使用了三次,LHS 共使用了两次,你能找到都是在哪里使用了 RHS 和 LHS 吗?

function add(a, b) {
    return a + b;
}
add(1, 2)
参考资料:《你不知道的JavaScript》 上篇
相关文章
相关标签/搜索