𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

JavaScript 闭包深入理解

闭包是 JavaScript 中最强大也是最容易被误解的概念之一。本文将深入解释闭包的机制、内存管理、性能优化和实际应用场景。

一、闭包的基础概念

1.1 什么是闭包?

闭包是指有权访问另一个函数作用域中变量的函数。简单来说,当一个内部函数引用了外部函数的变量时,就形成了闭包。

1.2 闭包的形成

function outer() {
let outerVar = '外部变量';

function inner() {
console.log(outerVar); // 访问外部函数变量
}

return inner;
}

const myClosure = outer();
myClosure(); // 输出: "外部变量"

执行过程

  1. 调用 outer() 函数
  2. 创建 outer 函数的作用域
  3. 创建 inner 函数的作用域
  4. inner 函数可以访问 outer 函数的变量
  5. outer 函数执行完毕,但 inner 函数仍然保留对外部变量的引用
  6. 返回 inner 函数

1.3 闭包的特点

  1. 可以访问外部作用域的变量
  2. 外部作用域的变量在闭包创建后仍然存在
  3. 闭包可以保持对变量的引用

二、作用域和作用域链

2.1 作用域的概念

作用域是变量的有效范围。JavaScript 有以下几种作用域:

全局作用域

// 全局变量
let globalVar = '全局变量';

function globalFunction() {
console.log(globalVar);
}

globalFunction(); // 访问全局变量

函数作用域

function functionScope() {
let funcVar = '函数变量';

function inner() {
console.log(funcVar);
}

inner(); // 输出: "函数变量"
}

// funcVar 在这里无法访问
// functionScope(); // Error: funcVar is not defined

块级作用域(ES6+)

// let 和 const 创建块级作用域
if (true) {
let blockVar = '块级变量';
console.log(blockVar); // 可以访问
}

// console.log(blockVar); // Error: blockVar is not defined

2.2 作用域链

function outer() {
let outerVar = '外部变量';

function inner() {
let innerVar = '内部变量';

// 访问内部变量
console.log(innerVar);

// 访问外部变量
console.log(outerVar);

// 尝试访问不存在的变量
// console.log(undefinedVar); // 输出: undefined
}

inner();
}

outer();

// innerVar 无法在外部访问
// console.log(innerVar); // Error: innerVar is not defined

作用域链

  1. 查找当前函数的变量
  2. 查找外部函数的变量
  3. 查找全局变量
  4. 查找不存在的变量返回 undefined

三、闭包的内存管理

3.1 内存占用

function createClosure() {
const largeData = new Array(1000000).fill('data');

function processData() {
console.log(largeData.length);
}

return processData;
}

const closure = createClosure();
closure(); // 输出: 1000000

// 问题:largeData 不会被垃圾回收

内存泄漏问题

闭包会保持对外部变量的引用,即使外部函数已经执行完毕,这些变量仍然存在于内存中。

3.2 内存泄漏

function setupEvent() {
let count = 0;

function handler() {
count++;
console.log(count);
}

document.getElementById('btn').addEventListener('click', handler);

return function cleanup() {
document.removeEventListener('click', handler);
};
}

const cleanup = setupEvent();
cleanup(); // 清理事件监听器,释放闭包

3.3 清理闭包

// 方式1:清空引用
function myClosure() {
let data = '敏感数据';

function inner() {
console.log(data);
}

return inner;
}

const closure = myClosure();
closure();
closure = null; // 清空引用,释放内存

// 方式2:不再使用闭包
function myClosure() {
let data = '敏感数据';

function inner() {
console.log(data);
}

return inner;
}

const closure = myClosure();
closure();
// 不再使用 closure,垃圾回收器会回收

四、闭包的实际应用

4.1 数据私有化

function createCounter() {
let count = 0;

return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}

const counter = createCounter();

console.log(counter.increment()); // 输出: 1
console.log(counter.increment()); // 输出: 2
console.log(counter.getCount()); // 输出: 2
console.log(counter.decrement()); // 输出: 1

// count 变量无法直接访问
// console.log(count); // Error

4.2 柯里化函数

function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}

const add = curry((a, b, c) => a + b + c);

const addFive = add(5);
const addFiveAndTen = addFive(10);
console.log(addFiveAndTen); // 输出: 25

4.3 闭包作为参数

function useClosure(callback) {
const value = '闭包值';

callback(() => {
console.log(value);
});
}

useClosure((fn) => {
fn(); // 输出: "闭包值"
});

4.4 高阶函数

// 过滤器
function filter(callback) {
const closure = function(...args) {
return args.filter(callback);
};
return closure;
}

const positiveNumbers = filter((n) => n > 0);
console.log(positiveNumbers(-1, 2, -3, 4, 5)); // 输出: [2, 4, 5]

4.5 事件处理

