舫摘

知人者智 自知者明 胜人者有力 自胜者强

0%

你不知道的函数声明提升

函数声明提升

在ES5 中存在这样的事实,变量声明提升 ,函数声明提升。 在抛出这篇文章要讨论的问题之前,我们先来解释下这两个概念。
变量声明提升: 通过 var 声明的变量在代码执行之前被js引擎提升到了当前作用域的顶部。
函数声明提升: 通过函数声明的方式(非函数表达式)声明的函数在代码执行之前被js引擎提升到了当前作用域的顶部,而且函数声明提升优先于变量声明提升。

示例代码

js中变量没有声明就直接使用,是会导致引用错误的,如下:

1
console.log(a);

运行结果:

1
Uncaught ReferenceError: a is not defined(…)+

变量声明提升

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

运行结果:

1
undefined

没有报错,而是输出了undefined, 说明变量声明var a被提升到了作用域的顶部,console.log进行RHS引用时找到了变量a, 所以没有报错。

函数声明提升

1
2
3
4
5
a();

function a() {
console.log('This is a function body');
}

运行结果:

1
This is a function body+

函数的调用发生在函数声明之前,但是依旧正常执行。其实真正的函数声明只包含如下部分:

1
2
3
function () {
console.log('This is a function body');
}

这部分执行的结果就是创建了一个函数对象,我们假设为funcObj
a只是指向funcObj的指针,函数声明提升,提升的应该是创建funcObj的过程,也就是上述的代码块。
在一些引擎的实现中,a也会赋值给funcObj对象上的一个name属性。 比如a.name会输出a

函数声明提升优于变量声明提升

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

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

a();

运行结果:

1
2
1
2

上述代码会被js引擎解析为如下:

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

var a;属于重复声明,被忽略掉了。然而后续的函数声明还是会覆盖之前的函数声明。如下:

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

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

运行结果:

1
3

所以得出如下猜想:

1
2
3
4
5
6
7
8
函数声明提升其实做了两个提升动作:
函数名变量提升,类似变量声明 function a();
函数定义提升 { /*function body*/ } .
也就是说通过函数声明的方式定义一个函数会有以下三个步骤(或者说两个也可以,第三个可以归并到第二步中)(YY而已,不要当真):
声明函数名变量 a
创建函数对象 funcObj
把函数名变量 a 指向函数对象 funcObj
而一般的函数声明提升会把这三个步骤都提升到作用域的顶部。

一般的函数声明提升的结果如上所述,那不一般的函数声明提升又会是什么样的呢?

问题如下:在chrome 49, FF 46, IE 11中运行如下代码都得出类似结果

1
2
3
4
5
6
7
8
9
10
(function(){
console.log(a);
if(false){
console.log(a());
function a(){
console.log('true');
}
}
a();
})();

chrome运行结果:

1
2
undefined
Uncaught TypeError: a is not a function

奇怪吧?!函数提升发生在所有代码执行之前,所以尽管a的定义过程写在了if分支中,但是理论上或者说ES6之前, 它是不会影响函数声明提升的,而现在,在作用域顶部console.log(a)输出undefined, 而执行a()发生TypeError。 我们稍作如下修改:

1
2
3
4
5
6
7
8
9
10
(function(){
console.log(a);
if(true){
console.log(a());
function a(){
console.log('true');
}
}
a();
})();

chrome运行结果:

1
2
3
undefined
true
true

再做一次修改:

1
2
3
4
5
6
7
8
9
10
11
(function(){
'use strict';

if(true){
console.log(a());
function a(){
console.log('true');
}
}
console.log(a);
})();

chrome运行结果:

1
2
true
Uncaught ReferenceError: a is not defined

现代浏览器的JS引擎已经支持块作用域了,只是在非严格模式下,只有函数名变量声明会提升到当前闭包的顶部,这也是不管if分支是否成立,在它之前console.log(a)都会输出undefined的原因。而函数定义提升只提升到了if的块作用域内,这就是在if为真时,在if块内且在函数声明之前console.log(a())会输出true的原因。但是在严格模式下,函数名变量的提升也只提升到了if块的顶部。这就是在严格模式下,在if块外部对a进行RHS引用是报TypeError的原因。

注意

在ES5中,严格模式下,函数的声明只能在全局作用域或者函数内,其他块if,for都会报错。

1
2
3
4
5
// 如下代码ES5中都会报错

if(1){
function a() {……} // 报错,ES6中不报错,是因为ES6支持了块作用域,所以在块作用域中声明函数是合理的。
}