JavaScript中的提升

JavaScript是允许在书写代码时先使用变量,之后再声明该变量,对于函数也是,你可以先使用函数,之后再声明该函数。

变量提升

JS引擎会在解释JavaScript代码之前进行编译,编译的时候就会去声明变量,并确定变量所属的作用域。也就是在执行JavaScript代码之前会将所有变量声明放在作用域的最顶部,但是赋值或其他运行逻辑保持不变(如果变了那还得了…)。MDN中介绍说,变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中,在物理层面不会进行移动。

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

上面这段代码经过编译,会变成如下样子,所以在打印时a已经有值了:

1
2
3
4
var a;
a = 2;
// 打印 2
console.log( a );

那下面这段代码又会输出什么呢?

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

经过编译处理,它会变成如下样子,所以在打印时a还没有值:

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

但是,用 letconst声明的变量不会被提升,必须先声明再使用。

函数提升

JavaScript中的函数也可以先使用,再声明。下面这段代码是会正常执行的:

1
2
3
4
5
6
foo();

function foo() {
console.log(a);
var a = 2;
}

经过处理,这段代码会变成如下样子:

1
2
3
4
5
6
7
function foo() {
var a;
console.log(a);
a = 2;
}


foo();

注意,变量只会被提升到其所在作用域的最顶部,而不是.js文件的顶部。

对于函数提升容易迷糊的地方,可能在于JavaScript里还可以用表达式的方式创建函数。像下面的这段代码执行时就会报错了。

1
2
3
4
foo();
var foo = function bar() {
// ...
};

因为这段代码经过处理,会被理解为如下样子:

1
2
3
4
5
6
var foo;
foo();
foo = function () {
var bar = ...self...
// ...
}

foo变量被提升了,但是表达式还在原来的位置,所以调用foo();时,报TypeError的错误:Uncaught TypeError: foo is not a function。如果我们在上面调用bar();呢,bar在全局作用域里就不可见,所以调用时,会报ReferenceError错误:Uncaught ReferenceError: bar is not defined

所以,对于函数来说,函数声明会被提升,函数表达式不会被提升

还一个要注意的细节时,如果函数声明和函数表达式出现了“重复”的情况,那么函数会优先于变量被提升。考虑下面的代码:

1
2
3
4
5
6
7
8
9
10
11
foo();

var foo;

function foo() {
console.log(1)
}

foo = function () {
console.log(2)
}

这段代码打印1,而不是2!经过提升处理后,这段代码被解释为如下样子:

1
2
3
4
5
6
7
8
9
10
11
function foo() {
console.log(1)
}

foo(); // 1

var foo;

foo = function () {
console.log(2)
}

如果同时声明了多个相同的函数,尽管有提升的存在,但后面的函数还是会覆盖前面的。

1
2
3
4
5
6
7
8
9
10
11
12
13
foo();  // 3

function foo() {
console.log(1)
}

var foo = function () {
console.log(2)
}

function foo() {
console.log(3)
}

总结

从上面的各种情况的分析可以看到,如果利用JavaScript对函数和变量声明的提升机制,总是先使用,再声明,会让我们的代码变得多么不易阅读,而且容易产生bug。所以我认为最好不要依赖提升机制,我们在写代码时就老老实实的先声明函数和变量,然后再对其使用!

本文作者:意林
本文链接:http://shinancao.cn/2019/08/02/JS-Promote/
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!