0%

hoisting

Hoisting in JavaScript

JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables, classes, or imports to the top of their scope, prior to execution of the code.

Variable Hoisting

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

The above code is equivalent to:

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

注意变量提示只提升到它所在的作用域的顶部,而不是全局作用域的顶部。

1
2
3
4
5
6
7
8
9
function outer() {
console.log(a); // ReferenceError: a is not defined
function inner() {
console.log(a); // undefined
var a = 1;
}
inner();
}
outer();

The above code is equivalent to:

1
2
3
4
5
6
7
8
9
10
11
function outer() {
console.log(a); // ReferenceError: a is not defined
function inner() {
var a; // a is hoisted to the top of its enclosing function `inner`.
console.log(a); // undefined
a = 1;
}

inner();
}
outer();

Function Hoisting

1
2
3
4
foo(); // hello
function foo() {
console.log('hello');
}

The above code is equivalent to:

1
2
3
4
function foo() {
console.log('foo');
}
console.log(foo); // [Function: foo]

注意函数提升和变量提升一样,只提升到它所在的作用域的顶部,而不是全局作用域的顶部。

1
2
3
4
5
6
7
8
9
10
inner(); // ReferenceError: inner is not defined

function outer() {
inner(); // 'Hello'

function inner() {
console.log('Hello');
}
}
outer();

Hoisting in ES6

ES6中的letconst不会被提升,所以在使用之前必须先声明。letconst会产生暂时性死区(Temporal Dead Zone)。

1
2
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a = 1;

关于这一点实际上是有争议的,有些观点认为,letconst是被提升的,只是在TDZ中,不能访问。看下面的代码:

1
2
3
4
5
const x = 1;
{
console.log(x); // ReferenceError: Cannot access 'x' before initialization
const x = 2;
}

如果我们把const x = 2;注释掉,那么代码就可以正常运行,此时x使用的是外层的x。这说明const x = 2;是被提升的(进而掩盖了外层的x),只是在TDZ中不能访问。

1
2
3
4
5
const x = 1;
{
console.log(x); // 1
// const x = 2;
}

Temporal dead zone (TDZ)

A variable declared with let, const, or class is said to be in a "temporal dead zone" (TDZ) from the start of the block until code execution reaches the place where the variable is declared and initialized.
下面的代码中,

1
2
3
4
5
6
7
8
9
10
11
12
// Temporal dead zone (TDZ), TDZ 从block开始的地方开始,到其定义的地方结束。
// 在TDZ中访问let定义的变量会产生ReferenceError。
// 而var定义的变量则不存在此问题,因为var有hoisting(变量提升)
{
// TDZ starts at beginning of scope
console.log(bar); // undefined
console.log(foo); // ReferenceError
var bar = 1;
let foo = 2; // End of TDZ (for foo)
const xxx = 3;
}

Arrow function is not hoisted.

1
2
3
4
foo(); // ReferenceError: Cannot access 'foo' before initialization
const foo = () => {
console.log('Hello');
}

Function expression is not hoisted.

1
2
3
4
foo(); // ReferenceError: Cannot access 'foo' before initialization
const foo = function() {
console.log('foo');
}

difference between variable hoisting and function hoisting

变量提升只提升变量的声明,不提升赋值,所以变量提升后的值是 undefined。

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

函数提升是整体提升,所以可以放心的调用。

1
2
3
4
foo(); // 'hello'
function foo() {
console.log('hello');
}

在Modern JavaScript中,应避免使用变量提升和函数提升,遵循以下三点可以保证:

  1. Use let and const instead of var
  2. Use strict mode.
  3. Always declare variables and functions before using them.

面试题

第一题

以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
function bar() {
return 3;
}

return bar();

function bar() {
return 8;
}
}

console.log(foo()); // 8

答案:输出8。

提升后的代码如下,注意:JavaScript中允许同名函数存在,后面的函数会覆盖前面的函数。

1
2
3
4
5
6
7
8
9
10
11
function foo() {
function bar() {
return 3;
}

function bar() {
return 8;
}

return bar();
}

第二题

以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo() {
var a = 1;

function b() {
a = 10;
return;
function a() {}
}

b();
console.log(a);
}

foo();

答案:1

提升后的代码如下,function a()的声明提升至function b()的顶部,而a = 10;是对function a()的再赋值,不会影响外部的变量a

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
var a;
a = 1;

function b() {
function a() {}
a = 10;
return;
}

b();
console.log(a);
}

如果我们把function a() {}注释掉,那么代码就会输出10,因为此时a = 10;是对外部变量a的赋值。

第三题

以下代码输出什么?

1
2
3
4
5
```

### 第四题
以下代码输出什么?
```javascript

Reference:

MDN: Hoisting