09 闭包
理论:
在 JavaScript 中,根据 词法作用域 的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这 些变量的集合称为闭包。
闭包的重点:在函数内部定义其他函数返回!!!不是调用外部作用于域块就能够直接形成的。
闭包优缺点:
- 优点:① 能够读取函数内部的变量;②让这些变量一直存在于内存中,不会在调用结束后被垃圾回收机制回收;
- 缺点:由于闭包会使用函数中的变量存在在内存中,内存消耗很大,所以不能滥用闭包;解决的办法是退出函数之前,将不使用的局部变量删除;
产生闭包的核心:
- 第一步是需要 预扫描内部函数
- 第二步是把 内部函数引用的外部变量 保存到 堆 中
案例:
function foo() {
var myName = " 极客时间 "
let test1 = 1
const test2 = 2
var innerBar = {
getName:function(){
console.log(test1)
return myName
},
setName:function(newName){
myName = newName
}
}
return innerBar
}
var bar = foo()
bar.setName(" 极客邦 ")
bar.getName()
1、当 JavaScript
引擎执行到 foo()
时,首先会编译,并创建一个空 执行上下文 ,执行栈如下:
在编译过程中,遇到内部函数 setName
,JavaScript引擎还要对内部函数做一次 快速的词法扫描,发现该内部函数引用了 foo
函数中的 myName
变量,由于是 内部函数引用了外部函数的变量,所以JavaScript引擎判断这是一个闭包,于是在 堆 空间创建 一个 closure(foo)
的对象(这是一个内部对象,JavaScript是无法访问的) ,用来保存 myName
变量
接着继续扫描到 getName
方法时,发现该函数内部还引用变量 test1
,于是 JavaScript引擎又将 test1
添加到 closure(foo)
对象中
由于 test2
并没有被内部函数引用,所以 test2
依然保存在调用栈中(图中 test2
应该在词法环境中)
2、当 return innerBar
返回给全局变量 bar 时,虽然 foo
函数已经执行结束,但是 getName
和 setName
函数依然可以使用 foo
函数中的变量 myName
和 test1
。所以当 foo
函数执行完成之后,其整个调用栈的状态如下图所示:
3、bar.setName
中给 myName
赋值时,调用栈如下图:
当前执行上下文–> foo 函数闭包 –> 全局执行上下文
常见考题:
案例1:全部输出 3
// 下题并没有形成闭包
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0](); // 3
data[1](); // 3
data[2](); // 3
案例2:形成闭包
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}
data[0](); // 0
data[1](); // 1
data[2](); // 2