JS的做用域

1、前言

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

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

2、做用域

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

规则:git

  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
复制代码

3、执行环境和做用域链

1. 执行环境(EC)

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

  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'
复制代码

4、本节思惟导图

源码地址: github.com/Knight174/M…
相关文章
相关标签/搜索