深入理解JavaScript作用域链

783 阅读2分钟

参考文章

http://suo.im/5L3v4k
http://suo.im/60LVo7

在介绍JavaScript作用域链之前,先介绍一下与之相关的一些概念

1 词法作用域

众所周知,作用域即只变量的可见性和可访问性,JavaScript采用的作用域是词法作用域。

词法作用域就是由你写代码时将变量和块作用域写在哪里来决定的。

例:

let number = 42;
function printNumber() {
    console.log(number);
}
function test() {
    let number = 54;
    printNumber();
}
test(); // 42

可以看到,打印的值是42,而不是54,决定其结果的原因是由printNumber书写的位置决定的。

2 作用域的类型

2.1全局作用域

在全局作用域中的变量,可以在程序中任意位置访问

例:

var say = 'Hello';
function greet() {
    console.log(say);
}
greet(); // Hello

2.2局部作用域或函数作用域

声明在函数内部的变量只能被函数内部所访问,函数外部无权访问

例:

function greet() {
    var say = 'Hello';
    console.log(say);
}
greet();// Hello
console.log(say); // Uncaught ReferenceError: say is not defined

2.3块级作用域

ES6引入了let,const变量(和var 不同, var变量没有块级作用域),他们的作用域是最近的一对花括号。他们不能被花括号之外被访问。

例:

{
    let say = 'Hello';
    var haha = 'hahaha';
    console.log(say); // Hello
}
console.log(haha); // hahaha

console.log(say); // Uncaught ReferenceError: say is not defined

3 作用域链

当前js引擎在当前作用域中查找不到要引用的变量后,js引擎会在其外部作用域中查找,直到找到为止。 如果找不到,则会报一个引用错误

例:

let a = 'a';
function bar() {
    let b = 'b';
    console.log(b); // b
    
    console.log(a); // a
    
    c = 'c';
    console.log(c); // c
}
bar();
  1. 当初始化bar函数时,会为bar函数维护一个私有属性[[scope]],会使用当前环境的作用域链初始化,这里就是bar.[[scope]] = global scope;
  2. 当bar函数执行的时候,构建bar的作用域链,bar.scopeChain = [test.[[scope]]]
  3. 初始化bar的活动对象,即bar.activationObject = [arguments], 然后活动对象被当作变量对象进行初始化,即bar.variableObject = bar.activationObject.concat[b];
  4. bar函数的变量对象,被压入其作用域链的顶端,即bar.scopeChain = [test.variableObject, test.[[scope]]];