JavaScript 初始化不会提升 变量声明不是必须的 常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端
变量提升
例子一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hoistVariable() { //hoistVariable作用域
if (!foo) {
var foo = 5; //这里的声明要提升到这个作用域最上面
//引擎将变量声明提升到了函数顶部,初始值为undefined,自然,if语句块就会被执行,foo变量赋值为5
}
console.log(foo); // 5
}
hoistVariable();
预编译之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 预编译之后
function hoistVariable() {
var foo;
if (!foo) {
foo = 5; //声明提升
}
console.log(foo); // 5
}
hoistVariable();
例子二
1
2
3
4
5
6
7
8
9
10
11
12
var foo = 3;
function hoistVariable() {
var foo = foo || 5; // A || B 表示A是true 的时候 值为前面的 false 的时候值是5
console.log(foo); // 5
}
hoistVariable();
虽然外层作用域有个foo变量,但函数内是不会去引用的,因为预编译之后的代码逻辑是这样的:
预编译之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var foo = 3;
// 预编译之后
function hoistVariable() {
var foo;
foo = foo || 5; //这里的 foo是 undefined
console.log(foo); // 5
}
hoistVariable();
例子三
如果当前作用域中声明了多个同名变量,那么根据我们的推断,它们的同一个标识符会被提升至作用域顶部,其他部分按顺序执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hoistVariable() {
var foo = 3;
{
var foo = 5;
}
console.log(foo); // 5
}
hoistVariable();
预编译之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 预编译之后
function hoistVariable() {
var foo;
foo = 3;
{
foo = 5;
}
console.log(foo); // 5
}
hoistVariable();
函数提升
函数可以在声明之前就可以调用,并且跟变量声明不同的是,它还能得到正确的结果,其实引擎是把函数声明整个地提升到了当前作用域的顶部
例子一
1
2
3
4
5
6
7
8
9
10
11
12
13
function hoistFunction() {
foo(); // output: I am hoisted
function foo() {
console.log('I am hoisted');
}
}
hoistFunction();
预编译之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 预编译之后
function hoistFunction() {
function foo() {
console.log('I am hoisted'); //变量提升只提升声明部分 比如 var foo=5 如果提升只提升 var foo
//但是函数提升就不一样了 它是直接把整个函数提升到作用域顶部了
}
foo(); // output: I am hoisted
}
hoistFunction();
如果在同一个作用域中存在多个同名函数声明,后面出现的将会覆盖前面的函数声明
例子二
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function hoistFunction() {
function foo() {
console.log(1);
}
foo(); // output: 2
function foo() {
console.log(2);
}
}
hoistFunction();
预编译之后
1
2
3
4
5
6
7
8
9
10
11
function foo() {
console.log(1);
}
function foo() {
console.log(2);
}
foo(); // output: 2
对于函数,除了使用上面的函数声明,更多时候,我们会使用函数表达式,下面是函数声明和函数表达式的对比:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 函数声明
function foo() {
console.log('function declaration');
}
// 匿名函数表达式
var foo = function() {
console.log('anonymous function expression');
};
// 具名函数表达式
var foo = function bar() {
console.log('named function expression');
};
可以看到,匿名函数表达式,其实是将一个不带名字的函数声明赋值给了一个变量,而具名函数表达式,则是带名字的函数赋值给一个变量,需要注意到是,这个函数名只能在此函数内部使用。
我们也看到了,其实函数表达式可以通过变量访问,所以也存在变量提升同样的效果。如下实例三
例子三
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function hoistFunction() {
foo(); // 2
var foo = function() {
console.log(1);
};
foo(); // 1
function foo() {
console.log(2);
}
foo(); // 1
}
hoistFunction();
因为JavaScript中的函数是一等公民,函数声明的优先级最高,会被提升至当前作用域最顶端,所以第一次调用时实际执行了下面定义的函数声明,然后第二次调用时,由于前面的函数表达式与之前的函数声明同名,故将其覆盖,以后的调用也将会打印同样的结果
预编译之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 预编译之后
function hoistFunction() {
var foo; //表示变量提升
foo = function foo() { //表示函数提升
console.log(2);
}
foo(); // 2
foo = function() {
console.log(1);
};
foo(); // 1
foo(); // 1
}
hoistFunction();
为什么要变量提升
函数提升就是为了解决相互递归的问题,大体上可以解决像ML语言这样自下而上的顺序问题。 这里简单阐述一下相互递归,下面两个函数分别在自己的函数体内调用了对方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 验证偶数
function isEven(n) {
if (n === 0) {
return true;
}
return isOdd(n - 1); //如果没有函数提升编译到这一步时 isOdd函数还没有声明,所以就没办法调用isOdd函数
}
console.log(isEven(2)); // true
// 验证奇数
function isOdd(n) {
if (n === 0) {
return false;
}
return isEven(n - 1);
}
最佳实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var name = 'Scott';
var sayHello = function(guest) {
console.log(name, 'says hello to', guest); //这里的guest 就是提升后的
};
var i;
var guest;
var guests = ['John', 'Tom', 'Jack'];
for (i = 0; i < guests.length; i++) {
guest = guests[i];
// do something on guest
sayHello(guest);
}