作用域与提升
作用域和变量提升是JavaScript中两个重要的概念,它们决定了变量和函数在何时何地可以被访问。
作用域
作用域是指变量和函数的可访问范围,它决定了代码中的哪些部分可以访问特定的变量或函数。
1. 全局作用域
全局作用域中的变量可以在代码的任何地方被访问:
javascript
// 全局变量
const globalVar = '全局变量';
function globalFunction() {
console.log(globalVar); // 可以访问全局变量
}
globalFunction(); // 输出: 全局变量
2. 函数作用域
函数作用域中的变量只能在函数内部被访问:
javascript
function localFunction() {
// 局部变量
const localVar = '局部变量';
console.log(localVar); // 可以访问
}
localFunction(); // 输出: 局部变量
console.log(localVar); // ReferenceError: localVar is not defined
3. 块级作用域
ES6引入了let和const,它们支持块级作用域(由{}包围的代码块):
javascript
if (true) {
const blockVar = '块级变量';
console.log(blockVar); // 可以访问
}
console.log(blockVar); // ReferenceError: blockVar is not defined
for (let i = 0; i < 5; i++) {
console.log(i); // 可以访问
}
console.log(i); // ReferenceError: i is not defined
变量提升
变量提升是指JavaScript引擎在执行代码前,会将变量和函数声明提升到其所在作用域的顶部。
1. var的提升
使用var声明的变量会被提升到作用域顶部,但赋值不会提升:
javascript
console.log(hoistedVar); // undefined
var hoistedVar = '变量值';
// 相当于
var hoistedVar;
console.log(hoistedVar);
hoistedVar = '变量值';
2. function的提升
函数声明会被整体提升(包括函数体):
javascript
console.log(typeof hoistedFunction); // function
hoistedFunction(); // 输出: 函数被调用
function hoistedFunction() {
console.log('函数被调用');
}
3. 函数表达式的提升
函数表达式不会被整体提升,只有变量声明会被提升:
javascript
console.log(typeof hoistedExpression); // undefined
hoistedExpression(); // TypeError: hoistedExpression is not a function
const hoistedExpression = function() {
console.log('函数表达式');
};
4. let和const的提升
使用let和const声明的变量也会被提升,但在声明之前无法访问(暂时性死区):
javascript
console.log(hoistedLet); // ReferenceError: Cannot access 'hoistedLet' before initialization
let hoistedLet = '变量值';
console.log(hoistedConst); // ReferenceError: Cannot access 'hoistedConst' before initialization
const hoistedConst = '常量值';
函数提升与变量提升的优先级
函数提升的优先级高于变量提升:
javascript
console.log(hoisted); // function
var hoisted = '变量';
function hoisted() {
console.log('函数');
}
console.log(hoisted); // 变量
暂时性死区
暂时性死区(TDZ)是指使用let或const声明的变量在声明前不可访问的区域:
javascript
if (true) {
// 暂时性死区开始
console.log(a); // ReferenceError
let a = 10;
// 暂时性死区结束
console.log(a); // 10
}
常见的暂时性死区场景
1. 在声明前使用变量
javascript
let a = 10;
if (true) {
console.log(a); // ReferenceError
let a = 20;
}
2. 在typeof检查中
javascript
console.log(typeof x); // ReferenceError (如果x是let声明的)
let x;
console.log(typeof y); // undefined (如果y未声明)
3. 在参数默认值中
javascript
function func(x = y, y = 1) {
console.log(x, y);
}
func(); // ReferenceError: Cannot access 'y' before initialization
作用域链
当访问一个变量时,JavaScript引擎会沿着作用域链向上查找,直到找到该变量或到达全局作用域:
javascript
const globalVar = '全局变量';
function outer() {
const outerVar = '外部变量';
function inner() {
const innerVar = '内部变量';
console.log(innerVar); // 内部变量
console.log(outerVar); // 外部变量
console.log(globalVar); // 全局变量
console.log(nonExistentVar); // ReferenceError
}
inner();
}
outer();
作用域链的形成
作用域链是在函数定义时创建的,而不是在函数调用时:
javascript
function outer() {
const outerVar = '外部变量';
function inner() {
console.log(outerVar);
}
return inner;
}
const innerFunction = outer();
innerFunction(); // 输出: 外部变量
闭包与作用域
闭包是指有权访问另一个函数作用域中变量的函数,它与作用域密切相关:
javascript
function makeCounter() {
let count = 0; // 闭包可以访问这个变量
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
最佳实践
1. 使用let和const
避免使用var,优先使用let和const来利用块级作用域和避免变量提升问题:
javascript
// 好
let i = 0;
const PI = 3.14159;
// 不好
var j = 0;
2. 函数声明放在作用域顶部
为了提高代码可读性,将函数声明放在作用域顶部:
javascript
// 好
function main() {
// 函数体
}
// 调用函数
main();
// 不好
callFunction();
function callFunction() {
// 函数体
}
3. 避免全局变量
尽量减少全局变量的使用,以避免命名冲突和提高代码可维护性:
javascript
// 不好
const globalVar = '全局变量';
// 好
(function() {
const localVar = '局部变量';
// 代码逻辑
})();
4. 使用立即执行函数表达式(IIFE)
使用IIFE创建私有作用域,避免污染全局命名空间:
javascript
(function() {
const privateVar = '私有变量';
function privateFunction() {
console.log(privateVar);
}
// 暴露公共接口
window.publicAPI = {
publicMethod: function() {
privateFunction();
}
};
})();
publicAPI.publicMethod(); // 输出: 私有变量
学习资源
继续学习:JavaScript高级
最后更新:2026-02-08