-
核心定义:
- 闭包是指有权访问另一个函数作用域中变量的函数。 更精确地说,闭包是由函数以及声明该函数的词法环境组合而成的。这个环境包含了闭包创建时作用域内的任何局部变量。
- 即使外部函数已经执行完毕并从调用栈中弹出,其内部函数(闭包)仍然可以访问并操作外部函数作用域中的变量。
-
形成条件:
- 函数嵌套(一个函数定义在另一个函数内部)。
- 内部函数引用了外部函数作用域中的变量(或参数)。
- 内部函数在其外部函数的作用域之外被调用(例如,被返回、作为回调传递、被全局变量引用等)。
-
关键原理:
- 词法作用域: JavaScript 函数的作用域在定义时就确定了(写代码的位置),而不是在执行时确定。内部函数总能访问其定义时所处作用域链上的所有变量。
- 垃圾回收机制: 正常情况下,函数执行完毕后,其作用域内的变量会被标记为可回收。但如果一个内部函数(闭包)仍然持有对外部作用域变量的引用,那么这些变量就不会被垃圾回收器回收,因为它们对于闭包来说仍然是“可达的”。
-
常见用途:
-
数据封装与私有变量: 模拟私有方法/变量。外部函数的作用域变量只能通过内部函数(闭包)暴露的特定接口访问和修改,无法直接从外部访问。
function createCounter() { let count = 0; // "私有"变量 return { increment: function() { count++; }, getCount: function() { return count; } }; } const counter = createCounter(); counter.increment(); console.log(counter.getCount()); // 1 console.log(counter.count); // undefined (无法直接访问) -
模块模式: 构建模块化的代码结构,避免全局污染。这是现代模块系统(如 ES6 Module)出现前的主流方式。
-
高阶函数: 函数返回函数(例如:创建函数工厂、柯里化)。
function makeAdder(x) { return function(y) { return x + y; // 闭包记住了 x }; } const add5 = makeAdder(5); console.log(add5(3)); // 8 -
事件处理程序和回调函数: 在事件监听器或异步回调中,需要访问定义回调时上下文中的变量。
function setupButton(buttonId) { const button = document.getElementById(buttonId); let clickCount = 0; // 闭包将记住这个变量 button.addEventListener('click', function() { clickCount++; console.log(`Button ${buttonId} clicked ${clickCount} times`); }); } setupButton('myBtn');
-
-
注意事项:
- 内存泄漏: 如果闭包持有对大对象或 DOM 元素的引用,且不再需要这些引用时没有及时解除(例如移除事件监听器),这些对象就无法被垃圾回收,导致内存泄漏。在不需要闭包时(如组件卸载时),要记得清理(例如
removeEventListener)。 - 性能考量: 创建闭包比创建普通函数需要更多内存,因为需要存储整个作用域链。过度或不必要地使用闭包可能影响性能。
- 内存泄漏: 如果闭包持有对大对象或 DOM 元素的引用,且不再需要这些引用时没有及时解除(例如移除事件监听器),这些对象就无法被垃圾回收,导致内存泄漏。在不需要闭包时(如组件卸载时),要记得清理(例如
-
面试回答要点:
- 清晰定义:闭包是函数 + 其创建时的词法环境。
- 核心:内部函数访问外部函数作用域的变量,即使外部函数已执行完毕。
- 原理:词法作用域 + 垃圾回收机制(引用存在则不回收)。
- 作用:数据封装(私有变量)、模块化、高阶函数、回调保持上下文。
- 注意:潜在内存泄漏风险。
- 总结: 闭包核心是函数 + 创建时的词法环境。关键在于内部函数能在外部函数执行完毕后持久访问其作用域内的变量。用途广泛(封装、模块、高阶函数、回调),需注意内存管理。