JavaScript的作用域

一、前言

作用域、作用域链是JavaScript中重要的组成部分和重点知识,是我们务必要掌握的内容。

如果没有掌握,那么作为重点难点之一的函数的闭包将会难以理解、无从下手。

二、作用域

1. 函数作用域 [[scope]]

规则:

  1. 外部对内部可见
  2. 内部对外部不可见
  3. 内部优先
  4. JS中,只有函数级别的作用域,没有块级作用域。也就是说,只有在进入或者退出函数时,作用域会发生变化。

2. 代码解析

  1. 外部对内部可见
var scope = 'global';
function f() {
    console.log(scope); // 'global'
}
f();
  1. 内部对外部不可见
function f2() {
    var scope2 = 'local2';
}
console.log(scope2); // 报错scope2 is not defined
  1. 都可见时,内部优先
var scope3 = 'global3';
function f3() {
    console.log(scope3); // undefined
    var scope3 = 'local3';
    console.log(scope3); // 'local3'
}
f3();
  1. js作用域都是函数级别的
// 1 if代码块
var scope = 'g';
if(true) {
    var scope = 'l';
    console.log(scope); // 'l'
}
console.log(scope); // 'l'

// 2 for代码块
for(var i = 0; i < 10; i++) {
    console.log(i); // 0 1 2 3 4 5 6 7 8 9
}

// 3 function函数
function fn() {
    aa = 5;
}
fn();
console.log(aa); // 5

三、执行环境和作用域链

1. 执行环境(EC)

执行环境(execution context),也就是执行期上下文,它定义了执行期间可以访问的变量和函数。

  1. 全局执行环境
  • Global Object(Window),即GO
  • 从见到JS代码开始创建
  • 到网页关闭时销毁
  1. 函数执行环境
  • Activation Object(AO)
  • 从函数调用开始创建
  • 到函数调用结束时销毁

2. 作用域链(scope chain)

  1. 作用域链[[scope chain]],每个函数都有
  2. 作用域链是私有属性,只能由JS引擎访问
  3. 作用域链,是AO和GO构成的链
  4. 所谓执行环境就是沿着作用域链依次查找变量和函数
  • 找到即停
  • 全部找完没有结果的话,就报错

3. 生成作用域链

规则1:每个函数在定义(函数声明、函数表达式)时会拷贝其父级函数的作用域链。

规则2:在函数被调用时,生成AO然后将AO压入作用域链的栈顶(数据结构中的栈)。

var g = 'g';
function fa() {
    var a = 'a';
    function fb() {
        var b = 'b';
    }
    fb();
}
fa();

解析:

  1. 首先生成全局执行环境,进行全局环境的预编译,并产生作用域链,在作用域链中存着GO对象。(此时,GO作用域链的引用数为1)
  2. 按照规则1,函数在定义时会首先拷贝其父级函数的作用域链,因此在调用fa函数生成fa函数下的AO对象时,fa的作用域链中必然已经有一个GO对象的作用域链。 另外,fa函数生成的AO对象作用域链将被压入fa函数作用域的栈顶。(此时,GO作用域链的引用数为2,fa函数的AO对象引用数为1)
  3. 接着,当预编译fb函数时,首先按照规则1拷贝了其父级的作用域链,即fa函数的AO作用域链以及全局环境下的GO作用域链。最后将自己生成的AO作用域链压入fb函数作用域链的栈顶。(此时,GO引用数为3,fa的AO引用数为2,fb的AO引用数为1)
  4. 当fb函数执行完成后,fb的作用域链消失,fb的AO被回收。此时,fb的AO引用数为0,fa的AO引用数为1,GO引用数为2。
  5. 当fa函数执行完成后,fa的作用域链消失,fa的AO被回收。此时,fa的AO引用数为0,GO引用数为1。
  6. 因此,最后只剩下全局执行环境下的GO对象。

规则3 with中,生成的新的with variable object,放在作用域链表的最顶端

var name = 1;
var person = {name: 'Nancy'};
with (person) {
    console.log(name); // 'Nancy'
}
var person2 = {
    name2: 'Mike',
    age: 18,
    height: 175,
    wife: {
        name2: 'AA',
        age: 21
    }
}
with (person2.wife) {
    console.log(name2); // 'AA'
    console.log(age); // 21
}

4. 作用域链的注意点

  1. 效率:尽量少使用靠上层的变量,多使用自己的局部变量;
  2. 重名容易出错:尽量减少不同层次函数使用相同的变量名,避免函数名与变量名一样;
  3. 闭包:函数执行完成后,AO不一定被释放,利用这个特点可以生成闭包。
function outer() {
    var scope = 'outer';
    function inner() {
        return scope;
    }
    return inner;
}
var fn = outer();
console.log(fn()); // 'outer'

四、本节思维导图

posted @ 2019-06-15 16:45  见嘉于世  阅读(0)  评论(0编辑  收藏  举报  来源