JavaScript的编译

很多人说JavaScript是解释型或者动态语言,是不需要编译的。其实不然,JavaScript是需要编译的,其编译发生在代码执行前的一瞬间(几微妙或者更短的时间内)。大多数编译型的语言编译大致都可以分3个步骤:分词,解析,生成代码。JavaScript也不例外,但是JavaScript会有更复杂的过程。

分词

大家知道,其实我们99.99%的代码都是文本类型。也就是说编译器拿到的是一堆实现约定好格式的文本,编译器拿到文本要干的第一件事情就是分词(也可以叫做词法分析)。就拿赋值语句来说(var a = 0;),编译器经过分词之后就会拿到这样的结果:var、a、=、0。

解析

经过词法分析,编译器在拿到分词结果之后便会根据分词的结果生成一个叫做“抽象语法树”(Abstract Syntax Tree)的树形结构,简称AST。就拿(var a = 0;)来说,树结构下一般会有以下节点:存储a的名称(也就是“a”)、a的类型、a的值。

生成代码

在这阶段,编译器根据生成的“抽象语法树”来生成计算机可以识别的指令。在从JavaScript到机器指令之间,JavaScript引擎还做了很多事情,以var a = 0;为例: 其实这条语句,JavaScript编译器会分为2个步骤执行(感谢TylerPeng的提醒):

  • var a;此时,JavaScript引擎会询问当前作用域中是否已经申明了a变量,如果a已经申明,那么编译器会忽略此次申明;如果没有找到a的身影,那么编译器会在当前作用域中申明一个a变量。

  • a = 0;赋值时,询问当前作用域中是否已经申明了a变量,如果没有则询问当前作用域的上级(此过程与我在JavaScript之闭包一文中类似)。如果最终没有找到a,那么就会报错;如果找到了a,那么就会直接使用。

其他的事

其实JavaScript引擎除了基本的3个编译步骤,还做了很多其他的事情,例如:代码优化、整理等。

变量申明提升

既然已经讲到了这里,也不得不提到这个概念。其实在JavaScript中,变量申明是会被提升到当前作用域的最顶部,思考一下代码:

function foo(){
  console.log(a);

  var a = 3;
}

foo(); // undefined

但是按照编译规则,在执行到console.log(a)时,代码应该会报错才对,现在为什么又打印出“undefined”呢,打印出“undefined”很显然变量已经被申明了。上面我有提到,JavaScript引擎会对我们的代码进行优化、整理,真正编译器拿到的代码是这个样子的:

function foo(){
  var a;

  console.log(a);

  a = 3;
}

foo(); // undefined

就像我们上面说到的一样,申明赋值这个操作,JavaScript会分成2个步骤完成:申明和赋值。 其实不止变量申明的提升,函数的申明也会提升,思考一下代码:

foo(); // 3

function foo(){
  var a = 3;

  console.log(a);
}

我们在调用foo时,foo函数还没有申明。但是我们居然能得到我们所期望的值,这说明函数也会提升。

总结

了解了JavaScript的编译规则,应该对它会有不同的认识。其实JavaScript是一门被精心设计的语言,其优势不言而喻。当今有很多人吐槽JavaScript,吐槽JavaScript各种令人讨厌的坑。其实学习和工作哪个不是不断的在填自己知识上填坑呢?

results matching ""

    No results matching ""