美文网首页
变量提升

变量提升

作者: 田成力 | 来源:发表于2019-10-11 17:14 被阅读0次

    堆内存 & 栈内存

    JS中的内存一共两种:堆内存和栈内存

    堆内存

    作用:用来存储引用数据类型值的内存空间叫做堆内存(对象存储的是键值对,函数存储的是代码字符串),当前堆内存释放销毁那么这个引用值就彻底没了。

    当堆内存没有你被任何 的变量或者其他东西所占用,浏览器会在空闲时间自主的把内存回收,把所有不被占用的堆内存销毁掉。(谷歌浏览器)
    xxx=null通过空对象指针null可以让原始变量或者其他东西谁都不指向,那么原有被占用的堆内存就没有被东西占用了,浏览器会销毁它。

    形成:只要遇到对象/数组/正则/函数等引用类型的值,浏览器首先第一步就是创建一个堆内存...

    释放:如果当前的堆内存被变量(或者函数以及元素事件等)占用了(占用了:堆内存地址赋值给变量了),此时的堆内存是有用的,不能销毁;我们想要手动释放堆内存,只需要让存储地址的变量等于其它值即可(最好等于null,null是空对象指针,本意就是不指向任何的堆内存);

    var o={name:'珠峰培训'};//<=> o=AAAFFF000
    o=null;//=> 此时堆内存不被占用,浏览器会在空闲的时间,把所有不被占用的堆内存进行自动回收释放(谷歌浏览器的机制,IE浏览器是靠计数器来统计当前堆内存被占用的次数:当计数器统计为零次,说明没有人占用它,浏览器销毁这个堆内存)
    

    栈内存

    作用:又称为作用域,目的就是提供JS代码执行的环境(供代码执行的),基本数据类型``值都是直接的存储在栈内存中,当栈内存销毁存储的那些值也都销毁了。

    全局作用域(栈内存):
    形成:浏览器渲染页面,首先就会形成一个全局作用域
    销毁:关闭当前页面(F5刷新页面:先把页面关闭,然后再重新打开)

    私有作用域(栈内存):
    形成:函数执行会形成私有的作用域
    销毁:一般情况下,函数体中的代码执行完成,形成的栈内存会立即释放;

    作用域不释放:

    不立即销毁:若应函数返回一个需要被执行小函数,这是不会立即销毁,只有等返回的小函数执行后这个函数才销毁
    不销毁:若函数内的内容被外界给占用了,则这个函数就不销毁了

    不释放的栈内存

    一般情况下,函数执行完成,形成的私有作用域都会自动释放;

    但是有很多时候,函数执行完,形成的作用域(栈内存)无法释放:当前私有栈内存中的某些东西被栈内存以外的其它内容占用了

    当我们手动把栈内存以外占用其内容的东西清除掉(或者不让其在占用了),之前没有销毁的栈内存会在浏览器空闲的时候自动销毁释放

    var obj={
        name:'珠峰培训',
        fn:(function(){
            var i=10;
            return function(){
                i++;
                console.log(i);
            }
        })()//=>如果传obj.name会报错:因为此时键值对还没有完全存储到堆内存中呢,obj和堆内存还没有关系呢,obj=undefined (错误:Cannot ready property 'name' of undefined)
    };
    obj.fn();
    obj.fn();
    ...
    
    
    //var obj={name:'xxx'};
    //obj.fn=(function(){})(obj.name); 这样是可以的,因为此时的obj已经和堆内存关联在一起了
    

    变量提升

    当前作用域中,JS代码自上而下执行之前,浏览器首先会把所有带var/function关键字的,进行提前的声明(declare)/定义(defined),这种提前声明变量的机制,我们称之为变量提升

    • 变量提升阶段:带var的只声明未定义。带function的声明和赋值都完成了
    • 变量提升只发生在当前作用域,
    • 在全局作用域下声明的函数或者变量是全局变量,同理在私有作用域声明的变量是私有变量,带var、function的才是声明
    • 浏览器很懒做过的事情不会重复执行第二遍,当代码执行遇到创建函数这部分代码后直接的跳过即可,因为在变量提升阶段就已经完成函数的执行了
    • (函数执行)私有作用域形成后,也不是立即执行代码,而是先进行形参赋值,然后变量提升。在es3和es5语法规法中只有全局作用域和函数执行的私有作用域(栈内存)其他的大括号不会形成栈内存

    变量提升规律

    1.只会对等号左边的进行变量提升(function作为值时不会变量提升)
    2.return后面的也不能变量提升,对return下面的还是要变量提升
    3.重名变量,不会重复声明,但可以多次被定义赋值,后面的定义会把前面的值覆盖。
    4.自执行函数本身的function不进行变量提升,定义+执行一起完成
    5.不管条件是否成立,对待var关键字的进行变量提升,但是function关键字,标准浏览器下声明,ie9以上浏览器是声明+定义

    带var

    > console.log(a);//->undefined
    > var a =12;
    > console.log(a);//->12
    > console.log(window.a);//->12
    > 在全局作用域当中,我们声明一个变量相当于给全局对象window增加了一个属性名。
    

    不带var

    > console.log(a);//uncaught ReferenceError:a is not defined
    >console.log(window.a);//->undefined
    > a =12;
    > console.log(a);//->12
    > console.log(window.a);//->12
    
    

    带var不带var的区别?

    在全局作用域下声明一个变量,也相当于给window全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和window没啥关系 )

    • 带var进行变量提升
    • 不带var :在全局作用域中,仅仅给全局对象设置了一个新的属性名把window省略了.
    • 项目当中,目的是创建变量,最好不要省略var,更严谨。
    • 全局变量和window中的属性存在映射机制。

    私有作用域中带var和不带var也有区别:
    带var的在私有作用域变量提升阶段都声明为私有变量和外界没有任何关系
    不带var不是私有变量会向他的上级作用域查找,看是否为上级的变量,不是的话就向上级作用域找,一直找到window为止,我们把这种查找机制叫做作用域链.也就是我们在私有作用域中操作的这个私有变量是一直操作别人的

    • 不带var的查找顺序
    • 1.看是不是私有变量(形参和私有作用域里声明的)是的话就是它
      -2.若不是,则往上级作用域查找,查找过程叫做作用域链

    什么是上级作用域?

    函数在哪里定义,则上级作用域就是谁

    上级作用域只跟函数在哪定义有关,跟函数在哪里执行没有关系。

    只对等号左边的进行赋值

    =:赋值,左边是变量,右边都应该是值。(如果是个函数表达式,把函数变成一个值(空间地址)赋值给变量)

    console.log(fn);//undefined
    var fn=function(){}
    console.log(fn);//函数本身
    
    //=>全局变量提升:var x; var y; var z; fn=AAAFFF000;
    var x = 10,
        y = 20,
        z = 30;
    function fn(x, y) {
        //=>[私有作用域]
        //=>形参赋值:x=10 y=20 (x/y都是私有变量)
        //=>变量提升:var x(忽略的,已经存在x这个名字了)
        console.log(x, y, z);//=>z不是私有变量是全局变量 10 20 30
        var x = 100;//=>私有的x=100
        y = 200;//=>私有的y=200
        z = 300;//=>全局的z=300
        console.log(x, y, z);//=>100 200 300
    }
    fn(x, y, z);//=>FN执行传递的是实参(实参都是值) fn(10,20,30)
    console.log(x, y, z);//=>10 20 300
    
    

    真实项目中创建函数一般都用函数表达式
    1.因为只能对等号左边进行变量提升,所以变量提升完成后,当前函数只是声明了,没有定义,想要执行函数,只能放在赋值的代码之后执行,放在代码前会报错
    2.这种方法代码逻辑更加严谨,以后想要知道一个执行的函数做了什么功能,只需要向上查找定义的部分即可(不会存在定义的代码在执行下面的情况)
    var fn = function sum(){
    console.log(sum);//函数本身
    console.log(1);
    };
    sum();//uncaught referenceerror:sum is not defined.sum在函数外面不能用在里面可以用
    fn();

    不管条件是否成立都要进行变量提升
    console.log(num);//undefined
    console.log(fn);//undefined
    if(1===1){
    console.log(num);//undefined
    console.log(fn);//函数体本身
    var num =12;
    function fn (){}
    console.log(num);//12
    console.log(fn);//函数体本身
    }
    

    老版本浏览器不是这样处理的:不管条件是否成立,都要进行变量提升(和新版不一样的地方,新版本function只是声明,老版本function依然是声明+定义)

    //=>变量提升:没有
    f = function () {return true;};
    g = function () {return false;};
    ~function () {
        //=>[私有作用域]
        //变量提升:g=undefined (不管条件是否成立都要进行变量提升,但是新版本的浏览器只对函数进行声明)
        if (g() && [] == ![]) {//=>Uncaught TypeError: g is not a function
            f = function () {return false;};
            function g() {return true;}
        }
    }();
    console.log(f());
    console.log(g());
    

    不管条件是否成立,判断体当中出现的var和function都会进行变量提升,但是新版本浏览器当中,function声明的变量只能提前声明不能定义了(函数是在判断体当中)
    代码执行到判断地方
    1)条件不成立;进入不到判断体当中赋值的代码执行不了,此事之前声明的变量或者函数依然是undefined。
    2)条件成立:进入条件判断体中的第一件事情不是代码执行,而是把之前变量提升没有定义的函数首先定义了(进入到判断体中函数就定义了:迎合ES6中的块级作用域)
    老版本浏览器不是这样处理的:不管条件是否成立,都要进行变量提升(和新版不一样的地方,新版本function只是声明,老版本function依然是声明+定义)

    关于重名的处理

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

    function fn() {
        var i = 1;
        return function (n) {
            console.log(n + i++);
        }
    }
    var f = fn();
    f(10);//->11
    fn()(10);//->11
    f(20);//->22
    fn()(20);//->21
    

    相关文章

      网友评论

          本文标题:变量提升

          本文链接:https://www.haomeiwen.com/subject/elcxmctx.html