搞懂javascript中的变量提升

109 阅读9分钟

变量

代码中的变量提升

/* 你应该见过下面的类似代码,那你知道这是为什么*/ 
console.log(a)  // undefined 
var a = 10

定义:变量提升是当栈内存作用域形成时,JS代码执行前,浏览器会将带有var, function关键字的变量提前进行声明 declare(值默认就是 undefined),定义 defined(就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义。 在变量提升阶段:带 var 的只声明还没有被定义,带 function 的已经声明和定义。所以在代码执行前有带 var 的就提前声明,比如这里的 a 就赋值成 undefined,在代码执行过程中遇到创建函数的代码浏览器会直接跳过。

带 var 和不带 var 的区别

  • 全局作用域中不带var声明变量虽然也可以但是建议带上 var声明变量,不带 var 的相当于给window对象设置一个属性罢了。

  • 私有作用域(函数作用域),带 var 的是私有变量。不带 var 的是会向上级作用域查找,如果上级作用域也没有那么就一直找到 window 为止,这个查找过程叫作用域链

  • 全局作用域中使用 var 申明的变量会映射到 window 下成为属性。

面试题

a = 12  // == window.a
console.log(a)  // 12
console.log(window.a) // 12

var a = b =12   // 这里的 b 也是不带 var 的。
/* 相当于*/
var a = 12;
b = 12
console.log(a, b) //undefined undefined ,因为变量提升var a b 到这里还没有给值
var a =12, b ='ld'
function foo(){
    console.log(a, b)  undefined 'ld'
    var a = b =13 //这里a是函数的局部变量,b还是全局变量,因此a提升到函数顶部没有给值,了拿到的是window上的值
    console.log(a, b) //13 13
}
foo()
console.log(a, b) //12 13

console.log(a, b) //undefined undefined
var a =12, b = 'ld'
function foo(){
    console.log(a, b) //12 'ld'
    console.log(a, b) //12 'ld'
}
foo()
console.log(a, b) //12 'ld'
a = 2
function foo(){
    var a =12;
    b = 'ld'
    console.log('b' in window)
    console.log(a, b)
}

foo()
console.log(b)
console.log(a)
a = 2
function foo(){
    var a =12;
    b = 'ld'
    console.log('b' in window) //true
    console.log(a, b) //12 'ld'
}

foo()
console.log(b) //ld
console.log(a) //2 
这道题注意带var和不带var区别
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);
}

等号左边下的变量提升

print() 
function print(){ console.log('ld') } print()


//var print
print() //print1 is not a function
var print = function() {
    console.log('ld') 
}
print()
//同样由于变量提升机制带 `var` 的 print 是一开始值是 `undefined`,所以 print() 这时还不是一个函数,所以报出 类型错误TypeError

条件判断下的变量提升

console.log(a)
if(false){
    var a = 'ld'
}
console.log(a)
**在当前作用域中不管条件是否成立都会进行变量提升**

  • if 中 () 内的表达式不会变量提升
var y = 1
if(function f(){}){ 
    console.log(typeof f)  // undefined
    y = y + typeof f
}
console.log(y) //1undefined

console.log(print())    // == window.print()
if(true){
    function print() {
        console.log('ld')
    }
}
console.log(print())

console.log(a) //undefined
console.log(p()) //p is not a function
if(true){
    var a = 12
    function p() {
        console.log('ld') 
    }
}
if(true) {
    console.log(print())    //
    function print() {
        console.log('ld')
    }
}
console.log(print())
if(!("value" in window)){
    var value = 2019; 
}
console.log(value);  //undefined
console.log('value' in window); //true

原因:不管条件是否成立带 `var` 的变量提升,当前在全局作用域 `value` 就是 `window` 的属性,所以结果显而易见输出 `undefined 和 true`
var fn = 12
function fn() {
    console.log('林一一')
}
console.log(window.fn)
fn()

//解释
var fn
var fn function
给值阶段 fn=12 
因此是window.fn12 函数fn已经被给值12,所以最后fn()会报出 fn is not a function

重名问题下的变量提升

带 var 和带 function 重名条件下的变量提升优先级,函数先执行

var 和 function 同名的变量提升的条件下,函数会先执行。所以输出的结果都是一样的。换一句话说,var 和 function 的变量同名 var 会先进行变量提升,但是在变量提升阶段,函数声明的变量会覆盖 var 的变量提升,所以直接结果总是函数先执行优先。

