• 一步一步的理解闭包


         一步步的理解闭包:

    1. javascript是函数作用域,按常理来说,一个函数就不能访问另一个函数中的变量。
    2. 而我们实际操作有时候需要用一个函数去操作另一个函数中的变量。
    3. 为了能够访问另一个函数作用域中的变量,javascript提供一种内部机制,给嵌套在其它函数中的函数提供了一种父作用域链保存机制。
    4. 当内部函数创建时,静态的保存了父作用域链,即使父函数执行完毕,其变量对象(这里面保存着我们想要的父变量)一直保存在内部函数的作用域链中。内部函数不再被调用之前,这个活动对象一直在内存中(通俗讲就是这时候父函数的变量对象和内部函数绑定在一起了,同生共死)。这种机制取名为闭包。
    5. 简洁地说:闭包就是有权访问其他函数作用域变量的函数。

    Figure 6. An execution context structure.

    下文的叙述以此图为标准,若有疑问请参照

         下面我们来一些经典实例来进一步帮你解除闭包的困扰(js中最难的一个问题之一),嵌套函数由于其天然的嵌套形式是闭包最常见的一种表现方式。由嵌套函数开始吧 

    1  function sayHello2(name){
    2      var text='hello'+name;
    3      var sayAlert=function(){alert(text);}
    4      return sayAlert;
    5  }
    6  var say2=sayHello2('jack');
    7  say2();//hello jack

         上面的代码就是一个嵌套函数,也是我们见过的最常见闭包表现形式,因为匿名函数function(){alert(text);}可以访问到外部函数sayHello2中的text变量,这就形成了闭包。闭包虽然存在了,但它也不能徒有其表,还的做点事情。代码第6行的赋值表达式后,外部函数sayHello2就被销毁了,按常理其内部变量text也随之销毁。可第7行成功的调用说明,外部函数的变量对象始终保留在内存中,直到内部函数销毁,不再被调用为止(总的有个头,不然内存扛不住)。

    再看下面的例子

    1 function say667(){
    3     var num=666;
    4     var sayAlert=function(){alert(num);}
    5     num++;
    6     return sayAlert;
    7 }
    8 var sayNum=say667();
    9 sayNum();//667

         为什么最后是667,不是你期望的666?(有的地方是这样解释的,说是对父变量的引用,而不是复制。笔者认为基本类型不存在引用(地址)这一说吧)。保存着外部函数的活动对象,活动对象就是一个盒子。里面的变量,父函数可以随意改变。

         这个变量对象本来就是父函数的一部分,所以父函数能操作它是理所当然。而这个父变量对象也静态的保存在内部函数的作用域链中,那内部函数应该也有权利去操作它。

     1 function baz(){
     2     var x=1;
     3     return {
     4         foo:function foo(){return ++x;},
     5         bar:function bar(){return --x;}
     6             };
     7 }
     8 var closures=baz();
     9 alert(closures.foo(),//2
    10     closures.bar()//1)

         你会发现内部的foo函数改变了父变量,与此同时也影响到了bar函数的结果。这是为什么?这是因为内部的两个函数在创建的时候都保存的是同一个父作用域—baz的作用域。这样一来父变量对象就是共享的了,所以里面的变量相互影响。(作用域包含变量对象,变量对象包含变量)

         再来看我们遇到过的一个循环问题了,同样是父函数一某种方式改变了这个变量对象中的变量值。使得内部函数最后访问的值变成了循环最终值。

     1 function list(){
     2     var result=[];
     3     for(var i=0;i<3;i++){
     4         result[i]=function(){
     5             alert(i);
     6         }
     7     }
     8 }
     9 result[0];//3
    10 result[1];//3
    11 result[2];//3
    闭包的用途

    下面看看闭包的用途吧,闭包可以用在许多地方。

    1. 基本功能就是前面提到的可以读取函数外部的变量,就不在赘述了。
    2. 另一个就是让外部函数变量的值始终保持在内存中(直到不存在这些变量的引用)。

         把你期望的值保存在一个地方,这与缓存的思想不谋而合。例如我们需要处理一个过程很耗时的函数对象,每次调用都会花费很长时间,那么我们需要把计算的值保存起来。调用的时候首先在缓存中查找,如果找不到则进行计算,然后更新缓存的值。如果找到了,直接返回找到的值即可。闭包正是可以做到这一点.

     1 var cacheSearchBox=(function(){
     2     var cache={}; 
    4
    return { 5 attachSearchBox:function(boxid){ 6 if(boxid in cache){ 7 return cache[boxid]; 8 } 9 var fsb=new searchBox(boxid); 10 cache[boxid]=fsb; 11 return fsb 12 } 13 } 14 })(); 15 cacheSearchBox.attachSearchBox("input1");

         这样,当我们第二次调用cacheSearchBox.attachSearchBox("input1")时,我们可以从缓存中获取该对象,而不用再去创建一个新的searchbox对象。

         3 面向对象中实现特权方法

         在下面的例子中,在person之外的地方无法访问其内部变量。只有通过闭包构造的特权方法的形式来访问。同时实现了私有变量和访问私有变量的特权方法的封装:

     1 var person=function(){
     2     var name="deng";
     3     return{
     4         getName:function(){
     5             return name;
     6         },
     7         setName:function(newName){
     8             name=newName;
     9         }
    10     }
    11 }();
    12 alert(person.name);//undefined
    13 alert(person.getName());//deng
    14 person.setName("jack");
    15 alert(person.getName());//jack
    使用闭包的注意点

    1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    2)闭包会在父函数外部,改变父函数内部变量的值。实现面向对象封装中特权方法时,慎重修改私有变量。

    文章在上纲上线的同时,加入了很多自己的理解。也许存在纰漏,望探讨。

  • 相关阅读:
    函数功能
    Vue + Element UI 实现权限管理系统 前端篇(十六):系统备份还原
    Vue + Element UI 实现权限管理系统 前端篇(四):优化登录流程
    Vue + Element UI 实现权限管理系统 前端篇(五):国际化实现
    Vue + Element UI 实现权限管理系统 前端篇(一):搭建开发环境
    Vue + Element UI 实现权限管理系统 前端篇(三):工具模块封装
    Vue + Element UI 实现权限管理系统 前端篇(二):Vue + Element 案例
    小程序 web-view
    ffmpeg 给图片添加水印
    给右键菜单添加 cmd
  • 原文地址:https://www.cnblogs.com/mingwaer/p/3726452.html
Copyright © 2020-2023  润新知