# js中的变量提升和函数提升
# 什么是提升(Hosting)?
引擎会在解释JavaScript代码之前首先对齐进行编译,编译过程中的一部分工作就是找到所有的声明,并用合适的作用域将他们关联起来,这也正是词法作用域的核心内容。
☆☆☆☆☆简单说就是在js代码执行前引擎会先进行预编译,预编译期间会将变量声明与函数声明提升至其对应作用域的最顶端。举例来说:
console.log(a);
var a = 3;
//预编译后的代码结构可以看做如下:
var a; // 将变量a的声明提升至最顶端,赋值逻辑不提升。
console.log(a); // undefined
a = 3; // 代码执行到原位置即执行原赋值逻辑
# 开篇小题:
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
然而去看这段代码:
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
打印的结果却是两个 foo2
。
上面那段代码是以函数表达式的形式书写代码,效果就是和之后我们要提到的变量提升一样。但是下面这段代码,使用的是函数直接声明的方式,js代码在执行的时候就会涉及到函数提升的问题了。
# 变量提升:
console.log(a);
var a = "a";
var foo = () => {
console.log(a);
var a = "a1";
}
foo();
//打印结果:
//undefined
//undefined
由于js自上而下逐行解释执行的,有人可能会认为第一行代码引用了一个没有声明的变量a,会抛出 ReferenceError
异常,而注掉第一行后,由于变量 a 在第二行log之前已经声明并赋值,打印结果应该是 "a"。而实际的执行结果是:undefined undefined
# 解析:
☆☆☆变量声明的提升是以变量所处的第一层词法作用域为“单位”的,即全局作用域中声明的变量会提升至全局最顶层,函数内声明的变量只会提升至该函数作用域最顶层。那么开始的一段代码经过预编译则变为:
var a;//全局作用域
console.log(a); // undefined
a = "a";
var foo = () => {//函数作用域
var a; // 全局变量会被局部作用域中的同名变量覆盖
console.log(a); // undefined
a = "a1";
}
foo();
输出undefined就很明了。
ES6
新增了let
和const
关键字,使得js
也有了“块”级作用域,而且使用let
和const
声明的变量和函数是不存在提升现象的,比较有利于我们养成良好的编程习惯。
# 函数提升:
有了上面变量提升的说明,函数提升理解起来就比较容易了,但较之变量提升,函数的提升还是有区别的。举例说明:
console.log(foo1); // [Function: foo1]
foo1(); // foo1
console.log(foo2); // undefined
foo2(); // TypeError: foo2 is not a function
function foo1 () {
console.log("foo1");
};
var foo2 = function () {
console.log("foo2");
};
即函数提升只会提升函数声明(函数提升是可以直接在函数声明之前调用该函数,并能成功执行它),而不会提升函数表达式(函数表达式就可以看作成变量提升)。
再举一个小例子:
var a = 1;
function foo() {
a = 10;
console.log(a);
return;
function a() {};
}
foo();
console.log(a);
直接上结果:
10 1
上面的代码块经过预编译后可以看做如下形式(只分析foo
方法内部情况):
var a = 1; // 定义一个全局变量 a
function foo() {
// 首先提升函数声明function a () {}到函数作用域顶端, 然后function a () {}等同于 var a = function() {};最终形式如下
var a = function () {}; // 定义局部变量 a 并赋值。
a = 10; // 修改局部变量 a 的值,并不会影响全局变量 a
console.log(a); // 打印局部变量 a 的值:10
return;
}
foo();
console.log(a); // 打印全局变量 a 的值:1
# 进入执行上下文时,首先会处理函数声明,其次会处理变量声明
函数提升优先级高于变量提升
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
会打印出函数[function:foo]
,而不是正常思维的foo
变量提升的效果 undefined
。
这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
# 下面我们做一个小改变:
var foo = 1;
console.log(foo);
function foo(){
console.log("foo");
};
这次打印结果就是“1”;
因为此时var foo = 1;
先通过表达式进行赋值操作,当然这是已经执行了赋值操作,函数再怎么提升优先级也达不到执行的操作吧,所以打印的是“1”。