前言
JavaScript中的闭包的确是一个比较晦涩难懂的知识点,今天整理和总结了一篇文章,对闭包的分析和使用。
一、闭包的定义
MDN 对闭包的定义是:
闭包是指那些能够访问自由变量的函数。
什么是自由变量呢?
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
简单来说,
闭包 = 函数 + 函数能够访问的自由变量。
从理论角度讲,所有的 JavaScript 函数都是闭包。因为函数在创建时就会将上层上下文的数据保存起来,哪怕访问的是简单的全局变量,这也相当于访问自由变量,此时使用的是最外层的作用域。但从实践角度看,满足以下两个条件的函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回);
- 在代码中引用了自由变量。
二、实践中的闭包分析
let globalValue = "我是全局变量";
function outerFunction() {
let localValue = "我是局部变量";
function innerFunction() {
return localValue;
}
return innerFunction;
}
let resultFunction = outerFunction();
console.log(resultFunction());
在这段代码中,当
outerFunction执行完毕后,它的执行上下文会从执行上下文栈中弹出,按常理其内部的局部变量localValue应该被销毁。但由于innerFunction形成了闭包,它依然可以访问localValue。这是因为innerFunction的执行上下文维护了一个作用域链,通过这个作用域链,它能够找到outerFunction作用域下的变量对象,从而读取到localValue的值。
三、闭包应用的常见问题及解决
在 JavaScript 开发中,变量作用域和闭包的相互作用可能会导致一些与预期不符的结果。以下面这段代码为例:
let numList = [];
for (let i = 0; i < 3; i++) {
numList[i] = function () {
console.log(i);
};
}
numList[0]();
numList[1]();
numList[2]();
1、ES6+环境
在现代 JavaScript(ES6 及以上版本)环境下,这段代码会依次打印出 0、1、2。这是因为let声明的变量具有块级作用域,每次循环都会创建一个新的块级作用域,每个作用域中的i都是独立的。当把函数赋值给numList[i]时,函数捕获的i是当前循环迭代中块级作用域内的i,所以在调用numList[0]()、numList[1]()、numList[2]()时,会分别打印出对应迭代中的i值。

2、ES5环境
如果在 ES5 环境下,使用var声明变量,情况就大不相同了。因为var声明的变量只有函数作用域和全局作用域,没有块级作用域。将上述代码中的let改为var:
let numList = [];
for (var i = 0; i < 3; i++) {
numList[i] = function () {
console.log(i);
};
}
numList[0]();
numList[1]();
numList[2]();

此时,
i是在全局作用域下声明的,循环结束后i的值为3。当函数被调用时,在函数自身作用域找不到i,就会到全局作用域查找,所以打印结果会是 3 个3。
为了在 ES5 环境下实现类似let块级作用域的效果,利用闭包可以解决这个问题:
let numList = [];
for (var i = 0; i < 3; i++) {
numList[i] = (function (j) {
return function () {
console.log(j);
};
})(i);
}
numList[0]();
numList[1]();
numList[2]();
在这段代码中,每次循环都创建了一个新的匿名函数,并将当前的
i值作为参数j传递给它。这个匿名函数的活动对象中保存了传入的j,当内部返回的函数执行时,会从这个匿名函数的活动对象中找到对应的j值,从而正确打印出0、1、2。在 ES6 及以上环境中,虽然
let已经能满足需求,但理解这种闭包的应用方式,对于深入掌握 JavaScript 的作用域和闭包机制仍然非常有帮助。
四、总结
闭包是 JavaScript 中一个强大而灵活的特性,它让函数可以访问并操作外部作用域的变量,即使这些变量所在的上下文已经销毁。理解闭包的概念和工作原理。
如果你喜欢这篇文章,可以点赞、收藏。
关注我,及时了解最新文章消息~
7211

被折叠的 条评论
为什么被折叠?



