大牛的讲解,点击

  我们首先需要有作用域的概念,点击

  

那么什么是闭包?

官方的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

广义上的闭包就是指一个变量在它自身作用域的被使用了,就叫发生了闭包。粗鲁地理解:闭包就是能够读取其它函数内部变量的函数。 在js中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单粗暴地理解成“定义在一个函数内部的函数”,即一个函数嵌套了另一个函数。

闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:

作用域链,垃圾(内存)回收机制,函数嵌套......

  由于作用域的关系,我们在函数外部是无法直接访问到函数内部的变量的,但是函数内部可以把这个变量传给全局变量或者返回出来,这样外部作用域就可以访问函数内部作用域的变量了;

  简单的说,闭包就是有权限访问另一个函数内部作用域的变量的函数;

  1. javascript具有自动垃圾回收机制,函数运行完之后,其内部的变量和数据会被销毁;
  2. 但是闭包就是在外部可以访问此函数内部作用域的变量,所以闭包的一个特点就是
  3. script type="text/javascript">
  4. function outer(){
  5. var a = 1;
  6. function inner(){
  7. return a++; }
  8. return inner;
  9. }
  10. var abc = outer(); //outer()只要执行过,就有了引用函数内部变量的可能,然后就会被保存在内存中; //outer()如果没有执行过,由于作用域的关系,看不到内部作用域,更不会被保存在内存中了; console.log(abc());//1 console.log(abc());//2 //因为a已经在内存中了,所以再次执行abc()的时候,是在第一次的基础上累加的 var def = outer(); console.log(def());//1 console.log(def());//2 //再次把outer()函数赋给一个新的变量def,相当于绑定了一个新的outer实例; //console.log(a);//ReferenceError: a is not defined //console.log(inner);//ReferenceError: a is not defined //由于作用域的关系我们在外部还是无法直接访问内部作用域的变量名和函数名 abc = null; //由于闭包占用内存空间,所以要谨慎使用闭包。尽量在使用完闭包后,及时解除引用,释放内存;
  11. </script>

      立即执行函数能配合闭包保存状态。

    <script type="text/javascript">
    for(var i = 0; i < 3; i++){
    setTimeout(function(){
    console.log(i); //3 3 3
    //在执行到这一行时,发现匿名函数里没有i,然后向往外部作用域找,然后找到的其实是for循环执行完了的i,也就是2++,3
    },0);
    }; for(var i = 0; i < 3; i++){
    setTimeout((function(x){
    console.log(x); //0 1 2
    })(i),0);
    //在立即执行函数内部i传给了x,并且锁在内存中,所以不会变
    };
    </script>

    一道经典面试题:

      下面的ul中,如何点击每一个 li 的时候弹出其下标?

    <ul>
    <li>index 00000</li>
    <li>index 11111</li>
    <li>index 22222</li>
    </ul>

      方法一:用闭包

    <script type="text/javascript">
    window.onload = function(){
    var oLi = document.getElementsByTagName('ul')[0].children;
    for (var i = 0; i < oLi.length; i++){
    (function(index){
    oLi[index].onclick = function(){
    console.log(index);
    };
    })(i);
    }
    }
    </script>

      方法二:闭包还有一种写法

    <script type="text/javascript">
    window.onload = function(){
    var oLi = document.getElementsByTagName('ul')[0].children;
    for (var i = 0; i < oLi.length; i++){
    oLi[i].onclick = (function(index){
    return function(){
    console.log(index);
    }
    })(i);
    }
    }
    </script>

      方法三:将下标作为对象的一个属性,添加到每个数组元素中,(name: " i ", value: i 的值);

    <script type="text/javascript">
    window.onload = function(){
    var oLi = document.getElementsByTagName('ul')[0].children;
    for (var i = 0; i < oLi.length; i++){
    oLi[i].i = i;
    oLi[i].onclick = function(){
    console.log(this.i);
    };
    }
    }
    </script>
     
     

    变量的作用域 

    Js中变量的作用域分两种:全局变量和局部变量。

    函数内部可以直接读取全局变量。  

    1 var n = 'RORO彦';
    2 function f1() {
    3 console.log(n);
    4 }
    5 f1();

    在函数外部自然无法读取函数内的局部变量。

    1   function f1(){
    2 var n= 'RORO彦';
    3 }
    4 console.log(n); // error

      

    此外,函数内部声明变量时,一定要用var关键字来命名变量。否则,就声明了一个全局变量。

    当我们需要从外部读取局部变量,得到函数内的局部变量,可以在函数的内部再定义一个函数。  

    1   function f1(){
    2 n = 'RORO彦';
    3 function f2(){
    4 console.log(n);
    5 }
    6 }

    如上,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2是可见的。反之,不行。这就是Javascript语言特有的“链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。

    现在f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们就可以在f1外部读取它的内部变量

    1 function f1(){
    2 n = 'RORO彦';
    3 function f2(){
    4 console.log(n);
    5 }
    6 return f2;
    7 }
    8 var result=f1();
    9 result(); // RORO彦

     作用

    1.读取函数内部的变量

    2.令这些变量的值始终保持在垃圾(内存)回收机制中。

     1  function f1(){
    2 var n = 'RORO彦';
    3 add=function(){n = 'RORO';};
    4 function f2(){
    5 console.log(n);
    6 }
    7 return f2;
    8 }
    9 var result=f1();
    10 result(); // RORO彦
    11 add();
    12 result(); // RORO

    在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次是RORO彦,第二次是RORO。函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除,而是保存在内存堆(heap)里, 原因是它被包装或封装在一个函数体内,这些构造器都被称为闭包。它返回调用函数的运行结果,是函数本身。此外,add的值是一个匿名函数,它本身也是一个闭包,所以add相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

    注意事项 

    函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。闭包会在父函数外部改变父函数内部变量的值。所以,当你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,不要随便改变父函数内部变量的值。

    闭包与抽象数据类型

    我们通过闭包能简单地引入抽象数据类型。

    例如,通过闭包实现一个 堆栈

    function createStack() {
    
      var elements = [];
    
      return {
    
        push: function(el) { elements.unshift(el); },
    
        pop: function() { return elements.shift(); }
    
      };
    
    }
    
    var stack = createStack();
    
    stack.push(3);
    
    stack.push(4);
    
    stack.pop(); //

    闭包与面向对象编程

    在 JavaScript 中,闭包不是堆栈数据类型的最佳实现方式。用原型 Prototype 实现对内存更友好,在当前对象实例找不到相应属性或方法时,会到相应实例共同引用的 Prototype 属性寻找相应属性或方法。如果在当前Prototype属性找不到时,会沿着当前原型链向上查找,而Prototype 上的属性或方法是公用的,而不像实例的属性或方法那样,各自单独创建属性或方法,从而节省更多的内存。

    上述构造器看起来非常像类、对象、实例值和私有/公有方法。闭包与类相似,都会将一些能操作内部数据的函数联系在一起。于是,我们可以像使用对象一样使用闭包。当我们想在 JavaScript 创建“真正的”隐藏域,或者需要创建简单的构造器时,我们可以优先使用闭包。不过对于一般的类来说,闭包可能还是有点太繁重了。

     
     
     
     

    闭包的基本结构

    1. 通过函数: 写一个函数, 函数内定义一个新函数, 返回新函数, 使用新函数得到函数内的数据
    2. 通过对象: 写一个函数, 函数内顶一个对象, 对象中绑定多个方法, 返回对象, 利用对象内的方法访问函数内中的数据

    闭包的基本用法

    1. 带有私有访问数据的对象

      function Person(){
      this.name = 'Peter';
      // setName('')
      }
      // 私有数据指的是只有函数内部可以访问的数据,或者对象内部的方法 // 简单的例子,判断孩子是不是老张家的
      function createPerson(){
      var name = '张全蛋';
      return {
      getName : function(){
      return name;
      },
      setName : function(value){
      // 如果value的第一个字符串是 张 ,就设置
      if(value.charAt(0) === '张'){
      name = value;
      } esle {
      //不姓 张 ,就将错误抛出
      throw new Error('你不姓张,难道还能姓王?');
      }
      }
      }
      }
    2. 带有私有数据的函数

      // 不具有私有数据
      var fn = function(){};
      function fn(){} // 具有私有数据
      var foo = (function(){
      // 私有数据
      return function(){
      // 可以访问私有数据
      }
      })();

    闭包的基本模型

    函数模型

    function foo(){
    // 私有数据
    return function(){
    // 可以访问上面的私有数据
    }
    }

    对象模型

    function foo(){
    // 私有数据
    return {
    method : function(){
    // 可以访问的私有数据
    }
    }
    }

    闭包的性能问题

    • 函数的执行需要内存, 函数中定义的变量会在函数执行后自动回收
    • 因为是闭包结构, 如果还有变量引用这些数据的话, 这些数据不会被回收
    • 因此在使用闭包的时候,如果不使用这些数据了, 需要将函数赋值为 null

      var foo = (function(){
      var num = 123;
      return function(){
      return num;
      }
      })();
      // 如果不需要foo中的数据, 赋值为 null
      foo = null;

