闭包

闭包

写在前面

今天面试问了一个很常见的题

function fun(){
	for (var i = 0; i < 5; i++){
		setTimeout(()=>{
			console.log(i)
		},0); 
	}
}
fun(); 
  1. 问打印结果是什么?
  2. 然后 怎么修改 就可以打印 01234 出来
  3. 使用proimse怎么修改?
  4. 什么闭包?
    感觉面试小姐姐,很nice..还给了我一些就件

执行环境和活动对象

活动对象

在一个函数对象被调用的时候,会创建一个活动对象,首先将该函数的每个形参和实参,都添加为该活动对象的属性和值;将该函数体内显示声明的变量和函数,也添加为该活动的的属性(在刚进入该函数执行环境时,未赋值,所以值为undefined,这个是JS的提升机制)

执行环境

执行环境是JavaScript中最为重要的一个概念。执行函数定义了变量或函数有权访问的其它数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object)和一个作用域链(scope chain),环境中定义的所以变量和函数都保存在其变量对象中。执行环境分为两种,一种是全局执行环境,一种是函数执行环境。

  1. 全局执行环境 ​
    全局执行环境是最外围的一个执行环境,其变量对象就是全局活动对象(window activation object),全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁。
  2. 函数执行环境 ​
    每个函数都有自己的执行环境。当执行流进入一个函数时,函数环境就会被推入一个环境栈中。当函数执行完之后,栈将其环境弹出,把控制权返回给之前的执行环境。函数执行环境的变量对象是该函数的活动对象(activation object)。

什么是闭包?

在不同的书上又不同的定义,严格来说,闭包要满足三个条件。1. 访问所在作用域 2.函数嵌套 3.在所在作用域外被调用。
其实,无论怎么去定义都很难去理解什么是闭包。但是从执行环境的角度来分析,可能比较容易理解。

先来一段简单的代码,演示闭包并分析。

 function fun(){
	var a = 0;
	function fzz(){
		console.log(a);
	}
	return fzz;		
}
var f = fun();
f();   // 0
  1. 代码首先在全局环境执行,并对全局执行的代码进行 声明提升
    在全局执行环境(存储)

  2. 代码接着执行 var f = fun()这一句, 会调用fun函数,会进入到函数执行环境中,此时代码依然进行提升作用。此时。 执行环境栈中,存在两个执行环境。 全局执行环境压栈, 而fun函数执行环境,为当前使用的执行环境。

  3. 执行 var a = 0; 对a 进行LHS查询,并赋值0. 执行环境不变。

  4. 执行 return fzz; 返回fzz函数的引用, 此时,fun函数执行结束,销毁fun函数执行环境,等待垃圾回收。 但是由于在fzz函数中存在自由变量a 需要通过作用域链,链接到fun执行环境中的变量a,所以在垃圾回收的时候,由于a存在引用并不会立即释放内存,只是从活动状态变成非活动状态了,等引用释放之后,就会被回收。

  5. 执行 var f = fun(); 此时切换到了全局执行环境。
    fun函数活动对象变成非活动状态。

  6. 执行 f(); 函数, 因为f保存着fzz函数引用,所以就会执行fzz,调用fzz函数,创建fzz函数执行环境,并进入。 依然声明提升,但是a是自由变量,会通过作用域链[ fzz --> fun --> windows ] 查找, 最终会在fun函数中找到var a = 0; 此时在就会查找fun执行环境a 的值, 并赋值到fzz执行环境的a

  7. 执行console.log(a) 使用fzz执行环境,a 的值2

  8. 执行 fzz函数结束。 销毁 fzz 执行环境。

  9. 当页面关闭时,才会销毁所有的执行环境。

注意

我们在上述过程中发现, 其实fun的活动对象, 变成非活动状态之后一直在内存中。 并不会释放。 此时,占用内存多的话,可能造成内存泄漏。 在上述过程中,我们也知道了,fun没有释放的原因是,存在引用。如果引用不存在就会被GC回收。所以我们在使用是要注意,及时解除引用,以便更早释放内存。

现在我们来分析之前的面试题吧

  1. 打印结果都是5。 因为引用的是同一 i 变量。 所以打印的是 55555
  2. 由于块作用域可以将索引值i重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值,相当于为每一次索引值都创建一个执行环境。当然使用IIFE也可以
function fun(){
	for (let i = 0; i < 5; i++){
		setTimeout(()=>{
			console.log(i)
		},0);
	}
}
fun(); 
function fun(){
	for (var i = 0; i < 5; i++){
		(function(i){
			return setTimeout(()=>{
				console.log(i)
			},1000)
		})(i)
	}
}
fun();
  1. 使用promise实现。(希望有人可以告诉我)
posted @ 2019-03-06 21:58  Cyrus_Br  阅读(179)  评论(0编辑  收藏  举报