前言
JavaScript
中存在着闭包概念,也是前端面试经常提到得知识点,本文将从定义、工作原理、应用场景以及面试题等几个方面,对闭包进行简单而清晰的介绍!!!
什么是闭包
闭包是指一个函数能够记住并访问其词法作用域(Lexical Scope
)中的变量,即使这个函数在其定义时的作用域之外被调用。换句话说,闭包让函数可以"携带"其创建时环境中的变量,形成一个封闭的上下文。
简单来说,闭包由两部分组成:
- 函数本身:一个内部函数,定义在另一个函数内部。
- 外部环境:内部函数能够访问外部函数的变量,即使外部函数已经执行完毕。
javascript
function outerFunc(){
let name = "小猪Passion";
function innerFunc(){
console.log(name);
}
return innerFunc;
}
const func = outerFunc();
func(); // 输出:小猪Passion
上述代码中,innerFunc
函数形成了闭包,保留了对name
变量的引用,即使outerFunc
函数执行完毕,声明的name
变量依旧可以被访问到。
闭包工作原理
闭包的工作原理基于 JavaScript
的词法作用域 和垃圾回收机制。以下是闭包的核心机制:
- 词法作用域 :
JavaScript
中的函数作用域是在定义时决定的,而不是在调用时。这意味着内部函数可以访问外部函数的变量,因为它们在定义时共享了相同的词法环境。 - 变量生命周期:当外部函数执行完毕后,通常其变量会被垃圾回收。但如果内部函数被返回并在外部被引用,外部函数的变量会继续保存在内存中,形成闭包。
- 环境引用 :闭包会创建一个包含外部函数变量的引用,而不是复制这些变量的值。因此,外部变量的任何变化都会反映在闭包中。
javascript
function counter(){
let count = 0;
return function(){
count++;
}
}
const myCounter = counter();
console.log(myCounter()); // 输出: 0
console.log(myCounter()); // 输出: 1
console.log(myCounter()); // 输出: 2
在上述代码中,counter
函数返回的内部函数形成了闭包,保留了对 count
变量的引用。每次调用 myCounter
时,count
的值都会递增并保留。
闭包应用场景
- 构造函数的私有属性:模拟私有变量,隐藏数据,防止外部直接访问。
javascript
function createPerson(name) {
let age = 0;
return {
getName: () => name,
getAge: () => age,
growUp: () => age++
};
}
const person = createPerson("小猪Passion");
console.log(person.getName()); // 输出: 小猪Passion
console.log(person.getAge()); // 输出: 0
person.growUp();
console.log(person.getAge()); // 输出: 1
console.log(person.age); // 输出: undefined(age 不可直接访问)
- 函数柯里化:闭包可以用来创建柯里化函数,将多参数函数转化为一系列单参数函数。
javascript
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
console.log(add5(3)); // 输出: 8
- 模块开发:闭包被广泛用于模块模式(如
CommonJS
或IIFE
),实现代码的模块化和数据隔离。
javascript
const module = (function() {
let privateVar = "I am private";
return {
getVar: () => privateVar
};
})();
console.log(module.getVar()); // 输出: I am private
经典面试题
- 经典循环问题:
javascript
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
- 问题:上述代码会输出什么?如何修复以输出 0, 1, 2?
- 答案:
- 输出:3, 3, 3(因为 var 是函数作用域,setTimeout 的回调在循环结束后执行,i 已经是 3)。
- 修复方法 1:使用 let(块作用域):
javascript for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1000); }
- 修复方法 2:使用闭包:
javascript
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(() => console.log(index), 1000);
})(i);
}
- 函数防抖、节流:
在JavaScript
的函数的防抖和节流中也对闭包有应用,详细关于防抖、节流的介绍请看这篇文章🤌🤌🤌一文轻松拿捏防抖和节流 - 闭包内存问题 :
问题 :闭包会导致内存泄漏吗?为什么?
答案:闭包本身不会导致内存泄漏,但如果闭包引用了大量不必要的变量,且这些变量无法被垃圾回收,可能导致内存占用过高。解决方法是谨慎管理闭包中引用的变量,及时解除不需要的引用。
总结
闭包是 JavaScript
中一个强大且灵活的特性,理解其原理和应用场景对于编写高质量代码和应对面试至关重要。通过掌握闭包的定义、工作原理、实际应用以及常见面试题,开发者可以更深入地理解 JavaScript
的运行机制,并在开发中更高效地解决问题。
文章荐读: