Javascript 闭包

如果要了解闭包,我们需要先了解闭包的由来,闭包的产生,源于JS的词法作用域

词法作用域

作用域是指一个 变量能够访问到的区域 例如我们设置一个var n=0;实际分两步,声明和赋值,var n;n=1;在js中只有函数能够限定作用域,除此之外声明的都是全局作用域,在ES6的新标准中,采用了let和const来限定局部变量,注意局部变量的优先级是高于全局变量的

执行环境是JavaScript中最为重要的一个概念,在js代码开始执行的时候,会创建一个匿名函数的执行环境,执行环境定义了变量或函数有权访问的其它数据,每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中,虽然我们编写的代码无法访问这个对象,但解析器咋及处理数据的时候会在后台使用它

变量的作用域是在定义的时候决定的而不是在执行的时候决定的,变量只能在本层作用域和上层作用域生效,不能在下层作用域生效

举一个栗子来加深理解

    var num;
    function f1(){
        num=3;
    }
    f1();
    console.log(num);//3

在这个栗子中,num的值在全局最开始设置的是undefined,但是在函数f1中改变了该值,这个值变为3,在函数f1执行了一次之后我们再打印num的值,得到的是num=3,通过解读JavaScript是词法作用域我们可以知道,num这个变量的作用域是在它定义的时候决定了它是一个全局变量,所以即使在函数中执行num的操作,我们在函数外部依然可以获取到这个值

注意一个细节,获取全局变量比获取局变量要耗费性能,举个例子:

    function (obj){
        var i=0,
            l=obj.length;
        for(;i<l;i++){}
    }

在这段代码里,我们获取设置局部变量obj.length用来进行循环条件判断,使用i<l是一个比i<obj.length更佳的选择,因为如果obj包含大量的代码,我们在循环时每一次都要重新获取obj的值,而设置l则可以降低这部分的性能损耗

闭包

在JS中只有function能形成块级作用域,在函数内部的变量外部是无法获取的,而在函数中嵌套函数,被嵌套的函数则可以拿到外层函数的变量的值,所以有的时候我们需要拿到一个函数的内部数据时,可以采用如下的方法:

    function A(){
        var name="tom"

        function B(){

            return name
        }

        return B();

    }

    console.log(A());//tom

以上的代码就是我们常说的闭包,官方的闭包的解释十分的概念化,我对闭包的理解就是:

  1. 初步的理解:能够获取其他函数的内部数据的函数
  2. 深入的理解:函数记住并访问其所在的词法作用域,叫作闭包现象,而此时函数对作用域的引用叫作闭包(闭包就是引用,维基上有一段对闭包的引用解释的我觉的是比较好:引用了自由变量的函数,自由变量和函数将一同存在),如果想要理解这一块,我们需要对JavaScript中的垃圾回收机制JavaScript的存储机制做一些了解
JavaScript的垃圾回收机制

垃圾回收的主要工作是跟踪内存的分配和使用,在内存不再工作时,将其进行释放

在垃圾回收机制中,涉及到的算法比较多,所以也不打算深入,只做简单的了解即可,我们只需要知道:

在js中,在创建变量或函数时,会开辟空间,有一个属性来标注它们是否被其它变量,被引用则计数器++,

  1. 如果函数被调用过了,并且以后不会再用到,此时判断计数器为0,那么垃圾回收机制就会将其作用域进行销毁
  2. 全局变量是不会被销毁的
JavaScript的存储机制

我们都知道,在JS的存储中是分为堆存储和栈存储的,堆存储是用来存储对象,也就是引用数据类型,而栈存储是用来存储简单数据类型,在这里我们需要了解的是:

  1. 对象的引用是通过地址来传递的,一个对象应用另一个对象后只是它的地址指向发生了改变,而它原来的值是依然存在的
  2. 简单数据类型的引用是直接在栈中把原先数据进行替换的

了解了以上两个概念我们可以再回头看一下关于深入的理解闭包,如果一个函数被引用,那么它的作用域就不会被垃圾回收机制进行销毁,这就可以理解为记住了这个作用域,如果对其内部的变量进行访问,那么称为访问

<script type="text/javascript" language="javascript">
    function a(){
        var i=0;
        return function b() {
            alert(++i);//记住并访问
        }
    }
    c=a();//引用
    c();
</script>

