JavaScripts高阶(2)变量提升,作用域链,暂时性死区

302 阅读13分钟

变量提升(预解析):

当前作用域(不管是全局还是私有作用域都要记得先变量提升,其中函数执行形成的私有作用域如果有形参先进行形参赋值) 中 js代码自上而下执行之前,浏览器首先会把带 ‘var’或者 ‘function’ 关键字的进行提前的‘声明或定义’

声明(declare):var num;在当前作用域声明

定义(defined):num=12;给声明的变量附一个值

页面加载时只把全局作用域中的变量提升,不管私有作用域里的变量,当函数执行时才把函数内的私有作用域下的变量提升

带var关键字的只是提前声明一下 并没有赋值;

带function关键字的在变量提升阶段把声明和定义都完成了

js代码执行时遇到创建函数的代码直接跳过(不在声明也不进行赋值操作)因为变量提升时 声明和定义都做过了

console.log(num) //=> undefined  此时由于变量提升只是 var num;
var num=13;      //=> num = 13  提升阶段已经声明了所以不再声明 只是赋值
console.log(num) //=> 13  此时代码执行了 已经赋值为13


console.log(fn)  //=> fn(){
                       //console.log(a)
                       //var a=10;
                       //console.log(a)
                    //}
                    //带function关键字的在变量提升阶段把声明和定义都完成了

fn();   //=>只要下面有,放在前面执行也可以  原因就是变量提升导致的
function fn(){      //=>遇到创建fn的代码直接跳过因为变量提升时 声明和定义都做过了
        //在私有作用域中的 私有变量  在函数执行时也进行变量提升
    console.log(a)  //=> undefined 原因是 声明了 未定义
    var a=10;
    console.log(a)  //=> 10
}
fn()

声明过的变量不再重复声明,遇到再次声明直接跳过

//=>1.全局下的变量提升:var x;var y; var z; fn=aaafff111;
    var x=10,
        y=20,
        z=30;
    function fn(x,y){
        //=>私有作用域
        //=>3.形参赋值 x=10 y=20 (x/y都是私有变量)在函数内所有的xy都是私有变量
        //变量提升: 按理说应该var x 应该重新变量提升,但是形参x已经存在,所以忽略var x,x是10而不是undefined
        console.log(x,y,z)      //=>  10 (私有的x) 20(私有的y) 30  (z是全局变量)
        var x=100;//私有的x=100,不再var
        y=200;//私有的y=200
        z=300;//全局的z=300
        console.log(x,y,z)  //100(私有的x) 200(私有的y) 300(z是全局变量)
    }
    fn(x,y,z)  //=>2.fn执行传递的是实参(实参都是值)fn(10,20,30)
    console.log(x,y,z)  //10 20 300

变量提升特殊情况

1.全局作用域下定义变量时带var 和不带var的区别

(1)如果当前是全局作用域,带var的声明了一个变量同时也给window全局作用域加了一个属性

(2)在全局作用域如果不带var只是给window加了一个属性,只不过在使用时把window.省略了(严格意义上不是一个变量)

//带var
console.log(a)  //=> undefined
var a=10;
console.log(a)  //=>10
console.log(window.a)   //=>10

//不带var
console.log(a) //=>报错  a is not defined
console.log(window.a) //=>undefined
a=12;    //其实是  window.a=12
console.log(a) //=>12
console.log(window.a) //=>12

2. 私有作用域下定义变量时带var 和不带var的区别

  • (1)在私有作用域里,带var的重新开始变量提升,声明为私有变量,和外界没有任何关系
  • (2)不带var不是私有变量,当前作用域没有声明的话会向他的上级作用域查找,看是否是上级的变量,是的话,操作的就是上级作用域的变量,
  • (3)不是上级的变量,向上继续查找,一直找到window为止(我们把这种查找机制叫做:“作用域链”),window有,操作的是window的变量(属性)
  • (4)window没有,如果带等号就是给window添加一个属性,如果不带等号,是undefined,也就是不带var我们在私有作用域中没有声明这个变量的情况下,操作的一直是别人的
    //带var
    function fn(){
        console.log(a); //=>undefined
        var a=12;
        console.log(a); //=>12
    }
    fn();
    console.log(a) //a is not defined  闭包机制(私有作用域保护里边的私有变量不受外界的干扰)

    //不带var
    function fn(){
        console.log(a); //=> a is not defined
        a=12;
        console.log(a); //=>12 其实找到的是 window.a
    }
    fn();
    console.log(a) //=>12  其实找到的是 window.a

作用域链

函数执行形成一个私有作用域(保护变量),进入私有作用域中,首先有形参的话形参赋值没有的话变量提升(声明的变量是私有的),接下来才是代码执行

1、 执行的时候遇到一个变量,如果这个变量是私有的,那么按照私有变量处理即可

    function fn(){
        console.log(a);     //=>undefined
        var a=12;
        console.log(a);     //=>12
    }
    fn();
    console.log(a)  //=> a is not defined  //闭包机制(私有作用域保护里边的私有变量不受外界的干扰)

