Javascript 词法作用域

Posted by jiananshi on 2015-07-26

前阵子买了中文版《你不知道的 Javascript》,一直听说是好书,之前只是默默 start 了一下,买了读了发现确实是好书。前端可以学习的东西太多辣,决定以后在 todo 上都记下来。

在 JS 脚本运行时,曾经我单纯的以为只是浏览器内有一个 Javascrpt 引擎,所有东西都交给它就可以了,看了书以后才明白还有编译器和作用域这些模块来辅助代码的编译和执行。有人可能会问,Javascript 不是解释型语言吗?还需要像 C 和 Java 一样编译?

在编译语言中,通常 代码在被引擎执行之前会经历三个步骤:

  1. 词法分析(tokenzing/lexing)
  2. 解析/语法分析(parsing)
  3. 代码生成

而 Javascript 则是在运行时编译,边编译边执行,这个机制涉及到三样东西:

  1. 引擎
  2. 编译器
  3. 作用域

编译器首先对代码进行编译,然后将生成的代码提供给引擎执行。引擎和编译器工作的时候,都会用到作用域。编译器编译时,会在作用域中查找标示符,如果没有找到的话则会在对应的作用域创建一个标示符。引擎在执行时不断的在作用域中查找标示符,如果一直到全局对象(Global object)都没有找到的话则抛出一个 Reference Error 并停止代码的执行。

这篇博客主要来看一下作用域,作用域有两种主要的工作模式:词法作用域和动态作用域。大多数标准语言编译器的第一个工作阶段叫做词法化,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是根据你写代码时变量和块作用域写在哪里来决定的,和你使用/调用的位置无关。

Javascript 严格的遵从了词法作用域,考虑这样一段代码

1
2
3
4
5
6
function b () {
  var a = a || 2;
  console.log(a);
}
var a = 1;
b() // 2

如果 Javascript 采用的是动态作用域,那么 b() 在全局作用域中被调用,输出的 a 应该是 1。不过通过 with 和 eval 也可以达到 “欺骗” 词法作用域的目的

1
2
3
4
5
6
7
8
function b () {
  with(window) {
    var a = a || 2;
    console.log(a);
  }
}
var a = 1;
b() // 1