(三)JavaScript 作用域 作用域链 闭包 原理

作用域 作用域链

JavaScript中的函数是一种特殊的对象;函数对象中有些属性可以访问,但有些不可访问,这些属性仅供JavaScript引擎存取;[[scope]]就是其中一个不可访问属性。
[[scope]]指的就是我们所说的作用域,其中存储了执行期上下文的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链(scope chain)。

执行期上下文:当函数执行时(准确的说是执行前一刻),会创建一个称为执行期上下文的内部对象(AO)。一个执行期上下文定义了一个函数执行时的环境;函数每次执行时的执行期上下文都是独一无二的,所以多次调用一个函数会创建多个执行期上下文;函数每次执行时,都会把新生成的执行期上下文填充到作用域链的最顶端。当函数执行完毕,它所产生的执行期上下文被销毁。
例1:

function a(){
	function b (){
		var num = 3;
		console.log(num);
	}
	var num = 2;
	console.log(num);
	b();
}
var num = 1;
console.log(num);
a();

JavaScript执行的过程中,作用域链大致为如下:

a被定义时:
a.[[scope]]  --> 0 : GO{ num : 1 , ... }

a被执行时:
a.[[scope]]  --> 0 : a 的AO{ num : 2 , ... }
				 1 : GO{ num : 1 , ... }
//a被执行时,b才被定义
b被定义时:
b.[[scope]]  --> 0 : a 的AO{ num : 2 , ... }
				 1 : GO{ num : 1 , ... }

b被执行时:
b.[[scope]]  --> 0 : b 的AO{ num : 3 , ... }
				 1 : a 的AO{ num : 2 , ... }
				 2 : GO{ num : 1 , ... }
//当b函数执行完后,销毁的是 b的AO
//当a函数执行完后,销毁的是 a的AO

这里写图片描述

查找变量时,从该函数作用域链顶端依次向下查找。

闭包

当内部函数被保存到外部时,将会形成闭包。闭包会导致原有作用域链不释放,造成内存泄漏(内存被占用)。
例2:

function test(){
	var temp = 100;
	function a(){
		temp++;
		console.log(temp);
	}
	return a ;
}
var demo = test();
demo(); // 101
demo(); // 102

test()的返回值是a函数,a函数被赋给了demo,所以demo函数的作用域链就是a函数的作用域链。
a函数的作用域链:

a.[[scope]]  --> 0 : test 的AO{ temp : 100 , ... }
				 1 : GO{ ... }

demo函数的作用域链:

demo.[[scope]]  --> 0 : test 的AO{ temp : 100 , ... }
					1 : GO{ ... }

可见,demo函数的作用域链里有对test函数生成的AO的引用,即使test函数执行完了,test函数生成的AO也没有被释放。
这里写图片描述
demo函数执行时的作用域链:

//demo执行第一次
demo.[[scope]]  --> 0 : demo 的AO1 { ... }
					1 : test 的AO{ temp : 101 , ... }
					2 : GO{ ... }	
//demo执行第二次
demo.[[scope]]  --> 0 : demo 的AO2 { ... }
					1 : test 的AO{ temp : 102 , ... }
					2 : GO{ ... }	

例子3:

function test(){
	var arr = [];
	for (var i = 0; i < 10; i++) {
		arr[i] = function (){ console.log(i); }
	}
	return arr;
}

var myArr = test();
for (var j = 0; j < 10; j++) {
	myArr[j](); // 输出10个10
}

myArr[0]() ... myArr[9]()
定义时同用作用域链

myArr[0-9].[[scope]]  --> 0 : test 的AO{ i :, ... }
						  1 : GO{ ... }

而,没有被释放的test函数的AOi = 10,所以输出 10个10。
例子4:

function test(){
	var arr = [];
	for (var i = 0; i < 10; i++) {
		// 立执行匿名函数
		(function (k){

			arr[k] = function (){ console.log(k); }

		}(i));

	}
	return arr;
}

var myArr = test();
for (var j = 0; j < 10; j++) {
	myArr[j](); // 输出 0、1、2、3、4、5、6、7、8、9
}

立执行匿名函数也是生成执行期上下文(AO)的,
myArr[0]() ... myArr[9]()被定义时的作用域链:

myArr[0].[[scope]]  --> 0 : 立执行匿名函数 的AO{ k :  0, ... }
						0 : test 的AO{ i :, ... }
						1 : GO{ ... }

myArr[1].[[scope]]  --> 0 : 立执行匿名函数 的AO{ k :  1, ... }
						0 : test 的AO{ i :, ... }
						1 : GO{ ... }
.
.
.	

闭包的作用

  • 实现公用变量
  • 可以做缓存(存储结构)
  • 可以实现封装,属性私有化
  • 模块化开发,防止污染全局变量

闭包的防范

闭包会导致多个执行函数公用一个共有变量,如果不是特殊需要,应尽量防止这种情况发生。

猜你喜欢

转载自blog.csdn.net/weicy1510/article/details/82020021