JavaScript 闭包和作用域学习笔记
作用域(Scope)
作用域决定了变量和函数的可访问性。JavaScript 有三种作用域:全局作用域、函数作用域和块作用域。
全局作用域
javascript
var globalVar = "我是全局变量";
function test() {
console.log(globalVar); // "我是全局变量"
}
test();
console.log(globalVar); // "我是全局变量"函数作用域
javascript
function test() {
var functionVar = "我是函数作用域变量";
console.log(functionVar); // "我是函数作用域变量"
}
test();
// console.log(functionVar); // ReferenceError: functionVar is not defined块作用域(ES6+)
javascript
if (true) {
let blockVar = "我是块作用域变量";
const blockConst = "我也是块作用域变量";
console.log(blockVar); // "我是块作用域变量"
}
// console.log(blockVar); // ReferenceError: blockVar is not definedTIP
let 和 const 具有块作用域,而 var 只有函数作用域。
作用域链
JavaScript 引擎通过作用域链查找变量。
javascript
var globalVar = "全局变量";
function outer() {
var outerVar = "外部变量";
function inner() {
var innerVar = "内部变量";
console.log(innerVar); // 内部变量
console.log(outerVar); // 外部变量
console.log(globalVar); // 全局变量
}
inner();
}
outer();变量查找规则
- 首先在当前作用域查找
- 如果找不到,向上一级作用域查找
- 一直查找到全局作用域
- 如果全局作用域也找不到,抛出 ReferenceError
闭包(Closure)
闭包是指函数能够访问其外部作用域中的变量,即使外部函数已经执行完毕。
基本示例
javascript
function outer() {
var outerVar = "外部变量";
function inner() {
console.log(outerVar); // 访问外部变量
}
return inner;
}
const innerFunc = outer();
innerFunc(); // "外部变量"闭包的经典应用:计数器
javascript
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
const counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (独立的计数器)
console.log(counter1()); // 3闭包实现私有变量
javascript
function createBankAccount(initialBalance) {
let balance = initialBalance; // 私有变量
return {
deposit: function(amount) {
balance += amount;
return balance;
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
} else {
return "余额不足";
}
},
getBalance: function() {
return balance;
}
};
}
const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
console.log(account.deposit(500)); // 1500
console.log(account.withdraw(200)); // 1300
// console.log(balance); // ReferenceError: balance is not defined闭包在循环中的问题
javascript
// 问题代码
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出: 3, 3, 3
}, 1000);
}DANGER
使用 var 在循环中创建闭包会导致所有闭包共享同一个变量。
解决方案 1:使用 let
javascript
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出: 0, 1, 2
}, 1000);
}解决方案 2:使用 IIFE(立即执行函数)
javascript
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 输出: 0, 1, 2
}, 1000);
})(i);
}解决方案 3:使用 bind
javascript
for (var i = 0; i < 3; i++) {
setTimeout(function(j) {
console.log(j); // 输出: 0, 1, 2
}.bind(null, i), 1000);
}闭包的实际应用
函数工厂
javascript
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15模块模式
javascript
const myModule = (function() {
let privateVar = 0;
function privateFunction() {
return privateVar;
}
return {
publicMethod: function() {
privateVar++;
return privateFunction();
},
anotherPublicMethod: function() {
return privateVar;
}
};
})();
console.log(myModule.publicMethod()); // 1
console.log(myModule.anotherPublicMethod()); // 1
// console.log(myModule.privateVar); // undefined防抖(Debounce)
javascript
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
const debouncedSearch = debounce(function(query) {
console.log("搜索:", query);
}, 300);
// 用户输入时,只有停止输入 300ms 后才会执行搜索
debouncedSearch("JavaScript");节流(Throttle)
javascript
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
const throttledScroll = throttle(function() {
console.log("滚动事件");
}, 1000);内存泄漏注意事项
闭包可能导致内存泄漏,因为闭包会保持对外部变量的引用。
javascript
// 可能导致内存泄漏的示例
function attachHandler() {
const largeData = new Array(1000000).fill("data");
document.getElementById("button").addEventListener("click", function() {
// 这个闭包持有 largeData 的引用
console.log("按钮被点击");
});
}WARNING
如果闭包持有大量数据的引用,可能导致内存泄漏。使用完毕后记得清理引用。
总结
- 作用域:决定了变量的可访问性
- 作用域链:JavaScript 通过作用域链查找变量
- 闭包:函数能够访问外部作用域的变量
- 应用:闭包常用于实现私有变量、函数工厂、防抖节流等