2、 如果当前这个变量不是私有的,需要向它的上级作用域进行查找,上级如果没有,则继续向上查找,一直找到window全局作用域为止,我们把这种查找机制叫做 作用域链

  • 1)如果上级作用域有,我们当前操作的都是上级作用域中的变量(如果我们在当前作用域把值改了,相当于把上级作用域中的这个值给修改了)
  • 2)如果找到window也没有这个变量;变量=值:相当于给window设置了一个属性 以后再操作window下就有了;但是在赋值操作之前alert(变量):想要输出这个变量,此时是没有的,所以会报错
    function fn(){
        console.log(a);    //=> a is not defined
        a=12;
        console.log(a);    //=>12 其实找到的是 window.a
    }
    fn();
    console.log(a)         //=>12  其实找到的是 window.a

例题

    //变量提升 var a;var b; fn=AAAFFF111
    console.log(x,y)    //=> undefined  undefined
    var x=10,y=20;      //=>  x=10,y=20
    function fn(){
        //私有作用域变量提升 var x;(x私有变量)
        console.log(x,y)   //=>  undefined 20
        var x=y=100;     //相当于 var x=100 (私有); y=100(全局)
        console.log(x,y)  //=>  100(私有) 100(全局)
    }
    fn()
    console.log(x,y)  //=> 10 (全局) 100(全局)

只对等号左边的进行变量提升

=: 赋值,左边为变量,右边永远是值(右边不管是啥都要先计算出值再赋值给左边)

    //之前:
    i%2===0?item.className='c1':item.className='c2'
    //现在
    item.className = i%2===0?'c1':'c2'
    //先把等号右边的三元运算符计算为值再赋值给等号左边

匿名函数:函数表达式(把函数当做一个值赋值给变量或者其他内容 比如事件)

    oDiv.onclick=function(){}
    //等价于oDiv.onclick等于AAAFFF111  这样的一个函数空间地址(堆内存地址)

只对等号左边进行变量提升,右边是值不会提前声明(就算是函数也不会变量提升)

    console.log(fn)  => undefined 变量提升  var fn;=右边是值不进行变量提升
    var fn=function(){}
    console.log(fn) => 函数本身

声明函数 最好用函数表达式的方式 var fn=function(){}

  1. 因为只能对等号左边的进行变量提升,所以变量提升完成后,当前函数只是声明了,没有定义,要想执行只能放在赋值的代码之后执行(放在前面执行相当于让undefined执行,会报错的)

  2. 这样让我们的代码逻辑更加的严谨,以后想知道一个执行的函数做了什么功能只需要向上查找定义的部分即可

    sum();    //=> 因为 sum 是undefined  会报错 sum is not a function
    var sum=function(){}
    sum();     //=>不报错


    var fn=function sum(){    //==>不要用这种命名
        console.log(1)
    }
    sum();     //=>报错
    fn();      //=>  1

    var fn=function sum(){
        console.log(sum)  //=> 函数本身
        console.log(1)
    }
    fn();  // =>  1

不管条件是否成立都要进行预解析

不管条件是否成立,判断体中出现的var/function都会进行变量提升

新版浏览器中,function声明的变量只声明不能定义(前提:函数必须在判断体中)

老版本浏览器低版本ie 不管条件成不成立 function变量提升并定义

	//新版浏览器
    console.log(num)    =>  undefined
    console.log(fn)     =>  undefined
    if(1 === 1){   //或者if(1 != 1) 都对变量提升没有任何影响
        var num = 12;
        function fn(){

        }
    }

代码执行到条件判断地方

    1. 条件不成立时】 进入不到判断体中,赋值的代码不执行,此时依然是undefined
    1. 条件成立时】 进入判断体中的第一件事不是代码执行,而是把之前变量提升没有定义的函数首先定义了进入判断体中函数就定义了,为了迎合es6的块级作用域)
    1. 老版本浏览器低版本ie 不管条件成不成立 function变量提升并定义
    //类似于es6的块级作用域
    console.log(num)    =>  undefined
    console.log(fn)     =>  undefined
    if(1 === 1){   //或者if(1 != 1) 都对变量提升没有任何影响
        console.log(num)    =>  undefined
        console.log(fn)     =>  函数本身
        var num = 12;
        function fn(){

        }
         console.log(num)    =>  12
         console.log(fn)     =>  函数本身
    }


    console.log(fn)   => undefined
    for(var i=0;i<1;i++){   //i<1 也是条件
        function fn(){

        }
    }

