javaScript中用var定义变量时存在变量提升(ES6及之后的let/const定义变量时为块级作用域无变量提升,ES6之前js没有块级作用域),比如:
console.log(a); // 不会报错,会打印出“undefined”
var a = 2;
上述代码中变量声明var a会提升到代码顶部,但a = 2这个赋值操作并没有提升,相当于
var a;
console.log(a); // undefined
a = 2;
函数也有函数提升,且函数提升的优先级高于变量提升,但只有函数声明才有函数提升,函数表达式则没有函数提升,细看代码,一定要看:
foo(); // 输出 1
function foo() { // 函数声明的形式,则函数提升到顶部
console.log('1');
}
var foo = function() { // 函数表达式的形式,则等同于一个变量提升,优先级小于函数提升
console.log('2');
}
上述代码会被引擎理解为:
function foo() {
console.log('1');
}
var foo;
foo();
foo = function() {
console.log('2');
}
再看个例子,理解作用域链是静态的及函数提升:
// 全局环境
var a = "全局变量";
function A() {
var a = "局部变量";
B();
}
function B() {
console.log(a);
}
A(); // 会打印出“全局变量”
JS的函数在声明时,采用的是词法作用域(词法环境就是在JavaScript 引擎创建一个执行上下文时,创建的用来存储变量和函数声明的环境,它可以使代码在执行期间,访问到存储在其内部的变量和函数,而在代码执行完毕之后,从内存中释放掉。),即在声明时就确定好了作用域链。作用域链的定义是静态的!上述代码等价于:
// 全局环境
function A() {
var a = "局部变量";
B(); // 虽然函数B在函数A中被调用,但函数B在声明时它的作用域链就已经确定了,为函数B作用域 -> 全局作用域
}
function B() { // a在B函数内未定义,则向外层找,外层就是全局环境,找到了var a = "全局变量";
console.log(a);
}
var a = "全局变量";
A(); // 会打印出“全局变量”
B函数在声明时,js语言的作用域机制使得变量a会从本函数内向外逐层找它定义的地方,B函数内没有定义a,则向外层找,外层就是全局环境,找到了var a = "全局变量",而函数A和函数B在作用域的概念上面层级关系是同层级,即函数A/B作用域 -> 全局作用域,所以函数B中a未定义不会去函数A中找a的定义,而是去B函数的外层全局环境去找A的定义。
补充问题:为啥要函数提升?变量提升?
函数提升就是为了解决相互递归的问题,相互递归就是A/B两个函数互相在自身函数体内调用对方,如果没有函数提升,而是按照自下而上的顺序,当A函数被调用时,B函数还未声明,所以A内部无法调用B函数。所以开发者设计了函数提升这一形式,将函数提升至当前作用域的顶部。
变量提升是人为实现的问题,而函数提升在当初设计时是有目的的。变量提升可能会出现一些难以预知的问题,在ES6后,由let/const来定义变量,不会变量提升,为了更好的维护代码,目前很多公司也明确要求尽量使用let/const来定义变量而不要用var。