js的变量提升

195 阅读6分钟

变量提升:在当前上下文中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)

  • 会把当前上下文中所有带VAR/FUNCTION关键字的进行提前的声明或者定义
  •   var a=10;
    
  •   声明declare:var a;
    
  •   定义defined:a=10;
    
  • 带VAR的只会提前的声明
  • 带FUNCTION会提前的声明定义
例1:
    代码执行之前:全局上下文中的变量提升
     // var a;   默认值是undefined
    console.log(a); //=>undefined
    var a = 12; //=>创建值12 不需要在声明a了(变量提升阶段完成了,完成的事情不会重新处理) a=12赋值
    a = 13; //全局变量a=13
    console.log(a); //=>13

例2:
    全局上下文中的变量提升
    // func=函数   函数在这个阶段已经声明+定义了,所以赋值都做了
    func();
    function func() {
    	var a = 12;
    	console.log('OK');
    }
    // OK
    
    
    func(); //=>Uncaught TypeError: func is not a function
    var func = function () {
        // 工作中建议用函数表达式创建函数,因为这样在变量提升阶段只会声明FUNC,不会赋值
        console.log('OK');
    };
    func();
    
    var func = function AAA() {
    // 把原本作为值的函数表达式匿名函数“具名化”(虽说是起了名字,但是这个名字不能在外面访问 =>也就是不会在当前当下文中创建这个名字)
    // 当函数执行,在形成的私有上下文中,会把这个具名化的名字做为私有上下文中的变量(值就是这个函数)来进行处理
    console.log('OK');
    // console.log(AAA); //=>当前函数
    // AAA(); 递归调用   而不用严格模式下都不支持的 arguments.callee 了
    };
    // AAA();  //=>Uncaught ReferenceError: AAA is not defined
    func();
    
    
例3:
    EC(G)变量提升   只有VAR/FUNCTION会变量提升(ES6中的LET和CONST不会) 
    
    console.log('OK'); //=>'OK'
    console.log(a); //=>Uncaught ReferenceError: Cannot access 'a' before initialization  不能在LET声明之前使用变量
    let a = 12;
    a = 13;
    console.log(a);  // 13

基于“VAR或者FUNCTION”在“全局上下文”中声明的变量(全局变量)会“映射”到GO(全局对象window)上一份,作为他的属性;而且接下来是一个修改,另外一个也会跟着修改;

例4:
    var a = 12;
    console.log(a); //=>12  全局变量
    console.log(window.a); //=>12 映射到GO上的属性a
    
    window.a = 13;
    console.log(a); //=>13 映射机制是一个修改另外一个也会修改

遇到判断时,不论条件是否成立,都要进行变量提升(细节点:条件中带FUNCTION的在新版本浏览器中只会提前声明,不会在提前的赋值了)

例5:
    EC(G):全局上下文中的变量提升  全局上下文中声明一个a也相当于 window.a
    [老版本]  // 一般为IE10及以下
      var a;
      func=函数;
    [新版本]
      var a;   
      func;  //只声明,不定义

    console.log(a, func); //=>新:undefined undefined,老:undefined function func() {}
    if (!("a" in window)) {
        //=>"a" in window 检测a是否为window的一个属性   !TRUE => FALSE
        var a = 1;
        function func() {}
    }
    console.log(a); //=>undefined
    
例6:
    // EC(G)变量提升
    //  fn=>1
    //    =>2
    //  var fn;  已经声明过了
    //    =>4
    //    =>5
    //全局上下文中有一个全局变量fn,值是输出5的函数(此时window.fn=>5)
    fn(); //=>5
    function fn(){ console.log(1); }  //=>不再处理,变量提升阶段搞过了
    fn(); //=>5
    function fn(){ console.log(2); }
    fn(); //=>5
    var fn = function(){ console.log(3); }  //=>var //fn不用在处理了,但是赋值在变量提升阶段没处理过,此处需要处理  fn=window.fn=>3
    fn(); //=>3
    function fn(){ console.log(4); }
    fn(); //=>3
    function fn(){ console.log(5); }
    fn(); //=>3

下面看一个最新出炉的很恶的一个面试题:

例7:
    var a = 0;
    if (true) {
        a = 1;
        function a() {};
        a = 21;
        console.log(a)
    }
    console.log(a);
    这个题考虑的是关于新老版本浏览器的交替
    现在最新版本的浏览器是很惨的:
    -->向前兼容ES3/5规范
    1.判断体和函数体不存在块级上下文,上下文只有全局和私有
    2.不论条件是否成立,带function的都要声明+定义
    -->向后兼容ES6规范
    1.块级作用域,大括号中出现let/const/function...都会被认为是块级作用域
    2.不论条件是否成立,带function的只提前声明,不会提前赋值了

此题我们以画图的形式来分析

结合前几章来看,

1.首先创建了EC Stack 执行环境栈,并且创建了一个供全局代码执行的EC(G)执行环境上下文,并且进栈执行

2.在里面形成GO全局变量以及VO(G)全局变量对象

3.然后就是代码部分了
    首先进行变量提升,var a    function  a(){},同时也会映射到window中一份
    由于老版本浏览器带function的会声明+定义,所以就确定了其作用域,也会创建一个堆内存,用来存放函数的属性和方法,并且会有一个16进制的地址存放在栈内存当中供别人a指针指向
        堆内存中包含:函数内容的字符串以及名字、形参个数、原型....
    由于a在声明变量时已经声明过了,所以函数a就不再声明了,只有赋值,指针指向了函数a的16进制地址

开始执行代码

1.var a = 0;声明过了就不在声明只有赋值,所以a指针从函数a的16进制地址指向了0;
2.判断条件成立,a又指向了1;
3.a声明过了不再声明,略过
4.a又指向了21;

所以老版本浏览器结果就是 21、21

而新版本浏览器就不一样了

前两条与老版本浏览器一样,都是创建执行环境栈以及全局变量和全局变量对象
3.然后就是代码部分了(新版本)
    首先进行变量提升,var a    function a,新版本只声明不定义,同时也会映射到window中一份
    由于a在声明变量时已经声明过了,所以函数a就不再声明了,所以变量提升阶段只有一个a

开始执行代码

1.var a = 0;声明过了就不在声明只有赋值,所以a指针从函数a的16进制地址指向了0;
2.判断条件成立,此时遇到了大括号,而且里面有function,形成了一个块级作用域
3.形成块级作用域后,也就有了块级私有上下文EC(block),也会形成块级私有上下文的变量对象AO(block)
    在其内部初始化了作用域链<EC(block)-->EC(G)>
    块级作用域没有自己的this,用的是上下文中的this,与箭头函数类似
    也会有变量提升,function a(){};此时的变量提升,会声明+定义,与老版本的一样
    所以此时的块级作用域下的a是私有的
4.开始执行里面的代码,
    a指向了1;
    
    遇到了function(){}因为要兼容ES3/ES6,function a在全局下声明过,也在私有下处理过,遇到此行代码,
    私有下不会再处理,但是浏览器会把当前代码"之前",所有对a的才做,映射给全局一份,以此兼容ES3,但是它后
    面的代码和全局就没有任何关系了
    此时把a = 1映射给了全局一份
    
    a又指向了21;
    
    所以新版本浏览器结果就是 21、1