返回

JavaScript Hoisting:揭秘幕后的运作方式

前端

JavaScript 中的提升 (Hoisting) 揭示了变量和函数在执行上下文中的运作方式。让我们一同探寻幕后的奥秘,了解声明、赋值、提升的概念,并探讨执行上下文和作用域是如何影响它们的。通过深入浅出的示例,您将清晰理解 JavaScript 中提升的含义以及对代码的影响。

声明、赋值与提升的概念

1.1 声明 (declaration)

在不考虑 ES6 的情况下,声明有以下两种:

  • 变量声明 (var a)
  • 函数声明 (function foo() {})

1.2 赋值 (assignment)

赋值是指将值赋给变量或属性。例如:

var a = 1;

1.3 提升 (hoisting)

提升是一种将变量声明或函数声明提升到其所在作用域顶部的行为。这意味着在代码执行前,所有变量和函数声明都将被提升到作用域的顶部。

执行上下文与作用域

2.1 执行上下文 (Execution Context)

执行上下文是 JavaScript 代码执行的环境。它包含了变量对象 (VO)、作用域链 (scope chain) 和 this 。

2.2 作用域 (Scope)

作用域是指变量和函数可以被访问的范围。JavaScript 中有两种作用域:

  • 全局作用域:全局作用域是 JavaScript 代码可以访问的最高层作用域。
  • 局部作用域:局部作用域是函数体内的作用域。

变量提升

3.1 变量声明提升

变量声明提升是指变量声明在执行前被提升到作用域的顶部。这意味着变量声明可以在其被声明之前使用。例如:

console.log(a); // undefined
var a = 1;

在上面的代码中,变量 a 在使用之前被声明,但由于变量提升,console.log(a) 仍然可以访问变量 a 并输出 undefined。

3.2 变量赋值提升

变量赋值提升是指变量赋值在执行前被提升到作用域的顶部。这意味着变量赋值可以在其被声明之前进行。例如:

a = 1; // ReferenceError: a is not defined
var a;

在上面的代码中,变量 a 在使用之前被赋值,但由于变量赋值提升,a = 1 仍然可以执行,但会抛出 ReferenceError 异常。

函数提升

4.1 函数声明提升

函数声明提升是指函数声明在执行前被提升到作用域的顶部。这意味着函数声明可以在其被声明之前调用。例如:

foo(); // TypeError: foo is not a function

function foo() {
  console.log('Hello, world!');
}

在上面的代码中,函数 foo 在使用之前被声明,但由于函数声明提升,foo() 仍然可以执行,但会抛出 TypeError 异常。

4.2 函数表达式提升

函数表达式提升是指函数表达式在执行前不会被提升到作用域的顶部。这意味着函数表达式必须在声明之后才能使用。例如:

var foo = function() {
  console.log('Hello, world!');
};

foo(); // TypeError: foo is not a function

在上面的代码中,函数 foo 在使用之前被声明,但由于函数表达式提升,foo() 无法执行,会抛出 TypeError 异常。

块级作用域

5.1 块级作用域 (Block-Scoped)

块级作用域是指在代码块 ({ }) 内定义的变量和函数只在该代码块内有效。这意味着这些变量和函数在代码块之外无法访问。例如:

{
  let a = 1;
  const b = 2;

  console.log(a); // 1
  console.log(b); // 2
}

console.log(a); // ReferenceError: a is not defined
console.log(b); // ReferenceError: b is not defined

在上面的代码中,变量 a 和 b 在代码块内被声明,因此它们只在代码块内有效。在代码块之外,变量 a 和 b 都无法访问。

暂时性死区 (Temporal Dead Zone)

6.1 暂时性死区 (Temporal Dead Zone)

暂时性死区是指在变量或函数声明之前,该变量或函数无法被访问的区域。例如:

console.log(a); // ReferenceError: a is not defined

let a = 1;

在上面的代码中,变量 a 在使用之前被声明,但由于暂时性死区,console.log(a) 无法访问变量 a,会抛出 ReferenceError 异常。

词法作用域 (Lexical Scope)

7.1 词法作用域 (Lexical Scope)

词法作用域是指变量和函数的作用域由其在代码中的位置决定。这意味着变量和函数的作用域在代码编写时就已经确定,并且不会被函数调用或代码执行的顺序所影响。例如:

function outer() {
  let a = 1;

  function inner() {
    console.log(a); // 1
  }

  inner();
}

outer();

在上面的代码中,变量 a 在 outer 函数中被声明,因此它的作用域是 outer 函数。函数 inner 在 outer 函数中被声明,因此它的作用域也是 outer 函数。这意味着函数 inner 可以访问变量 a,并且输出 1。

预编译 (Precompilation)

8.1 预编译 (Precompilation)

预编译是指在 JavaScript 代码执行之前对代码进行处理的过程。预编译过程包括:

  • 变量声明提升
  • 函数声明提升
  • 词法作用域的确定
  • 暂时性死区的创建

预编译过程在代码执行之前完成,因此它不会影响代码的执行顺序。

总结

JavaScript 中的提升是一种将变量声明和函数声明提升到其所在作用域顶部的行为。变量提升和函数提升都会受到块级作用域、暂时性死区和词法作用域的影响。预编译过程在 JavaScript 代码执行之前完成,它不会影响代码的执行顺序。