javascript中的函数预解析和变量提升

小时候,向喜欢的姑娘展示快 长大后,向喜欢的姑娘展示慢
### 作用域 如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量:
1
2
3
4
5
6
7
8
'use strict';

function foo() {
var x = 1;
x = x + 1;
}

x = x + 2; // ReferenceError! 无法在函数体外引用变量x
如果两个不同的函数各自申明了同一个变量,那么该变量只在各自的函数体内起作用。换句话说,不同函数内部的同名变量互相独立,互不影响:
1
2
3
4
5
6
7
8
9
10
11
'use strict';

function foo() {
var x = 1;
x = x + 1;
}

function bar() {
var x = 'A';
x = x + 'B';
}
由于JavaScript的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:
1
2
3
4
5
6
7
8
9
'use strict';

function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以访问foo的变量x!
}
var z = y + 1; // ReferenceError! foo不可以访问bar的变量y!
}
如果内部函数和外部函数的变量名重名怎么办? JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
1
2
3
4
5
6
7
8
9
10
11
'use strict';

function foo() {
var x = 1;
function bar() {
var x = 'A';
alert('x in bar() = ' + x); // 'A'
}
alert('x in foo() = ' + x); // 1
bar();
}

不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

1
2
3
4
5
'use strict';

var course = 'Learn JavaScript';
alert(course); // 'Learn JavaScript'
alert(window.course); // 'Learn JavaScript'

由于函数定义有两种方式,以变量方式var foo = function () {}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,并绑定到window对象:

1
2
3
4
5
6
7
8
'use strict';

function foo() {
alert('foo');
}

foo(); // 直接调用foo()
window.foo(); // 通过window.foo()调用

进一步大胆地猜测,我们每次直接调用的alert()函数其实也是window的一个变量:

变量提升

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,JavaScript引擎自动提升了变量y的声明,但不会提升变量y的赋值。

考虑下面的代码:

1
2
var myvar = 'my value';
alert(myvar); // my value

Okay, 当然,弹出的结果肯定是”my value”,但是,跟着我,让我下面创建个方法,弹出相同的值:

1
2
3
4
5
var myvar = 'my value';

(function() {
alert(myvar); // my value
})();

好吧,好吧,仍然很明显,我知道。现在,让我们加点猛的调料,在匿名函数内部创建一个同名的局部变量。

1
2
3
4
5
6
var myvar = 'my value';

(function() {
alert(myvar); // undefined
var myvar = 'local value';
})();

在当前的作用域内,无论在哪里变量声明,在幕后,其都在顶部被“预解析”了。不过,仅声明被“预解析”。该变量即使初始化,其当前的值,在作用域的顶部,也会被设置成undefined。
恩,现在让我们好好的破译下这个“声明”和“初始化”,以var joe = ‘plumber’;为模特吧。
声明(Declaration)

1
var joe; // the declaration

初始化(Initialization)

1
joe = 'plumber'; // the initialization 

现在,我们知道了这些术语的意思,就可以更好的理解到底背地里都干了些什么勾当,请看下面的伪函数:

1
2
3
4
5
6
7
8
(function() {
var a = 'a';
// 一行代码
var b = 'b';
// 更多行的代码
var c= 'c'; // antipattern
// 最后一行脚本
})();

需注意,上面的这做法是不太好的。但是,先不管这个,在程序的背后,这个变量声明无论在函数作用域的什么地方,都被置顶解析了,就像下面这样:

1
2
3
4
5
6
7
8
9
(function() {
var a, b, c; // variables declared
a = 'a';
// 一行代码
b = 'b'; // initialized
// 更多行的代码
c= 'c'; // initialized
// 最后一行脚本
})();

http://www.zhangxinxu.com/wordpress/2010/10/%E7%BF%BB%E8%AF%91-%E8%A7%A3%E9%87%8Ajavascript%E7%9A%84%E2%80%9C%E9%A2%84%E8%A7%A3%E6%9E%90%E7%BD%AE%E9%A1%B6%E8%A7%A3%E6%9E%90%E2%80%9D/
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014344993159773a464f34e1724700a6d5dd9e235ceb7c000