console.log(a);    //ƒ a(){
    console.log(1);
}
var a=1;
function a(){
    console.log(1);
}
console.log(a);    //ƒ a(){
    console.log(1);
}
function a(){
    console.log(1);
}
var a=1; 
console.log('1',fn())
function fn(){
    console.log(1)
}

console.log('2',fn())
function fn(){
    console.log(2)
}

console.log('3',fn())
var fn = 'ld' //因为在这里把fn函数变成了字符串,因此下面4中fn就报不是函数了

console.log('4',fn()) //fn is not a function 
function fn(){
    console.log(3)
}
3
VM473:1 1 undefined
VM473:16 3
VM473:6 2 undefined
VM473:16 3
VM473:11 3 undefined
var a=2;
function a() {
    console.log(3);
}
console.log(typeof a); //Number **JS 代码自上而下执行时,`a` 被赋值成 2,输出就是 `number` 型**
console.log(fn);// fn(){} //原因是变量提升比函数在前
var fn = 2019;
console.log(fn);
function fn(){}

/*
var fn 变量
var function fn(){}
fn=2019
*/
console.log(fn); //undfined
var fn = 2019;
console.log(fn); //2019
var fn=function fn(){}
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); 
fn(2);

let a
var b fn

a=0 b=0
fn= (a){}

fn(1)
fn=fn2(b){
    console.log(a,b)
    console.og(++a+b)
}
打印 console.log('a',a++)  // a,1
fn(2) //这时候就执行fn=fn2(b)这个函数了,因为就打印里面2个console.log,这时候a=1,b传进去是2  打印出 2,2   5

函数形参的变量提升

function fn(b){ 
//这里开参的变量提升等于是在这里var b = undefined;
  console.log(b);  
}
fn(45);
var a = 1;
function fn(a) {
    console.log(a)
    var a
    console.log(a)
}
fn(a);

//变量提升阶段
全局 var a
fn(){}
fn函数局部变量提升
var a 

给值执行阶段
fn(a) 把函数里面的console.log(a) 直接打印出来的就是传进去的实参 1
var foo = 'ld';
(function(f){
    console.log(foo); //undfined
    var foo = f || 'hello';
    console.log(foo) //ld
})(foo);
console.log(foo) //ld

变量提升阶段
var foo=undefined
函数内部变量提升
var foo=undefined
执行阶段从上到下、赋值阶段

全局foo='ld'
函数立即执行进入,局部赋值
console.log(foo) //undfined

foo=形参传进来的f ‘ld’ 
console.log(foo) // 'ld'

全局下面console.log(foo)//'ld'
var foo = 'ld';
(function(foo){ //注这里传进来已经有值,里面打印的就是这个值
    console.log(foo); //ld
    var foo = foo || 'world';
    console.log(foo) //ld
})(foo);
console.log(foo) //ld

分析:

变量提升阶段
var foo=undefined

执行阶段、赋值阶段
僵尸foo='ld'

立即执行函数里
var foo=undefined
console.log(foo) //‘ld’ 原因是传进来的参数是ld 
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=undefined
var b=undefined

执行阶段从上往下,到赋值
a=10 
函数立即执行,里面变量提升
var a=undefined
console.log(a) //undfined
a=5  //函数私有变量
console.log(window.a) //10
a=20 //函数私有变量
console.log(a) //20
    
b={a,c:b} //b={a:10,c:undfined}
console.log(b.c) //undfined
var a = 1;
function foo(a, b) {
  console.log(a); 
  a = 2;
  arguments[0] = 3;
  var a;
  console.log(a, this.a, b);
}
foo(a);

分析过程:
var a=undefined
foo function(){}

执行过程 赋值
a=1
foo(a) //foo(1)
function foo(1,b){
    var a=undefined  //变量提升
    console.log(a) //1 输出的是传进来的参数a
    a=2 把a赋值2
    arguments[0]=3  //把第一个参数赋值3 也就是传进来的a
    var a 因为变量提升已经做过一次了,这里注意点 var a 在形参阶段声明一次后不会再声明即可
    console.log(a,this.a,b) //3,1,undfined  //注this是window,b没有传进来值
}

匿名自执行函数

var a = 10;
(function c(){
})()
console.log(c)
// Uncaught ReferenceError: c is not defined 匿名自执行函数不会变量提升

var a = 10;
function c(){
}
console.log(c) //function c(){} //函数会变量提升

在函数内注意加只有声明var定义的变量就是局部变量和外面没有关系,但是如果没有var那就是全局的,除有传参数的情况下

var a = 10;
(function(){
    console.log(a) //10 全局
    a = 20
    console.log(a)  //20
})()
console.log(a) //20

