zoukankan      html  css  js  c++  java
  • JavaScript专题(二)闭包

    前言 - ES6 之前,JS没有块级作用域,只有全局作用域和函数作用域

    用了许久ES6,春招在即,重写下博文。
    还是讲讲闭包。我们要知其然,知其所以然。

    仿佛大众情人一般,很多前端面试官都会问一问,说来复杂,说来也简单。就是这种既可以复杂又可以简单的东西往往让面试官能收获很多(对被面试者也是如此)。

    闭包所谓的专业定义:闭包是指有权访问另一个函数作用域中的变量的函数。

    其实这是一句后话,对于入门者来说毫无作用。一个不会喝酒的人,突然喝了一杯高浓度的酒,它只会醉倒,而不会体味到其中酒的美好滋味。只有阅尽千帆,才可能化繁为简。只有不断打磨自己的语言,最后产出的才能是最简练的东西。所谓知识储备。


    一. 闭包是什么?它来自哪里,又是什么模样

    专业定义其实是真正理解后再来看的。大家都肌肉记忆了,自然觉得简单。
    我们只需要明白我们要关注的:作用域。函数。变量。

    所以我们直接来看几个闭包的例子吧。

    • 例子A.局部变量

      function out() {
        var a = 3;
      }
      out();
      console.log(a); // error,报错
      

      a是局部变量。我们从 全局获取 a,并且得到了失败的结果。

    • 例子B.函数的调用 - 这就是闭包!!!

      function out() {
        var a = 3;
        function closure() {
          console.log(a);
        }
        closure();
      }
      out(); // 3
      

      a还是局部变量,被函数内部的 closure 调用了。out 内部 先声明、后调用 了 closure。
      这一次,我们从 全局(其实依靠了外物out), whatever 获取 a -> 成功了。

      其实闭包从例子B就结束了。访问另一个函数作用域中的变量的函数即是闭包。然而人们总是要探寻其中的原理,所以才有了大量的后文。

    • 例子C.经典例子 -- 常用于给list数组里面的每个item挨个绑定函数

      var oBtn = [];
      for (var i = 0; i < 6; i++) {
        oBtn[i] = function () {
          console.log(i);
        }
        // oBtn[i]();
      }
      oBtn[0](); // 6
      for (k = 0; k < 6; k++) {
        oBtn[k]();              // 6个 6
      }
      
    例子C的出现是为什么呢?我们先来看正确解答。

    二. 闭包引起的问题的逐句分析

    参考至 https://www.cnblogs.com/zhuzhenwei918/p/6131345.html

    1.var oBtn = []; // 定义一个数组(在我们平常使用中通常获取的是html节点数组)
    2.for (var i = 0; i < 6; i++) // 用 var 声明,所以 i 是全局变量,不在局部作用域中
    for (var i = 0; i < 6; i++) {}
    console.log(i); // 6   所谓的 跳出三界外,不在五行中!!!全局可以访问到 i.
    
    3.oBtn[i] = function() { console.log(i); } 这里进行了一个 变量赋值 操作。

    注意,只是赋值。没有进入 执行环境。所以,这里的 i 其实是还没有被确定的。由于for循环不具有块级作用域,所以这里的函数定义就是全局作用域。

    4.var i = 1; // 到了第二次循环,这时候的 var i=1 覆盖了 第一次的 var i=0

    此后每次循环不断覆盖。当我们最后在全局真正的 调用的时候:

    5.oBtn[0](); // 6

    三. 闭包引起问题的解决。(精细的原理还请仔细观看下方)

    • 办法一.现代方法 四两拨千斤。var 改 let。

      这就是现代的 四两拨千金。其中的关键在于 i 不在三界之内、五行之中,而在 全局里。

      var oBtn = [];
      for (let i = 0; i < 6; i++) {
        oBtn[i] = function () {
          console.log(i);
        }
      }
      oBtn[0](); // 0
      

      let的效果 - 我们只要让 i 拥有自己的作用域即可。ES6中,使用let之后,能够让定义的变量在 {} 之内拥有其块级作用域。

      {
        var a = 10;
        let b = 10;
      }
      console.log(a); // 10;
      console.log(b); // error
      

      既然知道了现代的解决办法,也让我们回顾一下以往的解决办法

    • 办法二.用匿名函数来形成自己的局部作用域(目的其实还是一样的,将i变为局部的变量)

      var oBtn = [];
      for (var i = 0; i < 6; i++) {
        (function(index) {
          oBtn[index] = function () {
            console.log(index);
          }
        })(i);
      }
      oBtn[0](); // 0
      oBtn[5](); // 5
      
    • 办法三.通过新建一个变量保存当前状态的 i

      (类似与我们的拍照,随着时间的变迁,我们慢慢长大,但我却可以用相片记录下曾经的那个我)

      var oBtn = [];
      for (var i = 0; i < 6; i++) {
        oBtn[i] = {};
        oBtn[i].index = i;
        oBtn[i].func = function() {
          console.log(this.index);
        }
      }
      oBtn[3].func();
      

    代码变多了是不是?

    至此、闭包带来的问题解决了。出问题的原因的关键原因其实还是 作用域的锅。另外,闭包本身不是问题,是特性。然使用不当则出现问题,从而被众人关注。


    四. 让我们回到对闭包的剖析上来。

    A.为什么全局里获取不到函数的局部变量呢?

    JS采用一种垃圾清除的机制,分别用 引用计数 和 标记清除。

    • 1.引用计数的原则是当使用了变量,则给变量记为 1。当大家都不使用了,就像那个被忘掉的人一样,就被清除了。
    • 2.标记清除则是 当变量进入环境则全部变量标记一遍;然后去掉环境中要使用的变量的标记;最后,垃圾收集器完成内存清除工作,销毁那些还带有标记的(即是没被环境中使用的)。

    闭包实现了一种特殊的情况。闭包中的变量,这个函数的空间将会一直在内存中保留。

    function test() {
      var a = 3;
      return function() {
        return a;
      }
    }
    b = test();
    

    虽然在外部没法输出a,这是因为没法访问,但a还是存在于内存之中。因为内部的函数引用了外部的变量a(引用计数法垃圾清除,为0则删除),所以a还被人惦记着,自然也不会消亡(只要b还在,js还在运行)

    局部变量作为函数环境内的变量,当函数运行结束,它就被销毁了,从而在全局中是找不到它的。而闭包通过对其引用,让其不被消亡,从而使其能够在全局中生存。

    B.函数是怎么查找变量的呢?为什么 内部函数 找到了 外部函数的 变量?并且将其带回并保留在了 全局 的世界里。

    • 函数中识别变量,是一层层向外找的。
    • 首先在函数内部找,看看是不是内部声明的,然后再到上一层找,没找到,再往上,直到全局作用域。
    • 如果全局页面都没声明,那浏览器就报错了。

      这一层层中的层是什么东西呢,就是函数,因为函数提供最小的作用域.

    内部函数发现自身没有找到那个变量。于是往外找,找到了外部函数的变量。将其返回则实现了 内部函数对外部函数的变量的引用,也就是闭包本身的定义。

    五. 它能够用来做什么呢?又带了哪些后果呢?

    万物都有优点和缺点。因为我们是人,纠结又迷茫,自卑而不敢确信。不唯一的想法带来的后果就是一切皆有可能,一切皆有两面性。
    所有的结果其实都是因为人意识的存在。


    闭包的优点

    • A. 全局变量可能会造成命名冲突。使用闭包则不用担心这个问题。它是私有化的,加强了封装性。
    • B. 变相地实现了数据的私有。跟C++的私有变量相似。
    • C. 缓存。
    • D. 减少了函数的 参数量(实际现象)。

    每个模块相互调用。当程序越来越复杂,全局变量可能带来不可预测的危险。
    闭包让局部变量发挥出了全局变量的作用,降低了风险。


    闭包的缺点

    • A. 内存消耗。由于闭包携带了包含它函数的作用域,所以比其他的函数占用内存占用的更多。

    参考+非常好的一篇文章:http://www.360doc.com/content/15/1008/17/19812575_504201072.shtml


    complete.

  • 相关阅读:
    BZOJ 1191 HNOI2006 超级英雄hero
    BZOJ 2442 Usaco2011 Open 修建草坪
    BZOJ 1812 IOI 2005 riv
    OJ 1159 holiday
    BZOJ 1491 NOI 2007 社交网络
    NOIP2014 D1 T3
    BZOJ 2423 HAOI 2010 最长公共子序列
    LCA模板
    NOIP 2015 D1T2信息传递
    数据结构
  • 原文地址:https://www.cnblogs.com/can-i-do/p/6675686.html
Copyright © 2011-2022 走看看