• JavaScript闭包


    写一下闭包到底是个什么东西,看了无数的博客,无数的例子,基本上还没有看到用简单的方式来说明闭包,我试着写写,没有新的东西,基本都是前辈资料的总结。抛个砖先。热烈欢迎大神们斧正。


    按照惯例,先看下官方是如何定义的

    Closures are functions that refer to independent (free) variables.
    In other words, the function defined in the closure 'remembers' the environment in which it was created in.

    • 第一句:闭包是函数,什么样的函数?涉及到独立变量的函数。什么叫“独立变量”?我也不明白,一会边写边说。
    • 第二句:换言之,这个函数是在闭包的‘记忆’环境定义。“记忆环境”是什么东东?还是不明白。

    好吧,以我苦逼的英语水平,就只能这样理解了。但这说了什么?我不明白,一点都不明白。
    链接在这里,英语好的少年可以去看看。


    再看另一个定义

    A "closure" is an expression (typically a function) that can have free variables together with an environment
    that binds those variables (that "closes" the expression)

    http://jibbering.com/faq/notes/closures/

    闭包是一个表达式(通常是一个函数),什么样的表达式?拥用自由变量和一个绑定这些变量的环境的表达式。
    这个定义相对要明白一些,但还是不太具体。


    再看看wiki

    In programming languages, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.A closure—unlike a plain function pointer-allows a function to access those non-local variables even when invoked outside its immediate lexical scope.

    http://en.wikipedia.org/wiki/Closure_(computer_programming)

    好吧,又是乱七八糟的一大堆,但是最后一句很关键:闭包不像一般函数,它允许一个函数访问这些变量,即使这些引用超出了变量的作用哉。

    诸位不要觉得看定义是很烦人,是没有必要的。在我看来这是非常非常重要的东西。因为大多知识点,在定义上面都写得很清楚。不过closures算一个例外吧,因为它本来就很难说清楚......
    既然国外文献定义得不太明白,就看看国内的先辈们有没有好的定义,摘录如下:

    1. 闭包就是能够读取其他函数内部变量的函数。
    2. 闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。
    3. 闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。
    4. 闭包是定义在另一个函数中的匿名函数。(javascript详解第二版 ——Ellie Quigley Page 124)

    对于第3条,是来自百度百科,看了之后我只能说:壮哉我大百度!还有比这更详尽的定义么?有发现的请发给我!这里对自由变量有个解释:未绑定到特定对象的变量。


    大部份程序员由于逼格原因,是鄙视百度的,动辙google,跳辙wiki。殊不知知识莫问出处,好用就行。

    这里多说两句,除了一些新的知识,国内论坛基本都有非常详尽的资料,如果你没有达到国内的资料已经不有满足你的程度,就不要到处找英文资料,因为母语资料理解起来,肯定比英文快得多。

    嗯,直白点说就是:在程序这一块,英语是用来用的,而不是用来show的。


    所以,我们总结一下什么是闭包:

    • 什么时候需要用到闭包:

      • 需要在一个函数外部,访问函数内部的变量的时候(也就是说在函数运行完之后,你想要把变量保存下来,待需要的时候调用。而不是通过垃圾回收机制消除(garbage collection))。
      • 保护变量安全。一个函数的内部变量,只能内部函数引用。
    • 如何定义闭包:

      • 在一个函数内部,定义一个函数,并返回一个函数的引用。

    如何在外部引用一个函数内部的变量?

    function a(){
     var i = 1;
    }
    alert(i); //undifined
    // 为什么这样?因为一个变量的作用域只在一个函数本身,这是javascript最基本的入门的概念,想来不用多说。
    function a(){
      var i=0;
      function b(){
        alert(++i); 
      }
      return b;
    }
    var c = a();
    c(); //1; 
    

    这个例子来自于百度百科,有三个关键点

    1. b是定义在a的内部
    2. a的运行结果是 return b。此时b就一直保存在内存当中,直到你手动删除为止。
    3. b引用了a的变量(这一点极其重要)。

    所以,c = a();其实调用的就是b,由于b是在a的内部,所以就可以访问i(这是javascript的链接作用域(chain scope),内部函数可以访问外部函数的变量)。
    这样,就达到了在a的外部,调用a中的变量。

    再有,a中定义的函数是b,而不是一个匿名函数,但是这样是可行的。所以,上面第4条的定义就是错的,闭包并不一定是一个匿名函数。

    这一个例子,基本就已经说完了闭包了。但是看的人可能还是没有一个明确的概念,那么就继续看几个例子。

    function a(){
      var i = 1;
      function b(){
        alert(i);
      }
      i++;
      return b;
    }
    var f = a();
    f(); // 2
    

    为什么呢是2呢?
    return b是在a的最后执行,所以,在ruturn b的时候,已经将a中代码全部执行了,所以 i=2;若在i++前返回b。
    f()的结果就是1。这就可以推出:也可以把 var i = 1;写在function b的后面。只要写在return之前即可。

    function a(){
      var i = 1;
      b = function(){alert(i)}
      c = function(){i++}
      d = function(j){i = j}
    }
    a(); //运行一次a,为bcd赋值。
    b(); // 1
    c(); // i++
    b(); // 2
    d(3); // i = 3
    b(); //3
    
    • 此处应有三点需要说明:
    1. 在函数内部定义变量一定要用var,否则定义的就是一个全局变量。在这里b c d皆没有用var,所以就可以在外部直接调用这三个全局变量
    2. a需要运行一次,才会给bcd赋值。否则会报错 b c d未定义
    3. b c d 都在a内,所以都可以调用变量i.

    循环中的闭包

    function closureInLoop(Ar){
      var result = [];
      for(var i=0;i<Ar.length;i++){
        var item = 'item' + Ar[i];
        result.push(function(){alert(item + ' ' + Ar[i])});
    } 
      return result; 
    }
    // 
    function test(){
      var fnList = closureInLoop([1,2,3]);
      alert(fnList);
      for(var j=0;j<fnList.length;j++){
        fnList[j]();
      }
    }
    test(); // item3 undifined(3次);
    

    我们原以为,会依然出现item 1,item 2,item 3。结果却不同,这是什么原因?
    先把fnList输出来看看
    [fnction(){alert(item + ' ' + Ar[i])},function(){alert(item + ' ' + Ar[i])},function(){alert(item + ' ' + Ar[i])},]
    三个一模一样的function,都是调用的Ar[i]。此时i是多少呢?看下前面的closureInLoop的for循环。
    传入的参数是[1,2,3],所以closureInLoop中的Ar.length就是3。所以for执行完的时候:
    i=3;
    item = 'item' + Ar[2];也就是item3;

    由于return result了。所以此时resulte里面的function就形成了闭包。
    三个function都引用了i和item。此时i=3,item = item3;
    所以都是function(){item + ' ' + Ar[3]}
    但是Ar传入的是[1,2,3],Ar.length = 3 没错。
    但是Ar[0] = 1;Ar[1] = 2; Ar[2] = 3....都没错。

    但是骚年们,Ar[3] = undifined;
    alert(item + ' ' + Ar[3]) = item3 undifined。

    Over,这个太麻烦,我想也没几个人这样写吧。不过这个例子非常经典,可以仔细看看。

    例外一种循环里的闭包

    for(var i=0;i<10;i++){
      setTimeout(function(){
        console.log(i);
      },1000);
    }
    // 这样并不能依次输出i,而是输出10次10。
    

    每次都创建一个新的闭包

    function createClosure(number,reference){
      var num = number,
      ref = reference,
      anArray = [1,2,3];
      return function(x){
        num += x;
        anArray.push(num);
        alert(num + ' ' + anArray.toString() + ' ' + ref);
      }
    }
    closure_1 = createClosure(20,'ref_1');
    closure_1(10); //30 [1,2,3,30] ref_1
    // 
    closure_2 = createClosure(100,'ref_2');
    closure_2(-10); //90 [1,2,3,90] ref_2
    

    说好的闭包呢,为什么没有继续用closure_1的nunber和reference?
    因为我们每建立一个新的闭包,都重新将number和reference通过参数改变。


    参考资料
    http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
    http://jibbering.com/faq/notes/closures/
    http://en.wikipedia.org/wiki/Closure_(computer_programming)
    http://baike.baidu.com/view/648413.htm
    http://coolshell.cn/articles/6731.html
    http://bonsaiden.github.io/JavaScript-Garden/zh/

  • 相关阅读:
    AndroidStudio中AlertDialog的四种用法(转载)
    ZPL指令封装
    Android程序闪退时写日志并上传到服务器
    sql server 导出表结构
    Kestrel服务器ASP.NetCore 3.1程序启用SSL
    Asp.Net Core 下 Newtonsoft.Json 转换字符串 null 替换成string.Empty(转)
    ApiResult-WebAPI开发统一返回值对象的演化(.net core版)
    EF Core 拓展SqlQuery方法(转载)
    钉钉小程序post提交json,报400、415
    体验.net core 3.1 socket
  • 原文地址:https://www.cnblogs.com/CoinXu/p/4574899.html
Copyright © 2020-2023  润新知