闭包
1. 需求示例:点击第 n 个按钮
window.onload = function () {
const btn = document.getElementsByTagName('button');
// 不能将 i 定义成 var,否则每个按钮都会变成"第 4 个"
// for (var i = 0, length = btn.length; i < length; ++i) {
// btn[i].onclick = function () {
// console.log('第' + (i + 1) + '个');
// };
// }
// 改用闭包 + var
for (var i = 0, length = btn.length; i < length; ++i) {
(function (i) {
btn[i].onclick = function () {
console.log('第' + (i + 1) + '个');
};
})(i);
}
// 或直接用 let(推荐)
for (let i = 0, length = btn.length; i < length; ++i) {
btn[i].onclick = function () { console.log('第' + (i + 1) + '个'); };
}
};
2. for 循环的优化
for 循环每次都会计算第二个分号里的表达式,所以 i < btn.length 每次都会重新读取 btn.length。可以缓存到外部以节省性能。
3. 如何产生闭包
当一个**嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)**时,就产生了闭包。
- 只要定义传入外部参数的内部函数就会产生闭包(不用调用内部函数)。
- 执行含有内部函数、且内部函数传入了外部函数参数的外部函数时,会额外产生闭包;而执行内部函数不会额外产生闭包。
- 因为函数提升,在外部函数中定义的内部函数会先在执行上下文对象中被定义,所以执行外部函数时立刻产生闭包。
4. 什么是闭包?
闭包 = 函数 + 其引用的外部词法环境。闭包让你能从内部函数访问外部函数作用域。
5. 常见闭包
- 将函数作为另一个函数的返回值。
- 将函数作为实参传递给另一个函数调用。
6. 闭包的作用
- 使函数内部的变量在执行完后仍然存活在内存中(延长了局部变量的生命周期)。
- 让函数外部可以操作(读写)到函数内部的变量。
function fn1() {
let a = 2;
function fn2() { a++; console.log(a); }
function fn3() { a++; console.log(a); }
return fn3;
}
const f = fn1(); // 31 行产生闭包 fn2、fn3
f(); // 3
f(); // 4
局部变量 a 通过闭包 fn3 被外部持续引用,因此 fn1 执行完后 a 并未消失。
7. 闭包的生命周期
- 闭包在嵌套的内部函数定义时产生(不是调用时)。
- 如果函数是通过表达式定义的,闭包就在表达式那一行产生:
function fn1() {
var a = 2;
var fn2 = function () { // 此时才产生闭包
a++;
console.log(a);
};
}
8. 闭包的应用:定义 JS 模块
模块封装要点:
- 要保证私有,必须封装在函数中,不能在对象里(对象的属性可见,不私有)。
- 向外暴露多个对象或函数要封装成对象。
// 方式一
function myModule() {
let data = 'private';
function getData() { return data; }
return { getData };
}
// 方式二(更常用)
const myModule = (function () {
let data = 'private';
function getData() { return data; }
return { getData };
})();
9. 闭包的缺点及解决
缺点:
- 闭包引用的外部变量会长期驻留在内存中,可能造成内存泄漏。
- 过度使用闭包会导致内存占用过高。
解决:
- 在不需要时手动解除引用:
fn = null,让闭包和外部变量都可被 GC。 - 避免在循环里大量创建闭包。
- 用
let/ 块级作用域替代闭包(如 for 循环示例)。
来源整理自:我的有道云笔记



