JavaScript 变量提升
JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。
JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
这几天在刷题的过程中经常碰到关于变量提升和作用域的问题。
作用域问题,在之前的博客有写过:Mr.J--JS学习(闭包私有化)
通常JS引擎会在正式执行之前先进行一次预编译,在这个过程中,首先将变量声明及函数声明提升至当前作用域的顶端,然后进行接下来的处理。(注:当前流行的JS引擎大都对源码进行了编译,由于引擎的不同,编译形式也会有所差异,我们这里说的预编译和提升其实是抽象出来的、易于理解的概念)。
var val = 12;
function foo(){
console.log(val);
var val = 20;
console.log(val);
}
foo();
我刚开始看这段代码,以为是作用域的问题,然后认为输出是: 12 20;
但是这段代码通过变量提升,可以转化成以下的代码:
var val = 12;
function foo(){
var val;
console.log(val);
var val = 20;
console.log(val);
}
foo();
答案很明显了:undefined 20
变量提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。
JavaScript 初始化不会提升
var x = 5; // 初始化 x
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y; // 显示 x 和 y
var y = 7; // 初始化 y
在这段代码中,在使用y的值之后出现了y值的初始化,转化代码格式:
var x = 5; // 初始化 x
var y; // 声明 y
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y; // 显示 x 和 y
y = 7; // 设置 y 为 7
最终输出:x 为:5,y 为:undefined
函数提升
function bar() {
foo(); // output: I am hoisted
function foo() {
console.log('I am hoisted');
}
}
bar();
为什么函数可以在声明之前就可以调用,并且跟变量声明不同的是,它还能得到正确的结果,其实引擎是把函数声明整个地提升到了当前作用域的顶部,预编译之后的代码逻辑如下:
// 预编译之后
function bar() {
function foo() {
console.log('I am hoisted');
}
foo(); // output: I am hoisted
}
bar();
其实这个就是执行上下文的有关问题了。好多学习JavaScript的人一般不会注意变量提升的问题,这样就会导致代码极其容易出错。
之前写过相关的博客,博客链接:一篇文章让你理解面试难点:执行上下文(干货满满(附面试题))
同样,如果出现同名函数,将会进行函数的覆盖,即后者覆盖前者:
function bar() {
function foo() {
console.log(1);
}
foo(); // output: 2
function foo() {
console.log(2);
}
}
bar();
为什么要进行提升
由于第一代JS虚拟机中的抽象纰漏导致的,编译器将变量放到了栈槽内并编入索引,然后在(当前作用域的)入口处将变量名绑定到了栈槽内的变量。(注:这里提到的抽象是计算机术语,是对内部发生的更加复杂的事情的一种简化。)
然后,Dmitry Soshnikov又提到了函数提升,他提到了相互递归(就是A函数内会调用到B函数,而B函数也会调用到A函数)。函数提升就是为了解决相互递归的问题,大体上可以解决像ML语言这样自下而上的顺序问题。
总结
function jsFun6(){ //函数声明和函数表达式的区别
test1();//函数声明提升,在执行代码之前会先读取函数声明,不会报错
function test1(){//函数声明方式创建函数
alert("测试1");
}
//test2();报错,函数还不存在
console.log(test2)//不会报错,变量提升只是提升变量的声明,并不会把赋值也提升上来,输出undefined
var test2=function(){
alert("测试2");
};//使用函数表达式创建一个匿名函数(实际是以变量test3命名的函数)
test2();//不会报错,以创建函数
var test3=function(){
alert("测试3");
}();//加了括号立即执行
var test4 = 12;// !注意看,一旦变量被赋值后,将会输出变量
//函数提升优先级高于变量提升,所以函数先提升,然后变量提升覆盖之前的函数声明,表
//现为变量
function test4() {
alert("测试4");
}
console.log(test4); //12
var test5="test5_1";
(function(){
//js中的变量搜索顺序:找变量时,先找局部变量,如果没有局部变量;再找全局变量。
alert(test5);//此时的test5为局部变量的提升,undefined
var test5="test5_2";
})();
}
只要理解了执行上下文,对于变量提升就会一下看懂。一篇文章让你理解面试难点:执行上下文(干货满满(附面试题))