3
头图

思维导图

image

推荐阅读地址 掘金

变量提升机制

一.什么是变量提升?

  • 变量提升示例
/* 你应该见过下面的类似代码,那你知道这是为什么*/
console.log(a)  // undefined
var a = 10
定义:变量提升是当栈内存作用域形成时,JS代码执行前,浏览器会将带有var, function关键字的变量提前进行声明 declare(值默认就是 undefined),定义 defined(就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义。
在变量提升阶段:带 var 的只声明还没有被定义,带 function 的已经声明和定义。所以在代码执行前有带 var 的就提前声明,比如这里的 a 就赋值成 undefined,在代码执行过程中遇到创建函数的代码浏览器会直接跳过。

不考虑变量提升阶段的 js 运行机制相关参考 JS 运行机制基础版

讲解示例

var a =12
var b = a
b = 1
function sum(x, y) {
    var total = x + y
    return total
}
sum(1, 2)

image

PS: 函数在调用时创建执行上下文对象还有其他关键的步骤作用域创建,this指向等这些内容放在后面文章讲,这样的机制有点类似变量提升。下面的函数创建过程都会被按作类似于变量提升来理解。
变量提升只发生在当前作用域。比如:在页面开始加载时,只有全局作用域发生变量提升,这时候的函数中存储的都是代码字符串。

二. 带 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

思考题

    1. 问下面分别输出什么?
// 1
console.log(a, b)
var a =12, b ='林一一'
function foo(){
// 2
    console.log(a, b)
// 3
    var a = b =13
    console.log(a, b)
}
foo()
console.log(a, b)

/* 输出:
    undefined undefined
    undefined "林一一"
    13 13
    12 13
*/
  • 2.. 问下面的结果和上面的有何不同?
console.log(a, b)
var a =12, b = '林一一'
function foo(){
    console.log(a, b)
//  var a =b =13
    console.log(a, b)
}
foo()
// 4
console.log(a, b)

/* 输出:
    undefined undefined
    12 "林一一"
    12 "林一一"
    12 "林一一
*/

解答

上面的思考题不知道你都对了没,下面让我来解答,详情看图
image

思路:1处的 a, b 其实就是window下面的属性为 undefined。在函数内部由于变量提升机制 avar 一开始就是 undefined,b不带var 将向上级作用域查找,找到全局作用域下的林一一所以2处打印出来的就是 undefined "林一一"。随后 a =13,window.b =13,即原来 b='林一一' 变成了 b=13,打印出13, 13,最后第4处打印处12, 13。所以结合流程图,很明显知道答案

  1. 问题3,再来看一道,问下面答案是什么?
a = 0
function foo(){
    var a =12;
    b = '林一一'
    console.log('b' in window)
    console.log(a, b)
}

foo()
console.log(b)
console.log(a)

/* 输出
    true
    12 "林一一"
    林一一
    0
/
思路:这是比较简单的一道题,需要注意的是函数内的 b 没有带 var,b 会一直向上查找到 window 下,发现 window 下也没有就直接给 window 设置了一个属性 window.b = '林一一',同理全局下的 a 也一样。

image

  • 问题4,问下面答案是什么?和问题3有什么区别
function foo(){
    console.log(a)
    a =12;
    b = '林一一'
    console.log('b' in window)
    console.log(a, b)
}
foo()
/* 输出
    Uncaught ReferenceError: a is not defined
/
思路:问题4和问题3的主要区别在于第一个 console.log(a) 处,因为 a 不在函数作用域内,就会向上找 window 下的作用域,发现也没有就会直接抛出引用错误 ReferenceError
  1. 经典面试题
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
/
思路:和问题4类似,不做分析

三. 等号左边下的变量提升

  • 函数左边的变量提升
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

image

四. 条件判断下的变量提升

  • if else 条件判断下的变量提升
console.log(a)
if(false){
    var a = '林一一'
}
console.log(a)
/* 输出
    undefined
    undefinedN
/
在当前作用域中不管条件是否成立都会进行变量提升

image

  • if else 条件判断下函数变量提升的坑

    • 新版浏览器中,在条件判断块级作用域之外使用条件内函数

      console.log(print())
      if(true){
          function print() {
              console.log('林一一')
          }
      }
      console.log(print())
      /* 输出
          undefined
          林一一
          undefined
      /
      • 新版浏览器中,在条件判断块级作用域中使用条件内函数
      if(true) {
          console.log(print())    // ???
          function print() {
              console.log('林一一')
          }
      }
      console.log(print())
      /* 输出
          林一一
          undefined
          林一一
      /
思路:{} 大括号属于块级作用域,在 if else 中带 function 的函数同样也会先被声明和定义所以条件判断中的 print() 可以直接使用

image

思考题

  1. 题目1,if判断语句中的变量提升
if(!("value" in window)){
    var value = 2019; 
}
console.log(value); 
console.log('value' in window); 

/* 输出
    undefined
    true
/
思路:和上面所说的一样,不管条件是否成立带 var 的变量提升,当前在全局作用域 value 就是 window 的属性,所以结果显而易见输出 undefined 和 true

五. 重名问题下的变量提升

  • 函数名和 var 声明的变量重名
var fn = 12
function fn() {
    console.log('林一一')
}
console.log(window.fn)
fn()
/* 输出
*  12
*  Uncaught TypeError: fn is not a function
/
思路:带 var 声明的和带 function 声明的其实都是在 window 下的属性,也就是重名了,根据变量提升的机制,JS 代码自上而下执行时此时的 fn 还只是fn = 12,所以 fn() == 12() 又是一个类型错误 TypeError

image

  • 变量重名在变量提升阶段会重新定义也就是重新赋值
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
/
思路:同样由于变量提升机制,fn 会被多次重新赋值最后赋值的地址值(假设为oxfffee)为最后一个函数,所以调用 fn都只是在调用最后一个函数输出都是 3, 代码执行到var fn = '林一一',所以 fn() 其实 == 林一一() 导致类型错误 TypeError

image

思考题

    1. 腾讯的一道变量提升的面试题
var a=2;
function a() {
    console.log(3);
}
console.log(typeof a);

/* 输出
 * number
 /
思路:这是一道比较简单的变量提升题,JS 代码自上而下执行时,a 被赋值成 2,输出就是 number

image

  • 2.. 再来一道面试题
console.log(fn);
var fn = 2019;
console.log(fn);
function fn(){}

/* 输出
    fn(){}
    2019
/
思路:这也是重名下的一道面试题,在变量提升阶段 fn由变量值声明为 undefined被修改定义为 fn函数本身 fn(){},所以第一个输出就是 fn(){},第二个输出 fn 由被赋值成 fn=12 输出12

参考

JavaScript面试题分析之变量提升和执行上下文

推荐

推荐阅读地址 掘金
感谢阅读,我是林一一,下次见

林一一
171 声望7 粉丝