skip to content
寻找莉莉丝

JavaScript 的作用域

/ 6 min read / 次阅读

一、前言

作用域、作用域链是 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'

四、本节思维导图