案例

    //=>变量提升:没有

    f= function (){ return true;};
    g= function (){ return false;};
    ~function(){
        //=>私有作用域
        //=> 变量提升:新版本浏览器 g=undefined (不管条件是否成立都要进行变量提升,但是新版本浏览器只对函数进行声明,不定义)
        if(g() && [] == ![]){   //在新版本浏览器中 直接报错
                                //旧版本浏览器g()为私有 true
                                //![]   !符号转化为了boolean
                                // [] == false
                                // == 操作符 不同类型先转数字 再比较
                                // 0 == 0
                                // true

                                //typeof []  //Object
				//typeof ![] //Boolean
            f = function(){ return false}
            function g(){
                return true;
            }
        }
    }()
    console.log(f())  => 新版本 前边就报错; 旧版本false
    console.log(g())  => 新版本 前边就报错; 旧版本false

变量提升重名的处理

在变量提升阶段,如果名字重复了,不会重新进行声明,但是会重新进行定义(一般都是指函数)(后面赋的值把前边赋的值给替换掉)

函数变量提升并定义后,代码至上而下执行,遇到声明定义函数的代码时不再声明和定义

    //=>变量提升:
        1var fn=aaafff111
        2、fn=aaafff222
        3、不进行任何操作
        4、fn=aaafff333
        5、fn=aaafff444
    // =>代码执行
    fn()        =>  4
    function fn(){ console.log(1)}    //忽略
    fn()        =>  4
    function fn(){ console.log(2)}    //忽略
    fn()        =>  4
    var fn=13;  =>  fn=13    //赋值
    fn()        =>  报错 fn is not a function  不再向下执行
    function fn(){ console.log(3)}
    fn()
    function fn(){ console.log(4)}
    fn()

var 变量提升 之所以为 undefined 不是因为赋值为了undefined而是因为不进行赋值操作才是undefined

只声明不赋值默认值为undefined

只有在全局作用域中我们声明一个变量,相当于给全局对象window增加了一个属性名

ES6中的let不存在变量提升

当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测)

在ES6中基于LET/CONST等方式创建的变量或者函数,不存在变量提升机制切断了全局变量和WINDOW属性的映射机制

在相同的作用域中,基于let不能声明相同名字的变量不管用什么方式在当前作用域下声明了变量,再次使用let创建都会报错(不同作用域下可以重复互不影响)

ES6同样具有作用域链

虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所用所有变量,一旦发现有重复的,直接抛出异常,代码也不会再执行了

虽然没有把变量提前声明定义,但是浏览器已经记住了当前作用域下有哪些let创建的变量,在let之前使用就会报错在let之前进行=赋值也报错

检测语法规范,看一下是否是基于新语创建的变量,如果是按照新语法规范来解析

下边有let声明的这个变量,那么在let代码之前 赋值和获取都是报错

//console.log(a)  报错
let a=12;
console.log(window.a)   undefined
var a=12;
var a=13;
console.log(a)  //=>13


let a=12;
let a=13;  //报错    在相同的作用域中,基于let不能声明相同名字的变量
console.log(a)    //不执行

let a=12;
console.log(a)  //报错  在代码执行前就已经验证变量是否重名
let a=13;  
console.log(a)


a=12;//报错  虽然没有把变量提前声明定义,但是浏览器已经记住了当前作用域下有哪些用let创建的变量在let a之前使用就会报错  a is not defined
console.log(a)  
let a=13;  
console.log(a)


b=12;
console.log(b)  //12
a=13//报错  虽然没有把变量提前声明定义,但是浏览器已经记住了当前作用域下有哪些用let创建的变量在let a之前使用就会报错  a is not defined
console.log(a)  
let a=14;  
console.log(a)
let a=10;
let b=10;
let fn = function(){
	//console.log(a)    //=>直接报错 虽然没有把变量提前声明定义,但是浏览器已经记住了当前作用域下有哪些用let创建的变量在let a之前使用就会报错  a is not defined (不会去找上级的变量a)
	//a是私有作用域下的变量
	////console.log(b)  //全局10
	let a=b=20;
	console.log(a,b)    //=>(把第一个console.log去掉后)  20私有 20全局
}
fn()
console.log(a,b)        //=>(把第一个console.log去掉后) 10全局  20全局 

JS中的暂时性死区

在代码块内,使用let命令声明变量之前该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)

ES6明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域

凡是在声明之前使用这些变量,就会报错

在原有浏览器渲染机制下(es5),基于typeof等逻辑运算符检测一个未被声明过的变量,不会报错,返回'undefined'

如果当前变量是基于ES6语法处理,如果在没有声明(let之前)之前使用typeof检测会直接报错

基于let创建变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语创建的变量,如果是按照新语法规范来解析

ES6if后面的{}形成块级私有作用域

上级块级作用域不是上级作用域(上级作用域只能由函数执行形成)

var a=12;
if(true){
	console.log(a)  //报错 a is not defined  (不会去找上级作用域的变量a)
	let a=13;    //基于let创建变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语创建的变量,如果是按照新语法规范来解析
}

//ES6中if后面的{}形成块级私有作用域