03-闭包与作用域链
分类:03-异步编程进阶
发布于:
阅读时间:101 分钟
闭包与作用域链
📋 学习目标
- 深入理解闭包的概念和形成原理
- 掌握作用域链的工作机制
- 学会闭包的实际应用场景
- 理解闭包的内存管理和性能影响
- 掌握常见闭包陷阱的解决方案
🎯 作用域基础
1. 作用域类型
// 全局作用域(Global Scope)
var globalVar = 'I am global';
function globalFunction() {
console.log(globalVar); // 可以访问全局变量
}
// 函数作用域(Function Scope)
function outerFunction() {
var outerVar = 'I am outer';
function innerFunction() {
var innerVar = 'I am inner';
console.log(globalVar); // 可以访问全局变量
console.log(outerVar); // 可以访问外层函数变量
console.log(innerVar); // 可以访问自己的变量
}
innerFunction();
// console.log(innerVar); // ReferenceError: 无法访问内层函数变量
}
outerFunction();
// console.log(outerVar); // ReferenceError: 无法访问函数内部变量
// 块级作用域(Block Scope)- ES6
function blockScopeDemo() {
if (true) {
let blockVar = 'I am block scoped';
const blockConst = 'I am also block scoped';
console.log(blockVar); // 可以访问
}
// console.log(blockVar); // ReferenceError: 无法访问块级变量
}
blockScopeDemo();
// 作用域嵌套
var level1 = 'global';
function level1() {
var level1 = 'level1';
function level2() {
var level2 = 'level2';
function level3() {
var level3 = 'level3';
// 作用域链查找
console.log(level3); // 'level3' (在当前作用域找到)
console.log(level2); // 'level2' (在外层作用域找到)
console.log(level1); // 'level1' (再外层作用域找到)
// console.log(global); // ReferenceError (未定义)
}
level3();
}
level2();
}
level1();
2. 作用域链查找机制
// 作用域链:从内到外查找变量
var name = 'Global';
function outer() {
var name = 'Outer';
function inner() {
var name = 'Inner';
console.log(name); // 'Inner' (在内层作用域找到)
}
function inner2() {
console.log(name); // 'Outer' (在outer作用域找到)
}
inner();
inner2();
}
outer();
// 变量遮蔽(Variable Shadowing)
var value = 'Global value';
function testShadowing() {
var value = 'Local value';
console.log(value); // 'Local value' (局部变量遮蔽了全局变量)
function nested() {
var value = 'Nested value';
console.log(value); // 'Nested value' (内层变量遮蔽了外层变量)
}
nested();
}
testShadowing();
console.log(value); // 'Global value'
🔐 闭包(Closure)
1. 闭包的定义和原理
// 闭包:函数能够访问其外部作用域的变量,即使外部函数已经执行完毕
function outerFunction(x) {
// 外部函数的局部变量
var outerVariable = 'I am from outer';
// 内部函数形成闭包
function innerFunction(y) {
// 可以访问外部函数的变量
console.log(outerVariable); // 'I am from outer'
console.log(x); // 外部参数
console.log(y); // 内部参数
return x + y;
}
return innerFunction; // 返回内部函数
}
// 创建闭包
const closureFunction = outerFunction(10);
// 即使outerFunction已经执行完毕,innerFunction仍然可以访问其变量
console.log(closureFunction(5)); // 15 (10 + 5)
// 每次调用outerFunction都会创建新的闭包
const anotherClosure = outerFunction(20);
console.log(anotherClosure(3)); // 23 (20 + 3)
2. 闭包的经典示例
// 示例1:计数器闭包
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
},
reset: function() {
count = 0;
return count;
}
};
}
const counter1 = createCounter();
console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter1.getCount()); // 2
const counter2 = createCounter(); // 独立的闭包
console.log(counter2.increment()); // 1
// 示例2:私有变量
function createPerson(name) {
// 私有变量
let _name = name;
let _age = 0;
return {
getName: function() {
return _name;
},
getAge: function() {
return _age;
},
setAge: function(newAge) {
if (newAge >= 0 && newAge < 150) {
_age = newAge;
} else {
throw new Error('Invalid age');
}
},
celebrateBirthday: function() {
_age++;
return `Happy ${_age}th birthday, ${_name}!`;
}
};
}
const person = createPerson('Alice');
console.log(person.getName()); // 'Alice'
console.log(person.celebrateBirthday()); // 'Happy 1st birthday, Alice!'
console.log(person.getAge()); // 1
// person._name = 'Bob'; // 无法直接访问私有变量
3. 闭包与循环
// 陷阱:循环中的闭包
function createFunctions() {
const functions = [];
for (var i = 0; i < 3; i++) {
functions[i] = function() {
return i; // 所有函数都引用同一个i
};
}
return functions;
}
const functions = createFunctions();
console.log(functions[0]()); // 3
console.log(functions[1]()); // 3
console.log(functions[2]()); // 3
// 解决方案1:使用IIFE
function createFunctionsFixed1() {
const functions = [];
for (var i = 0; i < 3; i++) {
functions[i] = (function(j) {
return function() {
return j; // 每个函数捕获不同的j
};
})(i);
}
return functions;
}
const fixedFunctions1 = createFunctionsFixed1();
console.log(fixedFunctions1[0]()); // 0
console.log(fixedFunctions1[1]()); // 1
console.log(fixedFunctions1[2]()); // 2
// 解决方案2:使用let(块级作用域)
function createFunctionsFixed2() {
const functions = [];
for (let i = 0; i < 3; i++) {
functions[i] = function() {
return i; // 每次循环创建新的块级作用域
};
}
return functions;
}
const fixedFunctions2 = createFunctionsFixed2();
console.log(fixedFunctions2[0]()); // 0
console.log(fixedFunctions2[1]()); // 1
console.log(fixedFunctions2[2]()); // 2
🚀 闭包的实际应用
1. 函数工厂
// 创建特定功能的函数
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(quadruple(5)); // 20
// 创建验证器
function createValidator(rules) {
return function(data) {
const errors = [];
for (const [field, rule] of Object.entries(rules)) {
if (!rule.test(data[field])) {
errors.push(`${field}: ${rule.message}`);
}
}
return {
isValid: errors.length === 0,
errors
};
};
}
const userValidator = createValidator({
name: {
test: name => name && name.length >= 2,
message: 'Name must be at least 2 characters'
},
email: {
test: email => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
message: 'Invalid email format'
},
age: {
test: age => age >= 18 && age <= 120,
message: 'Age must be between 18 and 120'
}
});
const userData = { name: 'A', email: 'invalid', age: 25 };
const validationResult = userValidator(userData);
console.log(validationResult);
// {
// isValid: false,
// errors: ['Name must be at least 2 characters', 'Invalid email format']
// }
2. 事件处理器
// 闭包在事件处理中的应用
function setupButtons() {
const buttons = document.querySelectorAll('button');
const messages = ['Button 1 clicked', 'Button 2 clicked', 'Button 3 clicked'];
buttons.forEach((button, index) => {
button.addEventListener('click', function() {
// 闭包保存了index和messages
console.log(messages[index]);
alert(messages[index]);
});
});
}
// 实际应用:点击计数器
function createClickCounter(maxClicks = 5) {
let clickCount = 0;
return function() {
clickCount++;
if (clickCount <= maxClicks) {
console.log(`Click ${clickCount} of ${maxClicks}`);
return `You clicked ${clickCount} times`;
} else {
console.log('Maximum clicks reached');
return 'Maximum clicks reached';
}
};
}
const clickCounter = createClickCounter(3);
console.log(clickCounter()); // 'You clicked 1 times'
console.log(clickCounter()); // 'You clicked 2 times'
console.log(clickCounter()); // 'You clicked 3 times'
console.log(clickCounter()); // 'Maximum clicks reached'
3. 数据缓存
// 函数结果缓存
function memoize(fn) {
const cache = new Map();
return function(...args) {
// 创建缓存键
const key = JSON.stringify(args);
// 检查缓存
if (cache.has(key)) {
console.log('From cache:', key);
return cache.get(key);
}
// 计算结果并缓存
const result = fn.apply(this, args);
cache.set(key, result);
console.log('Computed and cached:', key);
return result;
};
}
// 昂贵的计算函数
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 创建记忆化版本
const memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(10)); // 计算并缓存
console.log(memoizedFibonacci(10)); // 从缓存获取
console.log(memoizedFibonacci(15)); // 计算并缓存
console.log(memoizedFibonacci(10)); // 从缓存获取
// API请求缓存
function createApiCache() {
const cache = new Map();
const pending = new Map();
return async function(url, options = {}) {
const cacheKey = `${url}:${JSON.stringify(options)}`;
// 检查缓存
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
// 检查是否有正在进行的请求
if (pending.has(cacheKey)) {
return pending.get(cacheKey);
}
// 发起新请求
const promise = fetch(url, options)
.then(response => response.json())
.then(data => {
cache.set(cacheKey, data);
pending.delete(cacheKey);
return data;
})
.catch(error => {
pending.delete(cacheKey);
throw error;
});
pending.set(cacheKey, promise);
return promise;
};
}
const apiCache = createApiCache();
// 使用缓存API
async function fetchUserData(userId) {
return await apiCache(`/api/users/${userId}`);
}
4. 模块模式
// 使用闭包创建模块
const ShoppingModule = (function() {
// 私有变量
let cart = [];
let total = 0;
// 私有函数
function calculateTotal() {
total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// 公共接口
return {
addItem: function(item) {
cart.push(item);
calculateTotal();
console.log(`Added ${item.name} to cart`);
},
removeItem: function(itemId) {
cart = cart.filter(item => item.id !== itemId);
calculateTotal();
console.log(`Removed item ${itemId} from cart`);
},
getCart: function() {
return [...cart]; // 返回副本,防止外部修改
},
getTotal: function() {
return total;
},
clear: function() {
cart = [];
total = 0;
console.log('Cart cleared');
}
};
})();
// 使用模块
ShoppingModule.addItem({ id: 1, name: 'Book', price: 20, quantity: 2 });
ShoppingModule.addItem({ id: 2, name: 'Pen', price: 2, quantity: 5 });
console.log(ShoppingModule.getTotal()); // 50
console.log(ShoppingModule.getCart());
🔧 高级闭包技术
1. 柯里化(Currying)
// 柯里化:将多参数函数转换为单参数函数链
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
}
};
}
// 使用柯里化
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
// 实际应用:配置函数
function formatCurrency(symbol) {
return function(separator) {
return function(amount) {
return `${symbol}${amount.toFixed(2).replace('.', separator)}`;
};
};
}
const formatUSD = formatCurrency('$');
const formatEuro = formatCurrency('€');
const formatUSDWithComma = formatUSD(',');
const formatEuroWithDot = formatEuro('.');
console.log(formatUSDWithComma(1234.56)); // '$1,234.56'
console.log(formatEuroWithDot(1234.56)); // '€1234,56'
2. 函数组合
// 函数组合:将多个函数连接起来
function compose(...fns) {
return function(x) {
return fns.reduceRight((acc, fn) => fn(acc), x);
};
}
// 或者使用管道
function pipe(...fns) {
return function(x) {
return fns.reduce((acc, fn) => fn(acc), x);
};
}
// 实用函数
const add = x => x + 1;
const multiply = x => x * 2;
const subtract = x => x - 3;
// 组合函数
const processNumber = compose(subtract, multiply, add);
// 等同于 subtract(multiply(add(x)))
console.log(processNumber(5)); // ((5 + 1) * 2) - 3 = 9
// 实际应用:数据处理管道
const users = [
{ name: 'Alice', age: 25, salary: 50000 },
{ name: 'Bob', age: 30, salary: 60000 },
{ names: 'Charlie', age: 35, salary: 70000 }
];
const filterByAge = minAge => users => users.filter(user => user.age >= minAge);
const sortBySalary = users => [...users].sort((a, b) => b.salary - a.salary);
const extractNames = users => users.map(user => user.name);
const getHighEarners = pipe(
filterByAge(30),
sortBySalary,
extractNames
);
console.log(getHighEarners(users)); // ['Charlie', 'Bob']
3. 惰性求值
// 惰性求值:只在需要时计算值
function lazy(thunk) {
let computed = false;
let result;
return function() {
if (!computed) {
result = thunk();
computed = true;
}
return result;
};
}
// 使用示例
function expensiveComputation() {
console.log('Performing expensive computation...');
return Math.random() * 1000;
}
const lazyValue = lazy(expensiveComputation);
console.log('Function created');
console.log(lazyValue()); // 第一次调用时执行计算
console.log(lazyValue()); // 第二次调用时返回缓存值
// 惰性序列
function* lazySequence() {
let i = 0;
while (true) {
yield lazy(() => {
console.log(`Computing value ${i}`);
return i++;
})();
}
}
const sequence = lazySequence();
console.log(sequence.next().value()); // 计算并返回0
console.log(sequence.next().value()); // 计算并返回1
⚠️ 闭包的陷阱
1. 内存泄漏
// ❌ 问题:闭包导致内存泄漏
function createLeakyClosure() {
const largeData = new Array(1000000).fill('data');
return function() {
// 闭包引用了largeData,导致largeData无法被垃圾回收
return largeData.length;
};
}
const leakyFunction = createLeakyClosure();
// largeData仍然存在于内存中,直到leakyFunction被释放
// ✅ 解决方案:只保留必要的数据
function createOptimizedClosure() {
const largeData = new Array(1000000).fill('data');
const necessaryInfo = largeData.length; // 只保留需要的数据
return function() {
return necessaryInfo;
};
}
// 或者使用WeakMap
function createWeakMapClosure() {
const cache = new WeakMap();
return function(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = processData(obj);
cache.set(obj, result);
return result;
};
}
function processData(obj) {
// 模拟处理大对象
return Object.keys(obj).length;
}
2. this指向问题
// ❌ 问题:闭包中的this指向
const obj = {
value: 42,
getValue: function() {
return function() {
return this.value; // this指向全局对象或undefined
};
}
};
const getValueFunction = obj.getValue();
console.log(getValueFunction()); // undefined
// ✅ 解决方案1:保存this引用
const obj2 = {
value: 42,
getValue: function() {
const self = this;
return function() {
return self.value;
};
}
};
const getValueFunction2 = obj2.getValue();
console.log(getValueFunction2()); // 42
// ✅ 解决方案2:使用箭头函数
const obj3 = {
value: 42,
getValue: function() {
return () => this.value; // 箭头函数继承外层this
}
};
const getValueFunction3 = obj3.getValue();
console.log(getValueFunction3()); // 42
3. 循环引用
// ❌ 问题:循环引用导致内存无法释放
function createCircularReference() {
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1; // 循环引用
return function() {
return obj1;
};
}
// ✅ 解决方案:避免循环引用或使用WeakMap
function createSafeReference() {
const obj1 = {};
const obj2 = {};
// 避免循环引用
obj1.id = 'obj1';
obj2.id = 'obj2';
return function() {
return { obj1, obj2 };
};
}
📊 性能优化
1. 闭包性能考虑
// ❌ 避免在循环中创建闭包
function badExample(elements) {
for (let i = 0; i < elements.length; i++) {
elements[i].onclick = function() {
// 每次循环都创建新的闭包
console.log(i);
};
}
}
// ✅ 更好的方式:事件委托
function goodExample(container) {
container.addEventListener('click', function(e) {
if (e.target.classList.contains('item')) {
const index = Array.from(container.children).indexOf(e.target);
console.log(index);
}
});
}
// ✅ 或者使用data属性
function betterExample(elements) {
elements.forEach((element, index) => {
element.dataset.index = index;
element.addEventListener('click', function() {
console.log(this.dataset.index);
});
});
}
2. 闭包缓存策略
// 智能缓存:限制缓存大小
function createSmartCache(maxSize = 100) {
const cache = new Map();
const accessOrder = [];
return function(key, computeFn) {
if (cache.has(key)) {
// 更新访问顺序
const index = accessOrder.indexOf(key);
if (index > -1) {
accessOrder.splice(index, 1);
accessOrder.push(key);
}
return cache.get(key);
}
// 计算新值
const result = computeFn();
// 检查缓存大小
if (cache.size >= maxSize) {
const oldestKey = accessOrder.shift();
cache.delete(oldestKey);
}
cache.set(key, result);
accessOrder.push(key);
return result;
};
}
const smartCache = createSmartCache(50);
function expensiveOperation(key) {
console.log(`Computing for ${key}`);
return key * 2;
}
console.log(smartCache('a', () => expensiveOperation('a'))); // 计算
console.log(smartCache('a', () => expensiveOperation('a'))); // 缓存
📝 最佳实践
1. 闭包使用原则
// ✅ 1. 明确闭包的用途
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment: () => ++count,
decrement: () => --count,
reset: () => count = initialValue
};
}
// ✅ 2. 避免不必要的闭包
function processData(data) {
// 直接处理,不要创建不必要的闭包
return data.map(item => item * 2);
}
// ❌ 避免这样写
function processDataBad(data) {
const multiplier = 2;
return data.map(function(item) {
return item * multiplier; // 不必要的闭包
});
}
2. 内存管理
// ✅ 及时清理不需要的引用
function createTempClosure() {
const data = getLargeData();
const processData = () => {
const result = transformData(data);
data = null; // 清理引用
return result;
};
return processData;
}
// ✅ 使用WeakMap管理对象关联
const objectCache = new WeakMap();
function cacheObject(obj, processedData) {
objectCache.set(obj, processedData);
// 当obj被垃圾回收时,缓存条目也会被自动清理
}
3. 性能优化
// ✅ 减少闭包作用域
function optimizedFunction() {
const constant = computeConstant(); // 只计算一次
return function(variable) {
// 闭包只引用必要的变量
return constant + variable;
};
}
// ✅ 使用函数工厂减少重复代码
function createValidator(schema) {
const rules = Object.entries(schema);
return function(data) {
return rules.every(([field, rule]) => rule.test(data[field]));
};
}
🎯 小结
- 深入理解了闭包的概念和形成原理
- 掌握了作用域链的工作机制
- 学会了闭包的实际应用场景
- 理解了闭包的内存管理和性能影响
- 掌握了常见闭包陷阱的解决方案
- 学会了高级闭包技术:柯里化、函数组合、惰性求值
- 了解了闭包的性能优化和最佳实践
闭包是JavaScript中最强大的特性之一,正确使用闭包可以写出更优雅、更灵活的代码。
下一步学习:节流与防抖