JavaScript作用域
在本章节中,我们将学习JavaScript作用域的相关知识,包括作用域的定义、类型、作用域链和变量提升等。
1. 什么是作用域?
作用域是指变量和函数的可访问范围,它决定了代码中的变量和函数在何处可以被访问。作用域可以控制变量的可见性和生命周期,防止变量被意外修改或访问。
简单来说,作用域就是变量和函数的有效范围。在JavaScript中,作用域可以分为以下几种类型:
- 全局作用域
- 函数作用域
- 块级作用域
2. 全局作用域
全局作用域是指在所有函数和代码块之外定义的变量和函数,它们可以在代码的任何地方被访问。
2.1 全局变量
在全局作用域中定义的变量称为全局变量。
// 全局变量
var globalVar = "我是全局变量";
let globalLet = "我也是全局变量";
const globalConst = "我是全局常量";
function test() {
// 可以访问全局变量
console.log(globalVar); // 输出:我是全局变量
console.log(globalLet); // 输出:我也是全局变量
console.log(globalConst); // 输出:我是全局常量
}
test();
// 在函数外部也可以访问全局变量
console.log(globalVar); // 输出:我是全局变量2.2 全局对象
在浏览器环境中,全局变量会成为全局对象window的属性。
var globalVar = "我是全局变量";
console.log(window.globalVar); // 输出:我是全局变量
console.log(globalVar === window.globalVar); // 输出:true注意:使用let和const声明的全局变量不会成为全局对象的属性。
let globalLet = "我是全局变量";
const globalConst = "我是全局常量";
console.log(window.globalLet); // 输出:undefined
console.log(window.globalConst); // 输出:undefined3. 函数作用域
函数作用域是指在函数内部定义的变量和函数,它们只能在函数内部被访问。
3.1 局部变量
在函数内部定义的变量称为局部变量。
function test() {
// 局部变量
var localVar = "我是局部变量";
let localLet = "我也是局部变量";
const localConst = "我是局部常量";
console.log(localVar); // 输出:我是局部变量
console.log(localLet); // 输出:我也是局部变量
console.log(localConst); // 输出:我是局部常量
}
test();
// 在函数外部无法访问局部变量
console.log(localVar); // 报错:localVar is not defined
console.log(localLet); // 报错:localLet is not defined
console.log(localConst); // 报错:localConst is not defined3.2 函数参数
函数参数也是函数作用域内的局部变量。
function test(param1, param2) {
// param1和param2是函数作用域内的局部变量
console.log(param1); // 输出:1
console.log(param2); // 输出:2
}
test(1, 2);
// 在函数外部无法访问函数参数
console.log(param1); // 报错:param1 is not defined4. 块级作用域
块级作用域是指在代码块(由{}包围的代码)内部定义的变量,它们只能在代码块内部被访问。
块级作用域是ES6(2015)引入的新特性,通过let和const关键字实现。
4.1 块级作用域示例
// if语句块
if (true) {
var varInsideIf = "var声明的变量";
let letInsideIf = "let声明的变量";
const constInsideIf = "const声明的变量";
}
console.log(varInsideIf); // 输出:var声明的变量(var没有块级作用域)
console.log(letInsideIf); // 报错:letInsideIf is not defined(let有块级作用域)
console.log(constInsideIf); // 报错:constInsideIf is not defined(const有块级作用域)
// for循环块
for (var i = 0; i < 5; i++) {
// 循环体
}
console.log(i); // 输出:5(var没有块级作用域)
for (let j = 0; j < 5; j++) {
// 循环体
}
console.log(j); // 报错:j is not defined(let有块级作用域)
// 函数块
function test() {
let insideFunction = "函数内部的变量";
}
console.log(insideFunction); // 报错:insideFunction is not defined4.2 var、let和const的作用域对比
| 关键字 | 作用域 | 变量提升 | 重复声明 | 重新赋值 |
|---|---|---|---|---|
| var | 函数作用域 | 支持 | 允许 | 允许 |
| let | 块级作用域 | 不支持 | 不允许 | 允许 |
| const | 块级作用域 | 不支持 | 不允许 | 不允许 |
5. 作用域链
当访问一个变量时,JavaScript引擎会先在当前作用域中查找该变量,如果找不到,就会向上查找父级作用域,直到找到该变量或到达全局作用域。这种由多个作用域组成的链式结构称为作用域链。
5.1 作用域链示例
// 全局作用域
let globalVar = "全局变量";
function outer() {
// 外部函数作用域
let outerVar = "外部函数变量";
function inner() {
// 内部函数作用域
let innerVar = "内部函数变量";
// 可以访问内部函数变量
console.log(innerVar); // 输出:内部函数变量
// 可以访问外部函数变量
console.log(outerVar); // 输出:外部函数变量
// 可以访问全局变量
console.log(globalVar); // 输出:全局变量
}
inner();
// 可以访问外部函数变量
console.log(outerVar); // 输出:外部函数变量
// 可以访问全局变量
console.log(globalVar); // 输出:全局变量
// 无法访问内部函数变量
console.log(innerVar); // 报错:innerVar is not defined
}
outer();
// 可以访问全局变量
console.log(globalVar); // 输出:全局变量
// 无法访问外部函数变量
console.log(outerVar); // 报错:outerVar is not defined
// 无法访问内部函数变量
console.log(innerVar); // 报错:innerVar is not defined5.2 作用域链的查找顺序
作用域链的查找顺序是从内到外,即:
- 当前函数作用域
- 外部函数作用域
- 全局作用域
如果在全局作用域中仍然找不到该变量,就会抛出ReferenceError错误。
6. 变量提升
变量提升是指JavaScript引擎在执行代码之前,会将变量声明提升到当前作用域的顶部。这意味着可以在变量声明之前使用变量。
6.1 var的变量提升
使用var声明的变量会被提升到当前作用域的顶部。
// 变量提升示例
console.log(x); // 输出:undefined(变量被提升,但值未被初始化)
var x = 10;
console.log(x); // 输出:10
// 上面的代码等价于:
var x;
console.log(x); // undefined
x = 10;
console.log(x); // 106.2 let和const的变量提升
使用let和const声明的变量也会被提升,但不会被初始化,而是处于"暂时性死区"(Temporal Dead Zone,TDZ)中,在声明之前访问会抛出错误。
// let的暂时性死区
console.log(y); // 报错:Cannot access 'y' before initialization
let y = 20;
// const的暂时性死区
console.log(z); // 报错:Cannot access 'z' before initialization
const z = 30;6.3 函数声明的提升
函数声明也会被提升到当前作用域的顶部,可以在函数声明之前调用函数。
// 函数声明提升示例
test(); // 输出:我是函数声明
function test() {
console.log("我是函数声明");
}
// 上面的代码等价于:
function test() {
console.log("我是函数声明");
}
test(); // 输出:我是函数声明6.4 函数表达式的提升
函数表达式不会被提升,只能在函数表达式定义之后调用函数。
// 函数表达式不会被提升
test(); // 报错:test is not a function
var test = function() {
console.log("我是函数表达式");
};
// 上面的代码等价于:
var test;
test(); // 此时test是undefined,不是函数
test = function() {
console.log("我是函数表达式");
};7. 闭包
闭包是指有权访问另一个函数作用域中变量的函数。闭包可以让函数访问其定义时所处的作用域,即使该函数在其定义的作用域之外被调用。
7.1 闭包示例
function outer() {
let outerVar = "外部函数变量";
function inner() {
// inner函数可以访问outer函数的变量
console.log(outerVar); // 输出:外部函数变量
}
return inner;
}
// 创建闭包
let closure = outer();
// 调用闭包,即使outer函数已经执行完毕,仍然可以访问outerVar
closure(); // 输出:外部函数变量7.2 闭包的应用
闭包可以用于:
- 封装私有变量
- 实现函数柯里化
- 实现模块模式
- 保持变量的状态
// 使用闭包封装私有变量
function createCounter() {
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
let counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined(无法直接访问私有变量)8. 常见问题解答
Q: 什么是作用域?
A: 作用域是指变量和函数的可访问范围,它决定了代码中的变量和函数在何处可以被访问。
Q: 全局作用域和局部作用域有什么区别?
A: 全局作用域中的变量可以在代码的任何地方被访问,而局部作用域中的变量只能在定义它的函数或代码块内部被访问。
Q: var、let和const的作用域有什么区别?
A: var声明的变量具有函数作用域,let和const声明的变量具有块级作用域。
Q: 什么是作用域链?
A: 作用域链是由多个作用域组成的链式结构,当访问一个变量时,JavaScript引擎会先在当前作用域中查找,找不到就会向上查找父级作用域,直到找到该变量或到达全局作用域。
Q: 什么是变量提升?
A: 变量提升是指JavaScript引擎在执行代码之前,会将变量声明提升到当前作用域的顶部。使用var声明的变量会被提升并初始化为undefined,而使用let和const声明的变量会被提升但处于暂时性死区中。
Q: 什么是闭包?
A: 闭包是指有权访问另一个函数作用域中变量的函数。闭包可以让函数访问其定义时所处的作用域,即使该函数在其定义的作用域之外被调用。
Q: 什么是暂时性死区?
A: 暂时性死区是指使用let和const声明的变量,在声明之前访问会抛出错误的区域。
9. 练习项目
创建一个HTML文件,包含以下内容:
- 一个按钮,用于增加计数
- 一个按钮,用于减少计数
- 一个显示计数的区域
使用JavaScript实现以下功能:
- 使用闭包封装一个计数器
- 点击增加按钮时,计数加1
- 点击减少按钮时,计数减1
- 在显示区域显示当前计数
- 确保计数变量是私有的,无法直接访问
测试不同的操作,确保计数器功能正常
10. 小结
- 作用域是指变量和函数的可访问范围
- JavaScript作用域包括全局作用域、函数作用域和块级作用域
- 全局作用域中的变量可以在代码的任何地方被访问
- 函数作用域中的变量只能在函数内部被访问
- 块级作用域中的变量只能在代码块内部被访问
- 作用域链是由多个作用域组成的链式结构,用于查找变量
- 变量提升是指JavaScript引擎在执行代码之前,会将变量声明提升到当前作用域的顶部
- 闭包是指有权访问另一个函数作用域中变量的函数
- 建议优先使用let和const,避免使用var,以减少作用域相关的问题
在下一章节中,我们将学习JavaScript变量提升的相关知识。