什么是变量提升
变量提升是当栈内存作用域形成时,JS代码执行前,浏览器会将带有var,function关键字的变量提前进行声明,定义,这种预先处理的机制就叫做变量提升。
- 变量提升只发生在当前作用域
- 变量提升阶段,var的只有声明没有定义,函数声明function既有声明也有定义(匿名函数没有变量提升)
带var和不带var的区别
- 全局作用域中不带var进行变量声明相当于给window对象设置了一个属性
- 全局作用域中使用var声明的变量会映射到window下成为属性
- 私有作用域(函数作用域)中,带var的是私有变量。不带var的会向上级作用域查找,如果上级作用域也没有就一直找到window为止,这个查找过程叫作用域链。如果window上也没有,就给window对象设置这个属性
相关题目解析
// 1
console.log(a, b)
var a =12, b =15
function foo(){
// 2
console.log(a, b)
// 3
var a = b =13
console.log(a, b)
}
foo()
console.log(a, b)
代码第三行的变量a和b进行变量提升,故第一次输出a,b时为undefined undefined;进入函数foo,先看第八行代码var a=b=13,此处的b是不带var的, 等价于 var a=13;b=13。故a在函数作用域下进行变量提升,在第六行代码输出是,a为undefined,b在函数作用域下未找到,沿作用域链向上查找,在全局中找到b为15;代码继续执行,此时a和b均赋值为13,注意此处是对函数作用域下的a和全局作用域下的b进行赋值, 代码第九行输出为13 13;函数foo执行完毕,在代码第十四行输出a和b,此时输出的是全局作用域下的a和b,故输出为 12 13。
最后输出结果为:
/* 输出:
undefined undefined
undefined 15
13 13
12 13*/
下面这段代码对比上面那处
console.log(a, b)
var a =12, b = 15
function foo(){
console.log(a, b)
// var a =b =13
console.log(a, b)
}
foo()
// 4
console.log(a, b)
因为函数foo里不存在变量提升,函数foo里读取的均为函数作用域下的a和b。
最后输出结果为:
/* 输出:
undefined undefined
12 15
12 15
12 15*/
a = 2
function foo(){
var a =12;
b = '林一一'
console.log('b' in window)
console.log(a, b)
}
foo()
console.log(b)
console.log(a)
此段代码中的b为全局对象window中的一个属性。
最后输出结果为:
/* 输出
true
12 "林一一"
林一一
2/
function foo(){
console.log(a)
a =12;
b = '林一一'
console.log('b' in window)
console.log(a, b)
}
foo()
函数作用域内不存在a,就会从作用域链向上查找window下的作用域,发现也没有,抛出错误
最后输出为:
/* 输出
Uncaught ReferenceError: a is not defined
/
经典面试题
fn();
console.log(v1);
console.log(v2);
console.log(v3);
function fn(){
var v1 = v2 = v3 = 2019;
console.log(v1);
console.log(v2);
console.log(v3);
}
/*输出
2019
2019
2019
Uncaught ReferenceError: v1 is not defined
/
v1属于函数作用域下的变量,在全局作用域上无法读取
函数左边的变量提升
普通函数会进行变量声明并定义;而匿名函数赋给一个变量,函数不会进行变量提升
print()
function print(){
console.log('林一一')
}
print()
上面都输出了 林一一 ,因为带 function 的已经进行了变量提升并定义
print()
var print = function() {
console.log('林一一')
}
print()
/*输出
Uncaught TypeError: print is not a function
/
同样由于变量提升机制带 var 的 print 是一开始值是 undefined ,所以 print() 这时还不是一个函数,所以报出 类型错误TypeError
条件判断下的变量提升
- 在if后的{}中的代码,不管条件是否成立都会进行变量提升(此处只有变量提升,没有函数的定义)
- if中()内的表达式不会进行变量提升
- 为了迎合 ES6 语法只有 JS 执行到条件语句,判断条件是成立的才会对条件内的函数赋值,不成立不被赋值只被定义成undefined。===>即if后的{}中的普通函数只会进行变量提升而不会定义,即不能调用函数。只有当执行到条件成立时,才会立即给函数进行调用。
相关题目解析
console.log(a)
if(false){
var a = '林一一'
}
console.log(a)
if后面的变量a进行变量提升,成为全局作用域下的变量。
最后输出结果为:
/* 输出 undefined undefined/
var y = 1
if(function f(){}){
console.log(typeof f) // undefined
y = y + typeof f
}
console.log(y) // 1undefined
if中()内的表达式不会变量提升
console.log(print()) // == window.print()
if(true){
function print() {
console.log('林一一')
}
}
console.log(print())
/* 输出
undefined
林一一
undefined
*/
第一行代码相当于window.print(),window上确实又print()这个内置函数,返回值为undefined。if添加后的函数进行变量提升,并在执行到条件成立时定义,故函数内部执行输出'林一一';函数print执行完没有返回值,故返回值为undefined。
console.log(a)
console.log(p())
if(true){
var a = 12
function p() {
console.log('林一一')
}
}
添加后的变量a和函数p进行变量提升,注意此处的函数只进行了变量提升,未定义,相当于一个变量p值为undefined,不可以调用。
最后输出结果为:
/*
-
undefined
-
Uncaught TypeError: p is not a function
*/
if(true) {
console.log(print()) // ???
function print() {
console.log('林一一')
}
}
console.log(print())
此处进入了条件成立内,函数print被定义,故可调用。
最后输出结果为:
/* 输出
林一一
undefined
林一一
undefined
/
if(!("value" in window)){
var value = 2019;
}
console.log(value);
console.log('value' in window);
第二行代码变量value会进行变量提升,提到全局作用域中,故“value”in window。再者,不管此处有无var value=2019这句话,value本身就是window上的一个属性,“value”in window必然为真,不进入条件判断后的语句中
最后输出结果为:
/* 输出
undefined
true
/
重名问题下的变量提升
- 带var和带function重名条件下的变量提升优先级。var会先进行变量提升,function再进行变量提升定义,此时函数的声明定义会覆盖var的变量提升,所以结果总是函数先执行
- 变量重名在变量提升阶段会重新定义,也就是重新赋值
铭记var变量提升优先于function,function的定义优先var
相关题目解析
console.log(a);
var a=1;
function a(){
console.log(1);
}
// 或
console.log(a);
function a(){
console.log(1);
}
var a=1;
// 输出都是: ƒ a(){ console.log(1);}
以上两种情况输出都是相同的,因为var先进行变量提升,然后function再进行变量提升,function的会覆盖var的,所以a函数
var fn = 12
function fn() {
console.log('林一一')
}
console.log(window.fn)
fn()
/* 输出
* 12
* Uncaught TypeError: fn is not a function
/
这段注意和上面的进行对比。var先进行变量提升,然后function再进行变量提升并定义,然后fn赋值为12。 故最终代码第五行window.fn为12,代码第六行相当于12(),故不成立。
console.log('1',fn())
function fn(){
console.log(1)
}
console.log('2',fn())
function fn(){
console.log(2)
}
console.log('3',fn())
var fn = '林一一'
console.log('4',fn())
function fn(){
console.log(3)
}
/* 输出
* 3
* 1 undefined
* 3
* 2 undefined
* 3
* 3 undefined
* Uncaught TypeError: fn is not a function
/
首先对变量提升进行分析,var最先提升,此时fn为undefined;然后代码2-4行进行提升并定义,接着代码7-9行进行提升并定义,最后代码15-17行进行提升并定义。故最后fn为代码15-17行的内容。所以前面六个输出如输出结果所示,当代码执行到第12行时,fn被赋值为了’林一一‘,故此时的fn()相当于’林一一()‘,不是函数,无法执行。
经典面试题
var a=2;
function a() {
console.log(3);
}
console.log(typeof a);
/* 输出
* number
/
var先变量提升,再function变量提升并定义,最后a赋值为2。当代码第五行时,a为2
console.log(fn);
var fn = 2019;
console.log(fn);
function fn(){}
/* 输出
fn(){}
2019
/
var变量提升,function变量提升并定义,所以代码第一行fn为函数,执行到代码第二行,fn赋值为2019,故代码第三行输出2019
let a = 0, b = 0;
function fn(a) {
fn = function fn2(b) {
console.log(a, b)
console.log(++a+b)
}
console.log('a', a++)
}
fn(1); // a, 1
fn(2); // 2, 2 5
首先进入fn代码,创建一个函数作用域下新的变量a并将其赋值为1。执行到代码第三行时,全局作用域下的fn为函数fn2了,代码第七行的a为函数作用域下的a,因为是a++,先输出1在进行加一操作,故输出a,1。
再次执行fn函数,上面代码的执行已经将fn变为了函数fn2,函数fn2为内部函数,其外部创建函数为原先的fn,此时含有闭包,即fn2可以使用原先fn函数作用域下的变量,在此处为变量a,值为2(a++进行了加一操作)。进入fn2函数,创建一个函数作用域下新的变量b并将其赋值为2,所以代码第四行输出 2 2。代码第五行,++位于a前,先进行加一操作在读值,故为3+2,代码第五行输出5
函数新参的变量提升
function a(b){
console.log(b);
}
a(45);
// 等价于
// function a(b) {
// var b = undefined;
// b = 45;
// }
经典面试题
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
var b = {
a,
c: b
}
console.log(b.c);
代码第六行var a=20,进行变量提升,故代码3-7行中的a都是函数作用域下的a(除了window.a)。最后b.c输出结果为undefined,因为=的优先级是从右到左,所以变量提升阶段b=undefined后,将c赋值为undefined
最后输出结果为:
/*
undefined
10
20
undefined
*/
var a = 1;
function foo(a, b) {
console.log(a); // 1
a = 2;
arguments[0] = 3;
var a;
console.log(a, this.a, b); // 3, 1, undefined
}
foo(a);
进入函数foo,先创建函数作用域下的变量a并赋值为1和变量b没有赋值,值为undefined。代码第三行的a是函数作用域下的a,代码第五行arguments[0]于函数作用域下的a对应,改变arguments[0]的值,a也跟随其改变。故代码第七行,a为函数作用域下的a,值为3;this为调用foo的对象window,故this.a值为1;b为undefined
非匿名函数自执行和匿名函数自执行的变量提升
- 非匿名函数自执行的函数名不会被提升到全局变量中,但会被提升到自己的作用域中,且将函数名进行修改是无效的
- 匿名函数自身不可被提升,毕竟也没有名字。但其中的var变量可以被提升当前函数作用域上
var a = 10;
(function c(){
})()
console.log(c)
// Uncaught ReferenceError: c is not defined
//非匿名函数自执行的函数名不会被提升到全局变量中
var a = 10;
(function a(){
console.log(a)
a = 20
console.log(a)
})()
// ƒ a(){a = 20 console.log(a)} ƒ a(){a = 20 console.log(a)}
//非匿名函数的函数名会被提升到自己的作用域中,且将函数名进行修改是无效的
(function() {
var message = "This is an anonymous function and its variable is hoisted.";
console.log(message); //"This is an anonymous function and its variable is hoisted."
})();
console.log(message); // 报错