作用域与提升

作用域和变量提升是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引入了letconst,它们支持块级作用域(由{}包围的代码块):

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的提升

使用letconst声明的变量也会被提升,但在声明之前无法访问(暂时性死区):

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)是指使用letconst声明的变量在声明前不可访问的区域:

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,优先使用letconst来利用块级作用域和避免变量提升问题:

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