javascript之作用域与闭包详解

作者: 心远地自偏s | 来源:发表于2017-11-11 17:27 被阅读0次

    什么是作用域

    在js里作用域就是控制计算机内存的使用范围,掌握变量生死大权的一种规则。
    在正式开始前,我们有必要了解一下一些基础,由浅入深,以便更好的掌握知识点。

    • var

    计算机内存? 即 申明一个变量时 这个计算机会在内存中开辟一块空间,供变量存储、访问、修改,变量名就像你的身份证号一样,是唯一标识符。唯一与你不同的是,在同一个作用域里变量名相同的情况下,会后来居上,后面的变量覆盖前面的变量:

    var L= 'pig';
    var L= 'pink';
    console.log(L);   //pink
    

    上面的第一段代码我们可以看到,控制台会输出pink,而不是pig,更不会报错。

    1. 如果申明变量没取名字,会报错吗?
      答案是不会,js其实是编译语言,但是和其他语言不同的是,js不会像java等语言提前编译为中间码,它的编译仅仅发生在运行前的几毫秒,这几毫秒,编译器会做很多事,比如会有提升并自动给你未加var 的变量添加一个var。后面我们会详细讲解提升。
    2. 不会报错,又会自动添加var 为什么我们写代码还要多此一举,手动写上var呢?
      如果不写var 变量会自动成为全局变量。在浏览器环境下,全局变量会成为window对象下面的属性,node环境下,全局变量会成为global下的属性。也就是使用这个全局变量时,默认省略了window,当然,你可以自己加上,区别不大。
    3. 这么说全局变量不应该存在? 有什么坏处?
      应该尽可能避免全局变量,js之父Brendan Eich也曾谈到过这一点。应该尽可能让变量私有化,让外部无法访问,原因是:
      1.多人合作时 全局变量可能会与其他人变量发生冲突 ,当你引用其他框架,插入广告插件等,都可能引起这类事情,特别是开发大型web应用时,代码难以维护。
      2.使用全局变量时 编译引擎会查找所有全局变量 程序性能会大打折扣(没找到该变量会抛出not defined)。 相关资料> 深入理解变量私有化
    • undefined 和not defined有何差别?

    undefined是变量已经被申明,但是未被赋值,这个情况程序不会被终止运行。not defined是未被申明,又未被赋值,浏览器抛出这个错误,程序会停止运行

    var L ;
    console.log(L);  //undefined
    console.log(LL) // not defined
    HH = "haohao"  //  像这样 未申明,但是被赋值,这个变量会直接成为全局变量
    

    但是 如果把未申明,未赋值的变量作为window属性输出,则会成为undefined,而不是not definde因为原本就不会被提升,而window下面又没有这个属性,所以window.LL就会成为undefined。其他自定义对象都会如此。例如可以巧妙的利用这个特征来做ajax的ie浏览器兼容,在ie7以下,ie并不认识XMLHttpRequest,会直接not defined,所以把XMLHttpRequest放在window对象下,即不会报错了:

    if(window.XMLHttpRequest){
            var Ajax=new XMLHttpRequest();
        }
        else{
            var Ajax=new ActiveXObject("Microsoft.XMLHTTP");
        }
    

    js中 var申明的变量作用域是以函数为基准的,一个函数就是一个作用域,例如下面的代码,如果在haohao变量前面加一个var和不加var 有什么区别:

    function fun1(){
       haohao = 'javascript';
    }
    fun1();
    console.log(haohao);  // javascript   haohao未申明但是赋值,已被提升到全局
    
    var LL = "pig";
    function fun1(){
       var haohao = 'javascript';
    function fun2(){
       console.log(haohao) 
      }
    fun2();
    }
    function fun3(){
      var LL = "bigPig";
      console.log(LL) 
    }
    fun1(); //javascript
    fun3(); //bigPig
    console.log(LL)  //pig
    console.log(haohao);  // 报错 not defined 
    
    

    只有这个函数里面才能访问这个函数的变量,在函数这个外部,无法访问.既内部可以访问外部,外部无法访问内部
    全局里面的变量,任何地方都能访问,局部作用域优先于全局作用域,fun1里的变量 fun1和其子作用域fun2可以访问。
    作用域的变量使用完毕并且没有被其他地方引用时,js会自动进行垃圾回收,变量生命周期结束(死)

    作用域图

    在es6语法中, 情况就不同了

    • es6的let和const

    在es6后,javascript申明变量的方式不再只有var ,多了letconst,与var有如下区别:

    1. 已经申明的变量如果再申明会报错(not defined) ,var 则不会
    2. 会产生块级作用域,而不是var 那样的函数作用域
    3. let const申明的变量不会被提升,var申明的变量会被提升
    4. const是常量,定义后,值不可被修改,但是和其他语言不同的是,js常量如果存储的是对象,对象下的属性和方法依旧可以修改。

    到底什么是作用域

    • 函数作用域
    • 提升

    实例1,我们先自己思考一下,下面这段代码运行结果:

    <script>
        var animal= 'dog';
            test();
        function test(){
            alert(animal);
            var animal= 'pig';
            alert(animal);
      }     
    </script>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    你思考好了吗
    结果是什么,拖到浏览器去试试就知道了,这段代码答案大家都可以知道,但是遇到同类代码,我们又ctrl c一遍?所以学习,掌握基本原理才是根本。
    这是网易曾经的一道前端工程师面试题,(方便记忆,这里只是改了变量名),有多少工作多年的人前端开发者都还迷惑不清,其实原理并不复杂,而且比较简单,现在我们一起来揭开 js稀奇古怪的面纱,看看有多难。
    按照常规思维 test函数被调用 首先是 alert(animal);而前面有个ar animal= 'dog'; 所以认为第一次弹出 ‘dog’,接着函数里面的代码是var animal= 'pig';alert(animal);所以自然就继续弹一个‘pig’出来,所以给出的答案是 先弹出 dog 然后pig。
    还有答案是认为test在function test(){}函数之前调用了 ,所以会报错。
    事实上答案到底是哪个?没错 ,前面两个答案都错了 正确答案是undefined,pig。
    怎么会如此诡异? 这段代码究竟经历了啥?dog去哪里了?dog去哪里了?dog去哪里了?被吃了 ,成undefined了???

    我们来回放事发现场:

    编译器 : 我刚刚在代码里发现一个叫animal的变量是全局的,它装着dog,我把它处理了变成这个样子: var animal;animal = dog;
    报告!还发现一个test函数,函数可是一等公民啊!! 我把它调到前面去!
    ;我又在test函数里发现 var animal= 'pig'; 我先把把它提升了 var animal;animal=pig;
    所以我给引擎大人的结果如下:

    function test(){
        var animal;
        alert(animal);
        animal= 'pig';
        alert(animal);
      }     
            var animal;
            animal =  'dog';
            test();
        
    

    编译器 :呜呜 累死我了

    作用域: 辛苦你啦! 你刚刚发现两个同名的变量吗?

    编译器: 对呀!他俩是亲兄弟么?

    作用域: NONONO,他俩不是一个世界的动物,一个在test函数;里,一个是全局的

    编译器:我知道呀,不去调解一下吗?姓名一样住所一样,打起来咋办

    作用域: 不在一个世界 !他俩是完全不同的! 只不过都叫animal而已!互不干扰,我们不管它们。它们没法打起来的。

    引擎: 你们商量好了没? 我执行咯。

    编译器给的结果恍然大悟没?dog?根本没有dog的事。没有引用全局变量那个animal。

    var animal= 'pig';这个简单的赋值操作,实际上编译器帮我们干了两件事,变量被申明和被赋值。
    var animal;并且被提升到所在作用域的顶部
    然后在原来“=”所在的位置赋值。同样的,函数也会被提升,函数是js一等公民,提升优先级比变量还高,所以代码里函数还没被申明就调用,并不会报错。可以参考前面编译器给引擎的报告结果。

    实例2,思考如下代码,控制台会输出unfined还是报错?

    function tiger(){
      LL =20; 
    }
    console.log(LL);
    

    这里 LL变量作用域本应该为全局的但是tiger函数并未被使用,所以被当做垃圾回收了,console.log(LL)找遍所有地方都找不到,无奈只好报错。
    现在我们作如下改进:

    tiger();
    function tiger(){
      LL =20; 
    }
    console.log(LL);
    

    这时候,tiger被调用了,LL由于没有申明就赋值了,所以被提升为全局变量,首先它的值被变为unfined,然后执行到tiger函数后,LL的值被赋为20;tiger随便在哪里被调用都行,因为 这个函数总是会被提升到作用域顶端,而这个函数作用域在全局里,所以就在代码顶端:

    function tiger(){
     LL =20; 
    }
    tiger();
    console.log(LL);
    

    如果tiger()调用在console.log(LL)之后:

    function tiger(){
     LL =20; 
    }
    console.log(LL);
    tiger();
    

    同样会报错 not defined,打印到控制台,会找不到这个变量,这个变量在打印函数执行后才出现.。

    • 词法作用域

    词法作用域,即是静态作用域
    实例3:

    function cat(){
        var L ='pig';
      console.log(L);
    }
    function play(){
      var L = 'ZZ';
      cat();
    }
    play();
    console.log(L);
    

    词法作用域是静态的,是在函数或者变量词法化(原本的位置,而不是调用的位置)时产生的,如上代码,play()后,play函数执行,执行时,var L被赋值'ZZ',然后执行cat(),这时cat函数被调用, cat作用域里面的 var L 被赋值'pig',然后输出 pig,而不是输出‘ZZ’ 。因为作用域不会因为函数调用而改变(eval,with除外),最后由于两个L都分别是两个函数的作用域里的私有变量,没有任何全局的L,所以代码最后一行的console.log(L);会报错。


    • 块级作用域
    • 闭包 和 闭包在循环里的应用

    Loading 持续更新....

    相关文章

      网友评论

        本文标题:javascript之作用域与闭包详解

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