JavaScript 编译器,引擎,作用域

2018-10-08  本文已影响0人  不得不爱XIN

前言

提起JavaScript ,大家第一反应:脚本语言、解释性执行等,和java、 C这种编译性语言搭不上边。然而,事实上它确实是一门编译语言。只是区别在于JS并不会像其他的编译语言一样进行提前编译,他的编译过程(通常)是在实际执行前进行的,而且也不会产生可移植的编译结果。

通常的编译过程,会做以下几个步骤:首先是分词与词法分析,把输入的字符串分解为一些对编程语言有意义的代码块(词法单元)。第二步解析与语法分析,这一步的操作高级了许多,会将上一步的词法单元集合分析并最终转换为一个由元素逐级嵌套所组成的代表了程序语法结构的树,称为 抽象语法树(Abstract Syntax Tree,AST)。第三步代码生成就是将上一步的AST转换为可执行代码。JavaScript引擎中的编译器做的事情与这个类似,但是因为JS引擎的编译过程就在代码执行前,对于“用户”来说是完全透明的。并且无法事先执行编译生成静态文件,因此JS的编译执行效率就要比一般静态语言敏感的多,故而也非常复杂。JS引擎在这一部分做了非常多的优化,一是针对语法分析和代码生成阶段进行优化(例如针对冗余元素进行优化等),目的是提高编译后的执行效率。二是针对编译过程进行优化(如JIT,延迟编译甚至重编译),目的是缩短编译过程,保证性能最佳。

介绍

执行

下面我们以一个最简单的例子来进行分析:

var a = 2
  1. 编译器出马,先进行词法分析,将该赋值操作拆分: var a;a=2;。第一步 var a,编译器可以处理,他会先询问变量管家:作用域,是否存在一个该名称的变量?若存在,继续编译;若不存在,通知作用域声明一个新变量,命名为a。
  2. 编译器继续为引擎进行代码生成,这些代码主要用来处理 a=2这个赋值操作。
  3. 引擎拿到可执行代码,然后询问作用域:当前有没有一个叫a的变量啊? 如果有:使用这个变量,赋值给他;如果没有就继续往上级作用域查找,如果到根作用域仍然找不到,引擎直接报错抛异常。

这儿引入个关于变量查找的概念:

var a; // LHS 寻找a,未找到,通知作用域声明一个新变量,命名为a
a = 2; // LHS 找到a并给其赋值2
console.log(a); // RHS找到a的值2,并将其输出

有了上面的基础知识,我们把三兄弟的合作再细化一下,例子也升级一下,用上面赋值并输出的例子。

  1. 编译器:作用域,我需要对a进行LHS查找,你见过么?
  2. 作用域:我这找到根都没看到啊,要不咱声明一个吧!
  3. 编译器:好,建好了,那我生成代码了,引擎,给你你要的代码。
  4. 引擎:收到,咦,需要一个a啊,作用域,帮我LHS找一下有没有?
  5. 作用域: 找到了,编译器已经帮忙声明了。
  6. 引擎:好的,那我对它赋值。
  7. 引擎:作用域,不要意思,我碰到一个console,需要RHS引用
  8. 作用域: 找到了,是个内置对象,拿走不谢。
  9. 引擎: 好的作用域,对了能在帮我确认一下a的RHS么?
  10. 作用域:确认好了,没变,拿去用吧,他的值是2
  11. 引擎:好咧,我把2传递给log(..)

疑问:为什么要这么啰嗦的区分LHS和RHS?其实细心的话,你应该已经发现了,这两种查找有一个很重要的区别,即在变量未找到的时候的行为不同:

其他

//第一个练习:
a = 2
var a;
console.log(a);

//第二个练习:
console.log(a);
var a = 2;
上一篇 下一篇

猜你喜欢

热点阅读