又重新回顾了下闭包的知识
什么是闭包?
根据《JavaScript高级程序设计》一书中的定义,闭包是指有权限访问另一个函数作用域中的变量的函数。
创建闭包的常见方式就是在一个函数内部创建另一个函数。
闭包的三个特性
1、 闭包可以访问当前函数以外的变量
function outer() {
let outerData = "999"
function inner() {
console.log(outerData)
}
return inner();
}
outer(); // 999
2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量
function outer() {
let outerData = "999"
function inner() {
console.log(outerData)
}
return inner;
}
let temp = outer();
temp(); // 999
3、闭包可以更新外部变量的值
function outer() {
let outerData = 0;
function inner() {
outerData++;
console.log(outerData)
}
return inner;
}
let temp = outer();
temp(); //1
temp(); //2
temp(); //3
这段代码可以发现一个问题,每次打印时,outerData并没有被重新赋值为0。
这是因为函数outer有返回值,而返回的函数inner中又引用了outerData,所以outerData并没有被垃圾回收器清除,每次temp执行的时候其实都是对同一个outerData进行了操作。
作用域链
JavaScript的函数中存在一个属性[[scope]],这个属性表示了这个函数的作用域链,我们使用不了,仅供js引擎访问。
JavaScript在创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链会保存在函数内部的[[scope]]属性中,这个属性我们使用不了,仅供js引擎访问。
当调用创建的函数时,会为函数创建一个执行环境,然后通过复制函数的[[scope]]属性中的对象构建起执行环境的作用域链。作用域链本质上是一个指向变量对象的指针列表,它只引用但不包含变量对象。
一般来说,当函数执行完毕,局部活动对象就会销毁,内存中仅保存全局作用域。
但闭包有所不同,在一个函数内部定义的函数会将外部函数的活动对象加入自己的作用域链,这也就是闭包能获取和修改外部函数变量的原因。
闭包函数在被外层函数返回后,它的作用域链被初始化为包含外层函数的活动对象和全局变量对象。这样在外层函数执行完毕后,其作用对象不会被销毁,因为闭包仍然在引用这个活动对象。一直到闭包被销毁,其外层函数的活动对象才会被销毁。
例题
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0](); //2
data[1](); //2
data[2](); //2
结果是2,2,2,因为下面三行在执行时打印的都是同一个变量i。(执行的函数内部都没有i,像父级寻找,最终找到同一个i)
想看到1,2,3可以用闭包改进一下。
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
再或者把var改成let也可以出现1,2,3。因为使用了ES6的let,所以此处形成了块级作用域,每个data[i]都是一个闭包。
var data = [];
for (let i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0](); //0
data[1](); //1
data[2](); //2