理解作用域
作用域负责收集并维护由所有声明的变量组成的集合,等待引擎的查找。
var a = 2;
console.log(a); // 2
console.log(b); // ReferenceError: b is not defined
-
var a = 2
可以分解为var a;
a = 2
。当遇到var a
时,编译器会询问作用域是否存在变量a
。如果存在,则忽略该声明,否则会在当前作用域的集合中声明一个新的变量a
。 - 遇到
a = 2
时,引擎会询问当前作用域是否存在变量,如果未找到,则会继续在上级作用域查找。如果最终找到就会将 2 赋值给变量a
。 -
console.log(a)
时,引擎会去作用域中查找a
,找到把结果返回,输出 2,console.log(b)
时,引擎未在作用域查找到b
,抛出异常。
LHS 和 RHS 查询
可以看出 ’L’ 和 ’R’ 分别代表左侧和右侧,即赋值的左侧和右侧。赋值不只是 =
的赋值,函数参数的传递也是一种赋值操作。
var a = 2; // LHS 查询,a 出现在赋值左侧
console.log(a); // RHS 查询,a 出现在赋值右侧,将变量 a 赋值给参数
查询失败会出现什么情况
对于 LHS 查询a = 2
若 a 未找到,在非严格模式下并不会报错,而变量 a 会被自动创建。而对于 RHS 来说,直接使用未声明的变量就会报 ReferenceError。
console.log(b); // ReferenceError: b is not defined
词法作用域
作用域主要有两种工作模型:词法作用域和动态作用域。
词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你写代码时变量和块作用域写在哪决定的。
function foo(a) {
var b = a * 2;
function bar(a) {console.log(a, b, c);
}
bar(b*3);
}
foo(2);
在这个例子有三个逐级嵌套的作用域。
- 全局作用域,包含一个标识符:foo
- foo 所创建的作用域,包含三个标识符:a, bar, b
- bar 所创建的作用域,包含一个标识符:c
函数作用域
函数作用域是指属于这个函数的变量都可以在整个函数范围内使用和复用。
function fn() {
var a = 2;
console.log(a); // 2
}
console.log(a); // ReferenceError: a is not defined
从中可以看出,函数外部将无法访问函数内部的变量。
块作用域
ES6 引入 let
、const
将变量绑定到所在块作用域 (通常是{…} 内部)
{
let a = 2;
console.log(a); // 2
}
console.log(a); // ReferenceError: a is not defined
除 let
、const
外,with
、try/catch
的 catch
分句会创建一个块作用域。
小结
函数是 Javascript 中最常见的作用域单元。但函数不是唯一的作用域单元。块作用域属于某个代码块 (通常指{…} 内部)。
接下来会讲解提升和闭包两个概念。