变量提升总结

88 阅读10分钟

什么是变量提升

变量提升是当栈内存作用域形成时,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); // 报错

参考文章:juejin.cn/post/693337…