闭包(closure)的更多相关文章

  1. JavaScript闭包(Closure)

    JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...

  2. 聊一下JS中的作用域scope和闭包closure

    聊一下JS中的作用域scope和闭包closure scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了.我就被这个概念困扰了很久 ...

  3. python 函数对象(函数式编程 lambda、map、filter、reduce)、闭包(closure)

    1.函数对象 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 秉承着一切皆对象的理念,我们再次回头来看函数(function).函 ...

  4. 深入理解JavaScript闭包(closure)

    最近在网上查阅了不少javascript闭包(closure)相关的资料,写的大多是非常的学术和专业.对于初学者来说别说理解闭包了,就连文字叙述都很难看懂.撰写此文的目的就是用最通俗的文字揭开Java ...

  5. [转] Java内部类之闭包(closure)与回调(callback)

    闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域.通过这个定义,可以看出内部类是面向对象的闭包,因为它 不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥 ...

  6. JavaScript 进阶(四)解密闭包closure

    闭包(closure)是什么东西 我面试前端基本都会问一个问题"请描述一下闭包".相当多的应聘者的反应都是断断续续的词,“子函数”“父函数”“变量”,支支吾吾的说不清楚.我提示说如 ...

  7. [转载]学习Javascript闭包(Closure)

    学习Javascript闭包(Closure)     源地址: http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures ...

  8. Swift语言精要-闭包(Closure)

    闭包(Closure)这个概念如果没学过Swift的人应该也不会陌生. 学过Javascript的朋友应该知道,在Javascript中我们经常会讨论闭包,很多前端工程师的面试题也会问到什么是闭包. ...

  9. 【Python】闭包Closure

    原来这就是闭包啊... 还是上次面试,被问只不知掉js里面的闭包 闭包,没听过啊...什么是闭包 回来查了下,原来这货叫闭包啊...... —————————————————————————————— ...

  10. 闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别

    闭包(Closure)和匿名函数(Anonymous function)/lambda表达式的区别 函数最常见的形式是具名函数(named function): function foo(){ con ...

随机推荐

  1. redis客户端可以连接集群,但JedisCluster连接redis集群一直报Could not get a resource from the pool

    一,问题描述: (如题目)通过jedis连接redis单机成功,使用JedisCluster连接redis集群一直报Could not get a resource from the pool 但是使 ...

  2. RabbitMQ的安装和配置

    在Windows下进行rabbitMQ的安装 第一步:软件安装 如果安装rabbitMQ首先安装基于erlang语言支持的OTP软件,然后在下载rabbitMQ软件进行安装(安装过程都是下一步,在此不 ...

  3. linux新建用户并赋管理员权限

    输入useradd新建一个用户 [root@java-devenv ~]# useradd yaoqi [root@java-devenv ~]# passwd yaoqi passwd 是修改用户密 ...

  4. CountDownLatch 源码解析—— countDown()

    上一篇文章从源码层面说了一下CountDownLatch 中 await() 的原理.这篇文章说一下countDown() . public void countDown() { //CountDow ...

  5. web服务器学习4---httpd-2.4.29优化

    实验环境: 环境:CentOS 7.4 软件版本:httpd-2.4.29 一.网页压缩 1.检查是否安装压缩模块 apachectl -D DUMP_MODULES | grep deflate 如 ...

  6. python全栈开发-Day12 三元表达式、函数递归、匿名函数、内置函数

    一. 三元表达式 一 .三元表达式 仅应用于: 1.条件成立返回,一个值 2.条件不成立返回 ,一个值 def max2(x,y): #普通函数定义 if x > y: return x els ...

  7. 个人总结——Beta阶段

    Beta总结 我们在beta 结束之后, 每位写一个博客, 回顾并总结自己的beta过程,哪些方面做的好的,哪些方面做得不足需要改进的 回答问题 分析在Alpha阶段自己提出的五个问题,针对每个问题, ...

  8. 【iOS】swift-如何理解 if let 与guard?

    著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:黄兢成 链接:http://www.zhihu.com/question/36448325/answer/68614858 ...

  9. 构建微服务开发环境4————安装Docker及下载常用镜像

    [内容指引] 下载Docker: Mac下安装Docker: Windows下安装Docker; 下载常用docker镜像. 一.下载Docker 1.Mac适用Docker下载地址:https:// ...

  10. Python之旅.第三章.函数3.27

    一.形参与实参 1.形参与实参是什么? 形参(形式参数):指的是 在定义函数时,括号内定义的参数,形参其实就变量名 实参(实际参数),指的是 在调用函数时,括号内传入的值,实参其实就变量的值 x,y是 ...