看下面这个代码就是区别,加了var,就是局部了结果就不同了

var a = 10;
(function(){
     //var a=undfined 变量提升局部变量
    console.log(a)  //undfined
    var a
    a = 20 
    console.log(a) //局部变量 20
})()
console.log(a) //全局变量10

非匿名自执行函数-全局函数不会提升

var a = 10;
(function a(){
    console.log(a) //f a(){console.log(a) a=20 console,log(a)} //**而且非匿名自执行函数名是不可以修改的,即使修改了也不会有任何作用,严格模式下还会报错**
    a = 20
    console.log(a) //**而且非匿名自执行函数名是不可以修改的,即使修改了也不会有任何作用,严格模式下还会报错**因些这里还是第一次的赋值函数而不是20
})()

解释:
全局变量提升
var a=undefined

赋值 执行阶段
a=10
执行非匿名自执行函数内部
变量提升
变量a=undefined
函数a=(){}
函数赋值阶段
a=20  接着a又赋值函数 (){
    console.log(a)
    a = 20
    console.log(a) 
}

匿名和非匿名自执行函数区别

//匿名函数
var a = 10;
(function (){
    console.log(a) //undfined
})()

//非匿名函数
var a = 10;
(function a(){
    console.log(a) //ƒ a(){console.log(a)}
})()
匿名函数
var a = 10;
(function (){
    console.log(a)
    a = 20
})()
console.log(a)
VM9715:3 10
VM9715:6 20

//非匿名函数
var a = 10;
(function a(){
    console.log(a)
    a = 20
    console.log(a)
})()
console.log(a)
VM9683:3 ƒ a(){
    console.log(a)
    a = 20
}
VM9683:6 10
//这种非匿名函数的情况下在自执行函数内部函数名本身就是a会做为内部函数的提升变量,而且赋值只能是一次,后面再赋值的不会修改,例如下面a=20就赋值失败了,而且内部的a=20也不会影响外层全局的a

接着再来点面试题,我以自己的理解写出了执行顺序过程

console.log(a, b, c);  //
var a = 12,
    b = 13,
    c = 14;
function fn(a) {
    console.log(a, b, c);
    a = 100;
    c = 200;
    console.log(a, b, c);
}
b = fn(10);
console.log(a, b, c)

解释:
变量提升
var a=undefined b=undefined c=undefined fn=function(){}

开始从上往下执行,赋值
console.log(a,b,c) //undefined, undefined ,undfined
a=12 b=13 c=14 第一步赋值
b=fn(10) 执行fn函数进入函数内:
内部有传入a参数10
console.log(a,b,c)// 10,13 ,14
私有变量a=100 全局c=200
console.log(a,b,c)// 100,13,200
函数结束
console.log(a,b,c) //全局最后12,undefined,200


undefined undefined undefined
VM92:6 10 13 14
VM92:9 100 13 200
VM92:12 12 undefined 200
var a=1;
var obj ={
   name:"tom"
}
function fn(){
   var a2 = a;
   obj2 = obj;
   a2 =a;
   obj2.name ="jack";
}
fn();
console.log(a);
console.log(obj);

分析:

全局变量提升:
var a=undefined obj={} fn=function

从下往下执行,赋值
a=1 obj={name:'tom'} 
fn() 进入函数内部:
没有参数,看有没有var有,就变量提升
var a2=undefined
a2=1
obj2=obj 指向
a2=a
obj2.name='jack'
函数结束
console.log(a) //1
console.log(obj) //{name:'jack'}
var a = 1;
function fn(a){
    console.log(a)
    var a = 2;
    function a(){}
}
fn(a);

分析:
全局变量提升:
var a=undefined fn(){}

执行赋值:
a=1
fn(1)
进入fn函数中
var a=function(){}
console.log(a) //undfined
a=2:
console.log(a); 
var a=12; 
function fn(){
    console.log(a); 
    var a=13;   
}
fn();   
console.log(a);

分析:
全局变量提升:
var a=undefined  fn=function

执行赋值:
a=12 
fn() 进入函数中:
var a=undefined
console.log(a) //undfined
a=13
函数结束
console.log(a) //12 
console.log(a); 
var a=12;
function fn(){
    console.log(a); //12
    a=13;
}
fn();
console.log(a); //13
console.log(a); //a is not defined 没有var 变量不会提升
a=12;
function fn(){
    console.log(a);
    a=13;   
}
fn();
console.log(a);
var foo='hello'; 
(function(foo){
   console.log(foo);
   var foo=foo||'world';
   console.log(foo);
})(foo);
console.log(foo);