函数与作用域:作用域与闭包¶
在JavaScript中,理解作用域和闭包是掌握语言核心概念的关键。作用域决定了变量的可见性和生命周期,而闭包则是一种强大的机制,允许函数访问其词法作用域中的变量,即使函数在其词法作用域之外执行。
1. 作用域¶
1.1 什么是作用域?¶
作用域(Scope)是指程序中变量、函数和对象的可访问范围。JavaScript中有两种主要的作用域:
- 全局作用域:在全局作用域中声明的变量可以在程序的任何地方访问。
- 局部作用域:在函数内部声明的变量只能在函数内部访问。
1.2 全局作用域与局部作用域¶
// 全局作用域
var globalVar = "I am global";
function myFunction() {
// 局部作用域
var localVar = "I am local";
console.log(globalVar); // 可以访问全局变量
console.log(localVar); // 可以访问局部变量
}
myFunction();
console.log(globalVar); // 可以访问全局变量
console.log(localVar); // 报错:localVar未定义
在上面的代码中,globalVar
是全局变量,可以在函数内外访问。而localVar
是局部变量,只能在myFunction
函数内部访问。
1.3 块级作用域¶
在ES6之前,JavaScript只有函数作用域,没有块级作用域。ES6引入了let
和const
关键字,使得变量可以在块级作用域中声明。
if (true) {
var varVariable = "I am var"; // 使用var声明,变量提升到函数作用域
let letVariable = "I am let"; // 使用let声明,块级作用域
const constVariable = "I am const"; // 使用const声明,块级作用域
}
console.log(varVariable); // 可以访问
console.log(letVariable); // 报错:letVariable未定义
console.log(constVariable); // 报错:constVariable未定义
在这个例子中,varVariable
由于使用var
声明,变量提升到最近的函数作用域,因此在if
块外仍然可以访问。而letVariable
和constVariable
由于使用let
和const
声明,只能在if
块内部访问。
2. 闭包¶
2.1 什么是闭包?¶
闭包(Closure)是指函数能够记住并访问其词法作用域中的变量,即使函数在其词法作用域之外执行。闭包是JavaScript中非常强大的特性,常用于创建私有变量、回调函数等。
2.2 闭包的基本示例¶
function outerFunction() {
var outerVar = "I am outer";
function innerFunction() {
console.log(outerVar); // 访问外部函数的变量
}
return innerFunction;
}
var closure = outerFunction();
closure(); // 输出: I am outer
在这个例子中,innerFunction
是一个闭包,它能够访问outerFunction
中的outerVar
变量,即使outerFunction
已经执行完毕。
2.3 闭包的应用:私有变量¶
闭包常用于创建私有变量,防止外部直接访问和修改。
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
console.log(count);
},
decrement: function() {
count--;
console.log(count);
}
};
}
const counter = createCounter();
counter.increment(); // 输出: 1
counter.increment(); // 输出: 2
counter.decrement(); // 输出: 1
在这个例子中,count
变量是私有的,只能通过increment
和decrement
方法来修改和访问。
2.4 闭包的应用:回调函数¶
闭包也常用于回调函数中,特别是在异步编程中。
function delayedGreeting(name) {
setTimeout(function() {
console.log("Hello, " + name);
}, 1000);
}
delayedGreeting("Alice"); // 1秒后输出: Hello, Alice
在这个例子中,setTimeout
的回调函数是一个闭包,它能够访问delayedGreeting
函数中的name
变量。
3. 练习题¶
3.1 简单题¶
- 编写一个函数
createMultiplier
,它接受一个数字x
,并返回一个函数,该函数接受一个数字y
并返回x * y
。
function createMultiplier(x) {
// 你的代码
}
const double = createMultiplier(2);
console.log(double(5)); // 输出: 10
3.2 中等题¶
- 使用闭包实现一个简单的缓存函数,该函数接受一个函数
fn
作为参数,并返回一个新的函数,该函数在第一次调用时缓存结果,后续调用直接返回缓存的结果。
function cacheFunction(fn) {
// 你的代码
}
function expensiveCalculation(x) {
console.log("Calculating...");
return x * x;
}
const cachedCalculation = cacheFunction(expensiveCalculation);
console.log(cachedCalculation(5)); // 输出: Calculating... 25
console.log(cachedCalculation(5)); // 输出: 25 (不再计算)
3.3 复杂题¶
- 使用闭包实现一个简单的模块系统,允许创建多个模块,每个模块都有自己的私有变量和公共方法。
function createModule() {
// 你的代码
}
const module1 = createModule();
module1.setName("Alice");
console.log(module1.getName()); // 输出: Alice
const module2 = createModule();
module2.setName("Bob");
console.log(module2.getName()); // 输出: Bob
4. 总结¶
- 作用域决定了变量的可见性和生命周期。JavaScript中有全局作用域和局部作用域,ES6引入了块级作用域。
- 闭包是函数能够记住并访问其词法作用域中的变量,即使函数在其词法作用域之外执行。闭包常用于创建私有变量、回调函数等。
- 理解作用域和闭包是掌握JavaScript编程的关键,它们在实际开发中有广泛的应用。
通过本主题的学习,你应该能够理解JavaScript中的作用域和闭包,并能够在实际编程中应用这些概念。