一、闭包的原理
闭包是指函数能够访问其定义时所在的词法作用域,即使该函数在其定义的作用域之外被调用。
核心原理:
1.函数在创建时会捕获其周围的词法环境(变量、函数等)
2.当函数在外部作用域被调用时,依然能访问这些捕获的变量
function outer() {
// 外部函数的局部变量
const message = "Hello, Closure!";
// 内部函数(闭包)
function inner() {
// 访问外部函数的变量
console.log(message);
}
// 返回内部函数(在外部作用域执行)
return inner;
}
// 接收闭包函数
const closureFunc = outer();
// 即使 outer 已执行完毕,仍能访问 message
closureFunc(); // 输出: "Hello, Closure!"
// 当 outer() 执行结束后,其作用域并未被垃圾回收机制清除,因为 inner() 函数仍在引用其中的 message 变量,形成了闭包。
二、闭包的特点
变量私有化:外部无法直接访问闭包内部的变量,只能通过闭包提供的接口操作
状态保持:闭包可以保留其创建时的环境状态,多次调用可共享同一状态
作用域链延长:使函数能访问外层作用域的变量,即使外层函数已执行完毕
三、实际应用场景
1.模块化与私有变量
模拟类的私有属性和方法,避免全局变量污染。
function createCounter() {
let count = 0; // 私有变量
return {
increment: () => count++,
decrement: () => count--,
getCount: () => count
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 1(无法直接访问 count)
2.函数柯里化
将多参数函数转换为一系列单参数函数。
function multiply(a) {
return function(b) {
return a * b;
};
}
const multiplyBy2 = multiply(2);
console.log(multiplyBy2(5)); // 10
3.延迟执行
如定时器、事件处理等场景,保存当前状态。
function setupTimer(message, delay) {
setTimeout(function() {
console.log(message); // 闭包捕获 message 变量
}, delay);
}
setupTimer("延迟执行", 1000);
4.React Hooks 实现基础
React 中的 useState、useEffect 等 Hooks 本质上利用闭包保存组件状态。
四、优点
数据封装:实现私有变量,控制变量访问权限,提高代码安全性
状态持久化:在函数多次调用之间保持状态,如计数器、缓存等场景
模块化:将相关逻辑和数据组织在一起,避免全局变量污染
灵活的函数创建:可动态生成函数,适应不同场景需求
五、缺点
内存消耗:闭包会保留外层作用域,导致变量不会被垃圾回收,可能造成内存泄漏
性能影响:过度使用闭包会增加内存占用,影响程序性能
调试困难:闭包的作用域链复杂,变量来源不直观,增加调试难度
可能导致意外行为:如果不注意,闭包捕获的变量可能会有预期之外的变化
// 常见的闭包陷阱
function createFunctions() {
const result = [];
for (var i = 0; i < 3; i++) {
result.push(function() { return i; });
}
return result;
}
const funcs = createFunctions();
console.log(funcs[0]()); // 3(而非预期的 0)
// 解决:使用 let 或 IIFE 创建独立作用域
*闭包的this指向
在闭包中,this 的指向是一个容易混淆的点,它并不像普通函数那样简单地指向调用者,而是由闭包的调用方式和定义环境共同决定。
一、闭包中 this 的默认指向
默认情况下,闭包中的 this 指向全局对象(浏览器中是 window,Node.js 中是 global),而非外层函数的 this。
这是因为闭包本身是一个独立的函数,它没有继承外层函数的 this 绑定,其 this 指向由调用时的上下文决定。
const obj = {
name: "obj",
outer: function() {
// 外层函数的this指向obj
console.log("outer this:", this); // { name: "obj", outer: [Function] }
// 闭包
function inner() {
// 闭包的this默认指向全局对象
console.log("inner this:", this); // window(浏览器环境)
console.log("inner this.name:", this.name); // window.name(可能为空)
}
return inner;
}
};
const closure = obj.outer();
closure(); // 直接调用,this指向全局
二、如何让闭包访问外层函数的 this?
如果需要在闭包中使用外层函数的 this,可以通过以下两种方式:
- 用变量保存外层 this(最常用)
在外层函数中用变量(如 that、self)缓存 this,闭包通过访问该变量间接使用外层 this。
const obj = {
name: "obj",
outer: function() {
const that = this; // 保存外层this
function inner() {
console.log(that.name); // 访问外层this的name → "obj"
}
return inner;
}
};
const closure = obj.outer();
closure(); // 输出: "obj"
- 使用箭头函数(ES6+)
箭头函数没有自己的 this,它会继承外层作用域的 this,因此适合作为闭包直接使用外层 this。
const obj = {
name: "obj",
outer: function() {
// 箭头函数作为闭包,继承外层this
const inner = () => {
console.log(this.name); // this指向outer的this(即obj)
};
return inner;
}
};
const closure = obj.outer();
closure(); // 输出: "obj"
三、特殊场景:闭包被绑定 this 时
如果通过 call、apply、bind 等方法调用闭包,this 会被强制修改为指定对象。
const obj = {
name: "obj",
outer: function() {
return function inner() {
console.log(this.name);
};
}
};
const closure = obj.outer();
const otherObj = { name: "other" };
closure.call(otherObj); // 强制this指向otherObj → 输出: "other"
总结:闭包中 this 的指向规则
默认情况:指向全局对象(非严格模式)或 undefined(严格模式)。
箭头函数闭包:继承外层作用域的 this,与外层函数 this 一致。
显式绑定:通过 call/apply/bind 调用时,this 指向绑定的对象。
常见解决方案:用变量缓存外层 this 或使用箭头函数,确保闭包能访问预期的 this。