由于比包内的函数对外部函数的变量进行了引用,在垃圾回收机制看来,引用计数不为0,所以在函数的作用域被销毁后,闭包内的变量会一直存在,这也是闭包的缺点,大量的闭包引用如果在引用后没有赋值为null会占用大量的内存空间,在IE低版本中会导致内存泄漏

闭包还有一个特性是闭包只能气的包含函数中任何变量的最后一个值,例如一些常见的面试题中经常出现这个问题

    function person(){

        var arr=[];

        for(var i=0;i<10;i++){
           arr[i]=function (){
                return i;
            };
        };
        return arr;
    }
    
    console.log(person()[1]());//10

在上面的代码中arr的每个位置存储的都是10,如果我们想要使每个返回不同的数字那么我们需要对代码进行改进

    function person(){

        var arr=[];

        for(var i=0;i<10;i++){
           arr[i]=function (num){
                return function(){//再次添加一个闭包,在这个闭包内将每次i的值进行存储
                    return num;
                };
            }(i)
        };
        return arr;
    }
    console.log(person()[1]());//10

闭包的用途

  1. 获取其它函数内部的变量的值

  2. 缓存变量的值

        function A(){
            var i=1;
            return function(){
                i++;
                console.log(i);
            }
        }
        var a=A();
        a();
        a();
        a();
      //这里我们要探讨一个问题,闭包缓存数据的形式是什么,之前一直对闭包的概念有误解,一直以为A事必报,但是实际上
      // return function(){
      //          i++;
      //          console.log(i);
      //     }
      //这部分才是真正的闭包,所以能够进行缓存的是这一部分,这也就解决了之前的一个疑惑,为什么需要写
      // var a=A();这一步再调用a();才能看到闭包缓存的数据
      //我们可以根据这个特性座椅计时器,来记录一个构造函数创建了多少个对象
       var C=function(){
    
            var i=0;
    
            return function(){
    
                return ++i
            }
        }();//让函数自调用自身
    
        function fn(name){
    
            if(this==window){//判断当前调用的是不是window对象
                return 
            }
    
            this.name=name;
    
            fn.cn=C();//注意,在这里实际上已经相当于直接使用++i为fn.cn赋值了
        }
    
        var a=new fn("tom");
        var b=new fn("son");
        var c=new fn("xm");
    
        console.log(fn.cn)//3
    
  3. 设置变量的可读可写(封装)

        function A(_name){
            var name=_name;
    
            return {
                getName:function(){//可读
                    return name;
                },
                setName:function(newName){//可写
                    name=newName; 
                }   
    
            }
        }
    
        var a=new A("tom");
        console.log(a.getName())//tom
        a.setName("xm");
        console.log(a.getName())//xm
      
      //还有一个写法
      
        function A(_name,_age){
            var name=_name,
                age=_age;
    
            return {
                name:function(value){
                    if(value===undefined) return name;
                    else name=value;
                },
                age:function(value){
                    if(value===undefined) return age;
                    else age=value;
                }
            } 
        }
        
        var a=new A("tom",18);
        console.log(a.name());//tom 
        a.name("xm");
        console.log(a.name());//xm
    
  4. 沙箱模式

    (function(){})();//防止代码污染
    //这里我们需要提到一个概念,也就是JS中的依赖注入,和AngularJS中的类似的概念,JS中的依赖注入也就是表示,在JS中如果需要调用某种我们已经封装好的,或者外部的JS库,那么我们可以提前声明
    1.在JS中,如果让JS自己解析,寻找我们定义的变量,这个过程的效率很低,无形中会损耗一部分性能,如果我们以实参的方式传入,那么会减少这部分额性能损耗
    (function(window){
    
      })(window)
    2.提前声明我们使用了什么库,便于代码的维护和可读性,保障代码的健壮性
    (function($){
    
      })($)
    
  5. 提前返回

      var addEventlistner=function (){
       if(window.addEventlistner){
    
        return function(elem,type,calback,bool){
    
          elem.addEventlistner("type",calback,bool);
        };
      }else{
          return function(elem,type,calback){
    
            elem.attachEvent("on"+type, calback)
        };
      }
    }();
     //在之前我们进行能力检测的时候,每次调用都会进行if..else..的判断,其实在我们进入浏览器的时候,浏览器的能力已经确定了,所以我们可以通过利用闭包的提前返回的特性,把能力检测的结果保存下来,不需要每次都进行判断
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容