JavaScript 深入理解JavaScript闭包
·
文章目录
1. 闭包的基本概念
MDN对闭包的定义
- 闭包是由捆绑起来(封闭的)的函数和函数周围状态(词法环境)的引用组合而成。
- 换言之,闭包让函数能访问它的外部作用域。
- 在 JavaScript 中,闭包会随着函数的创建而同时创建。
- 狭义的理解:JavaScript中一个函数,如果访问了外层作用域中的变量,那么它就是一个闭包
// 基本闭包示例
function outerFunction(x) {
// 外部函数的变量
var outerVariable = x;
// 内部函数
function innerFunction(y) {
// 内部函数可以访问外部函数的变量
return outerVariable + y;
}
// 返回内部函数
return innerFunction;
}
// 创建闭包
var closure = outerFunction(10);
console.log(closure(5)); // 输出: 15
// outerFunction已经执行完毕,但outerVariable仍然可以被访问
// 闭包示例1:定时器回调
function broadClosureExample() {
var timerVariable = "Timer data";
setTimeout(function() {
console.log(timerVariable); // 访问外部作用域变量
}, 1000);
// 这个回调函数也是的闭包
}
// 闭包示例2:事件处理器
function eventClosureExample() {
var eventData = "Event context";
document.addEventListener('click', function() {
console.log(eventData); // 访问外部变量
});
// 事件处理器也是的闭包
}
// 闭包示例3:数组方法回调
function arrayClosureExample() {
var multiplier = 2;
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map(function(num) {
return num * multiplier; // 访问外部变量
});
// map回调函数也是的闭包
}
闭包的本质
闭包由两部分组成:
- 函数:内部函数本身
- 词法环境:函数创建时的作用域链
// 闭包的内存结构示意
function createClosure() {
var localVar = "I'm local";
var counter = 0;
// 返回的函数形成了闭包
return function() {
counter++;
return {
value: localVar,
count: counter
};
};
}
var myClosure = createClosure();
// myClosure包含了:
// 1. 函数体代码
// 2. 对createClosure作用域的引用(包含localVar和counter)
2. 闭包的形成机制
作用域链和闭包
var globalVar = "global";
function outer() {
var outerVar = "outer";
function middle() {
var middleVar = "middle";
function inner() {
var innerVar = "inner";
// inner函数可以访问所有外部作用域的变量
console.log(globalVar); // "global"
console.log(outerVar); // "outer"
console.log(middleVar); // "middle"
console.log(innerVar); // "inner"
}
return inner;
}
return middle();
}
var closure = outer();
// closure保持对outer、middle作用域的引用
闭包的生命周期
function createCounter() {
var count = 0; // 这个变量不会被回收
// 返回的函数形成闭包,保持对count的引用
return function() {
count++;
return count;
};
}
var counter1 = createCounter();
var counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (独立的闭包实例)
// createCounter函数执行完毕,但count变量由于被闭包引用而不会被回收
3. 闭包的常见应用场景
1. 数据封装和私有变量
// 模块模式 - 创建私有变量
function createBankAccount(initialBalance) {
var balance = initialBalance; // 私有变量
return {
// 公共方法
deposit: function(amount) {
if (amount > 0) {
balance += amount;
return balance;
}
},
withdraw: function(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
return "Insufficient funds";
},
getBalance: function() {
return balance; // 通过闭包访问私有变量
}
};
}
var account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500
account.withdraw(200);
console.log(account.getBalance()); // 1300
// 无法直接访问balance变量
console.log(account.balance); // undefined
2. 函数工厂
// 创建特定功能的函数
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
var double = createMultiplier(2);
var triple = createMultiplier(3);
var square = createMultiplier(4);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(square(5)); // 20
// 每个函数都保持对不同multiplier值的引用
3. 回调函数和事件处理
// 事件处理中的闭包
function setupEventHandlers() {
var buttons = document.querySelectorAll('.button');
for (var i = 0; i < buttons.length; i++) {
// 闭包保持对i的引用
buttons[i].addEventListener('click', function() {
console.log('Button ' + i + ' clicked'); // 问题:总是输出最后一个i值
});
}
}
// 解决方案1:使用let创建块级作用域
function setupEventHandlersFixed1() {
var buttons = document.querySelectorAll('.button');
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('Button ' + i + ' clicked'); // 正确输出
});
}
}
// 解决方案2:使用闭包
function setupEventHandlersFixed2() {
var buttons = document.querySelectorAll('.button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', (function(index) {
return function() {
console.log('Button ' + index + ' clicked');
};
})(i));
}
}
4. 记忆化(Memoization)
// 使用闭包实现函数记忆化
function memoize(fn) {
var cache = {}; // 缓存结果
return function() {
var key = JSON.stringify(arguments);
if (key in cache) {
console.log('Cache hit for:', key);
return cache[key];
}
console.log('Computing result for:', key);
var result = fn.apply(this, arguments);
cache[key] = result;
return result;
};
}
// 斐波那契数列计算
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
var memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(10)); // 计算并缓存
console.log(memoizedFibonacci(10)); // 从缓存获取
console.log(memoizedFibonacci(8)); // 部分从缓存获取
5. 柯里化(Currying)
// 使用闭包实现函数柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
};
};
}
// 示例函数
function add(a, b, c) {
return a + b + c;
}
var curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
4. 闭包的高级应用
1. 迭代器模式
// 使用闭包创建迭代器
function createIterator(array) {
var index = 0;
return {
next: function() {
if (index < array.length) {
return {
value: array[index++],
done: false
};
}
return {
done: true
};
},
reset: function() {
index = 0;
}
};
}
var iterator = createIterator([1, 2, 3, 4, 5]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
2. 防抖和节流
// 防抖函数
function debounce(func, delay) {
var timeoutId;
return function(...args) {
// 清除之前的定时器
clearTimeout(timeoutId);
// 设置新的定时器
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 节流函数
function throttle(func, limit) {
var inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// 使用示例
var debouncedSearch = debounce(function(query) {
console.log('Searching for:', query);
}, 300);
var throttledScroll = throttle(function() {
console.log('Scrolling...');
}, 100);
3. 配置和状态管理
// 使用闭包管理配置
function createConfigManager(initialConfig) {
var config = Object.assign({}, initialConfig);
return {
get: function(key) {
return config[key];
},
set: function(key, value) {
config[key] = value;
},
getAll: function() {
return Object.assign({}, config);
},
reset: function() {
config = Object.assign({}, initialConfig);
}
};
}
var config = createConfigManager({
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
});
console.log(config.get('apiUrl')); // https://api.example.com
config.set('timeout', 10000);
console.log(config.getAll()); // { apiUrl: ..., timeout: 10000, retries: 3 }
5. 闭包的内存考虑
内存泄漏风险
// 潜在的内存泄漏
function createLeakyClosure() {
var largeData = new Array(1000000).fill('data'); // 大量数据
var element = document.getElementById('myElement');
// 闭包保持对largeData和element的引用
element.addEventListener('click', function() {
console.log(largeData.length);
});
// 问题:即使element被移除,largeData也不会被回收
}
// 解决方案:及时清理引用
function createSafeClosure() {
var largeData = new Array(1000000).fill('data');
var element = document.getElementById('myElement');
var handler = function() {
console.log(largeData.length);
};
element.addEventListener('click', handler);
// 提供清理方法
return function cleanup() {
element.removeEventListener('click', handler);
largeData = null; // 释放引用
element = null;
};
}
性能优化
// 闭包性能优化示例
function createOptimizedCounter() {
var count = 0;
// 避免在闭包中创建不必要的函数
function increment() {
return ++count;
}
function decrement() {
return --count;
}
// 返回对象而不是多个闭包
return {
increment: increment,
decrement: decrement,
getCount: function() {
return count;
}
};
}
// 对比:创建多个独立闭包(性能较差)
function createMultipleClosures() {
var count = 0;
return {
increment: function() { // 每次都创建新函数
return ++count;
},
decrement: function() { // 每次都创建新函数
return --count;
},
getCount: function() { // 每次都创建新函数
return count;
}
};
}
6. 闭包与其他概念的关系
闭包与this绑定
// 闭包中的this问题
var obj = {
name: 'MyObject',
createMethod: function() {
var self = this; // 保存this引用
return function() {
console.log(self.name); // 通过闭包访问正确的this
};
},
createArrowFunction: function() {
// 箭头函数自动绑定外层this
return () => {
console.log(this.name);
};
}
};
var method = obj.createMethod();
var arrowMethod = obj.createArrowFunction();
method(); // "MyObject"
arrowMethod(); // "MyObject"
闭包与模块模式
// 现代模块模式
var MyModule = (function() {
// 私有变量和函数
var privateVar = 'private';
function privateFunction() {
return 'This is private';
}
// 公共API
return {
publicVar: 'public',
publicMethod: function() {
// 通过闭包访问私有成员
return privateFunction() + ' and ' + privateVar;
},
setPrivateVar: function(value) {
privateVar = value;
},
getPrivateVar: function() {
return privateVar;
}
};
})();
console.log(MyModule.publicMethod()); // "This is private and private"
MyModule.setPrivateVar('modified');
console.log(MyModule.getPrivateVar()); // "modified"
7. 闭包的最佳实践
最佳实践总结
// 1. 合理使用闭包,避免过度嵌套
// 不好的做法
function badExample() {
return function() {
return function() {
return function() {
return "Too deep!";
};
};
};
}
// 2. 及时清理不需要的闭包引用
function goodCleanup() {
var largeData = new Array(1000000);
var process = function() {
// 处理数据
return largeData.length;
};
// 使用完后清理
var result = process();
largeData = null; // 释放引用
return result;
}
// 3. 使用WeakMap处理DOM相关的闭包
function attachDataToElement() {
var elementData = new WeakMap();
return {
setData: function(element, data) {
elementData.set(element, data);
},
getData: function(element) {
return elementData.get(element);
}
// WeakMap不会阻止元素被垃圾回收
};
}
// 4. 避免在循环中创建不必要的闭包
function efficientEventBinding() {
var buttons = document.querySelectorAll('.button');
// 使用事件委托而不是为每个按钮创建闭包
document.body.addEventListener('click', function(event) {
if (event.target.classList.contains('button')) {
var index = Array.prototype.indexOf.call(buttons, event.target);
console.log('Button', index, 'clicked');
}
});
}
闭包是JavaScript中一个强大而重要的概念,它使得函数式编程、模块化开发和数据封装成为可能。正确理解和使用闭包,能够帮助开发者编写出更加优雅和高效的JavaScript代码。同时也要注意闭包可能带来的内存问题,合理使用才能发挥其最大价值。
更多推荐


所有评论(0)