function setupEvent() {
let count = 0;

function handleClick() {
count++;
console.log(`点击次数: ${count}`);
}

return handleClick;
}

const handleClick = setupEvent();
document.getElementById('btn').addEventListener('click', handleClick);

4.6 生成器函数

function* generateSequence() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}

const generator = generateSequence();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }

五、闭包的性能考虑

5.1 性能影响

function createClosure() {
const largeData = new Array(1000000).fill('data');

return function() {
console.log(largeData.length);
};
}

const closure = createClosure();
closure(); // 调用闭包

// 问题:每次调用都会创建闭包
function createClosure() {
const largeData = new Array(1000000).fill('data');

return function() {
console.log(largeData.length);
};
}

// 不好的做法
for (let i = 0; i < 1000; i++) {
createClosure()(); // 每次都创建新闭包
}

// 好的做法
const closure = createClosure();
for (let i = 0; i < 1000; i++) {
closure(); // 复用同一个闭包
}

5.2 性能优化

// 优化1:避免在循环中创建闭包
// 不好的做法
for (let i = 0; i < items.length; i++) {
(function(index) {
items[index].addEventListener('click', function() {
console.log(index);
});
})(i);
}

// 好的做法
items.forEach((item, index) => {
item.addEventListener('click', function() {
console.log(index);
});
});

// 优化2:避免不必要的闭包
function processData(data) {
const processed = data.map(item => item.toUpperCase());

return processed;
}

// 优化3:使用箭头函数避免不必要的闭包
function add(a, b) {
return a + b;
}

// 不好的做法
const addWithClosure = (function(a, b) {
return function() {
return a + b;
};
})(1, 2);

// 好的做法
const addWithClosure = (a, b) => a + b;

六、常见误区

6.1 认为闭包会一直占用内存

function createClosure() {
let count = 0;

return function() {
count++;
console.log(count);
};
}

const closure = createClosure();
closure();
closure = null; // 清空引用,内存可以被回收

6.2 误用闭包

// 不好的做法:过度使用闭包
function factory() {
const data = new Array(1000).fill('data');
return function() {
console.log(data);
};
}

// 好的做法:根据需要使用闭包
function factory() {
return {
getData: function() {
return data;
},
processData: function() {
return data.map(item => item.toUpperCase());
}
};
}

6.3 闭包导致性能问题

// 不好的做法:在循环中创建闭包
for (let i = 0; i < 1000; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}

// 好的做法:使用立即执行函数
for (let i = 0; i < 1000; i++) {
(function(index) {
setTimeout(() => {
console.log(index);
}, 100);
})(i);
}

// 或者使用箭头函数
for (let i = 0; i < 1000; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}

七、高级应用

7.1 函数式编程

// 防抖函数
function debounce(fn, delay) {
let timer = null;

return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}

const debouncedFn = debounce(() => {
console.log('防抖执行');
}, 1000);

// 节流函数
function throttle(fn, limit) {
let lastTime = 0;

return function(...args) {
const now = Date.now();

if (now - lastTime >= limit) {
fn.apply(this, args);
lastTime = now;
}
};
}

const throttledFn = throttle(() => {
console.log('节流执行');
}, 1000);

7.2 单例模式

const Singleton = (function() {
let instance = null;

return {
getInstance: function() {
if (!instance) {
instance = {
data: '单例数据',
method: function() {
console.log(this.data);
}
};
}
return instance;
}
};
})();

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();

console.log(s1 === s2); // 输出: true

7.3 工厂模式

function createPerson(name, age) {
return {
name,
age,
greet() {
console.log(`你好, 我是 ${this.name}`);
}
};
}

const person1 = createPerson('张三', 25);
const person2 = createPerson('李四', 30);

person1.greet(); // 输出: 你好, 我是 张三
person2.greet(); // 输出: 你好, 我是 李四

7.4 策略模式

function calculate(a, b, strategy) {
return strategy(a, b);
}

const addStrategy = (a, b) => a + b;
const multiplyStrategy = (a, b) => a * b;

console.log(calculate(2, 3, addStrategy)); // 输出: 5
console.log(calculate(2, 3, multiplyStrategy)); // 输出: 6

八、总结

8.1 闭包的核心要点

  1. 闭包可以访问外部函数的变量
  2. 外部函数的变量在闭包创建后仍然存在
  3. 闭包会保持对外部变量的引用
  4. 可能导致内存泄漏

8.2 闭包的应用场景

  1. 数据私有化
  2. 柯里化函数
  3. 高阶函数
  4. 事件处理
  5. 生成器函数

8.3 最佳实践

  1. 合理使用闭包:在需要的地方使用,避免过度使用
  2. 及时清理引用:不再使用时清空闭包引用
  3. 优化性能:避免在循环中创建闭包
  4. 文档注释:为闭包添加清晰的注释

掌握闭包,写出更强大的 JavaScript 代码!