一、前言
作用域、作用域链是JavaScript中重要的组成部分和重点知识,是我们务必要掌握的内容。
如果没有掌握,那么作为重点难点之一的函数的闭包将会难以理解、无从下手。
二、作用域
1. 函数作用域 [[scope]]
规则:
- 外部对内部可见
- 内部对外部不可见
- 内部优先
- JS中,只有函数级别的作用域,没有块级作用域。也就是说,只有在进入或者退出函数时,作用域会发生变化。
2. 代码解析
- 外部对内部可见
var scope = 'global';
function f() {
console.log(scope); // 'global'
}
f();
复制代码
- 内部对外部不可见
function f2() {
var scope2 = 'local2';
}
console.log(scope2); // 报错scope2 is not defined
复制代码
- 都可见时,内部优先
var scope3 = 'global3';
function f3() {
console.log(scope3); // undefined
var scope3 = 'local3';
console.log(scope3); // 'local3'
}
f3();
复制代码
- 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),也就是执行期上下文,它定义了执行期间可以访问的变量和函数。
- 全局执行环境
- Global Object(Window),即GO
- 从见到JS代码开始创建
- 到网页关闭时销毁
- 函数执行环境
- Activation Object(AO)
- 从函数调用开始创建
- 到函数调用结束时销毁
2. 作用域链(scope chain)
- 作用域链[[scope chain]],每个函数都有
- 作用域链是私有属性,只能由JS引擎访问
- 作用域链,是AO和GO构成的链
- 所谓执行环境就是沿着作用域链依次查找变量和函数
- 找到即停
- 全部找完没有结果的话,就报错
3. 生成作用域链
规则1:每个函数在定义(函数声明、函数表达式)时会拷贝其父级函数的作用域链。
规则2:在函数被调用时,生成AO然后将AO压入作用域链的栈顶(数据结构中的栈)。
var g = 'g';
function fa() {
var a = 'a';
function fb() {
var b = 'b';
}
fb();
}
fa();
复制代码
解析:
- 首先生成全局执行环境,进行全局环境的预编译,并产生作用域链,在作用域链中存着GO对象。(此时,GO作用域链的引用数为1)
- 按照规则1,函数在定义时会首先拷贝其父级函数的作用域链,因此在调用fa函数生成fa函数下的AO对象时,fa的作用域链中必然已经有一个GO对象的作用域链。 另外,fa函数生成的AO对象作用域链将被压入fa函数作用域的栈顶。(此时,GO作用域链的引用数为2,fa函数的AO对象引用数为1)
- 接着,当预编译fb函数时,首先按照规则1拷贝了其父级的作用域链,即fa函数的AO作用域链以及全局环境下的GO作用域链。最后将自己生成的AO作用域链压入fb函数作用域链的栈顶。(此时,GO引用数为3,fa的AO引用数为2,fb的AO引用数为1)
- 当fb函数执行完成后,fb的作用域链消失,fb的AO被回收。此时,fb的AO引用数为0,fa的AO引用数为1,GO引用数为2。
- 当fa函数执行完成后,fa的作用域链消失,fa的AO被回收。此时,fa的AO引用数为0,GO引用数为1。
- 因此,最后只剩下全局执行环境下的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. 作用域链的注意点
- 效率:尽量少使用靠上层的变量,多使用自己的局部变量;
- 重名容易出错:尽量减少不同层次函数使用相同的变量名,避免函数名与变量名一样;
- 闭包:函数执行完成后,AO不一定被释放,利用这个特点可以生成闭包。
function outer() {
var scope = 'outer';
function inner() {
return scope;
}
return inner;
}
var fn = outer();
console.log(fn()); // 'outer'
复制代码