美文网首页
闭包里的自由变量

闭包里的自由变量

作者: 一直玩编程 | 来源:发表于2016-03-28 19:37 被阅读441次

闭包(closure),是一种编程语言特性,它指的是代码块和作用域环境的结合,早期由scehme语言引入这一特性,随后几乎所有语言都带有这一特性,典型的闭包如下:

(define (func a)
  (lambda (b) 
     (lambda (c) (+ a b c))))
(((func 1) 2) 3)

闭包里的自由变量会绑定在代码块上,在离开创造它的环境下依旧生效,而这一点使用代码块的人可能无法察觉。
闭包里的自由变量的形式有很多,先举个简单例子。

function add(one){
    return function(two){
       return one+two;
    }
}
const a = add(1);
const b = add(2);
a(1);//2
b(1);//3

在上面的例子里,a和b这两个函数,代码块是相同的,但若是执行a(1)和b(1)的结果却是不同的,原因在于这两者所绑定的自由变量是不同的,这里的自由变量其实就是函数体里的one。add这个函数嵌套返回一个新的函数,而新的函数也带来了新的作用域,在JS里,自由变量的查找会从本级作用域依次向外部作用域,直到查到最近的一个,而自由变量的绑定也会在函数定义的时候就已经确定,这也是词法作用域(或称静态作用域)的具体表现。自由变量的引入,可以起到和OOP里的封装同样作用,我们可以在一层函数里封装一些不被外界知晓的自由变量,从而达到相同的效果,举例说这么一个简单的java类。

class Demo{
    private int r;
    private int k = 1;
    public Demo(int r){
       this.r = r;
    }
    public int getSquare(){
       return this.r*this.r*this.k;
    }
    public void incr(){
       this.k++;
    }
}

这里的变量r被封装了,我们可以new Demo(1)或者new Demo(2)返回不同的实例,然后调用相同的方法来得到不同的结果,这一点如果用自由变量也可以做到。

function demo(r){
   let k = 1;
   return {
      getSquare:function(){
         return r*r*k;
      },
      incr:function(){
         k++;
      }
   }
}

在执行demo(1)或者demo(2)的时候,得到的对象都可以用来执行相同的方法,然而他们的自由变量(r和k)都是相互隔离的,这就是封装的表现。自由变量的确定在其他语言有着不一样的表现,比如说php里,函数与函数之间的作用域是完全隔离的,除非你用传参或者global来拿到外部作用域的变量,这会导致我们做封装的时候极为麻烦,所以php5.3里加了use语法,它允许在函数作用域里引用上一层的自由变量。比如上面的JS代码可以改成这样的php代码。

<?php
function demo($r){
   $k = 1;
   return array(
      "getSquare"=>function() use ($r,&$k){
         return $r*$r*$k;
      },
      "incr"=>function() use (&$k){
         $k++;
      }
   );
}  

这里还有一个要注意的地方就是在use $k的时候,用了&表示按引用传递,因为如果不这么做的话,内部函数里的这个$k实际上只是一份值拷贝,无法改变其值,也无法应用改变之后的新值。有人比较偏爱php 的use语法,因为这样可以明确的确定需要使用的外部自由变量,而有的人偏爱js这种隐式写法,原因是写起来简洁不累赘,只能说语言设计都有不同的取舍。刚才说到的这些,其实函数都是一等公民的情况下,然而在其他形式的语言里,其实也都有闭包,比如说在java里,虽然无法定义一个脱离于class的函数,但是我们可以在method里的内部定义一个class,这个class也就是local class,它实际上就是一种闭包,举例来说。

class Demo {
  private volatile int a;
  public void test(final int b) {
    new Thread(
      new Runnable() {
        void run() {
          a++;
          System.out.print(b);
        }
      }
    ).start();
  }
}  

上面test方法里的local class,可以直接引用或者更改定义在类里的private variable,也可以读取方法里的参数,并且它的自由变量绑定也是在定义的时候就已经确定好的。然而由于java本身的限制,所以上面的参数b必须是final的,这一点在java8的lambda也不例外,就算在java8里你不使用final确定,它还是隐式的认为其是final,所以无法在local class里的方法更改这个参数。再说说C++,C++里最开始是使用运算符重载来达到定义函数类型,但是它有一个缺点就是无法捕获外部的自由变量,为了达到相同的效果,你需要绕很多圈子,在C++11里引入的lambda expression特性终于可以轻易的使用闭包了。

void find(string a) {
    int size;
    vector<string> arr;
    auto i = std::find_if(arr.begin(), arr.end(),
               [&](const string& s) { 
                  return s != a && s.size() > size; 
                }
             );
}

在上面的& {}语句里,我们可以使用外部的自由变量a和size,这一点可以极大方便我们在C++里使用函数。然而在C语言里,我们想要做同样的事情就很困难了,C语言并不支持高阶函数,我们想要让函数能作为参数代入,或者让函数能够返回函数,我们需要使用函数指针,典型的函数指针是这样子的:

int (*funcP) (int,int);
(*funcP)(1,2);  

这里的funcP本质上是一个指针,所以它可以在C语言里被函数当做参数或者当做返回值,然而它无法代入自由变量,也就是说它根本没法做到捕获作用域变量,我们如果想要使用外层变量,必须手动加入一个参数的指针,然后再和函数指针一起代出去,这样才能使用到外层的变量。何其麻烦!所以很多厂商为c语言定制了闭包特性,其中比较有名的就是苹果家的block,它的定义形式和函数指针极为相似,只不过把*换成了^,然而它却有闭包的特性,可以捕获自由变量,举例来说:

int a =10;
int main(void){
  int (^op) (int);
  int b = 20;
  static c = 30;
  op = ^(int one){ return one+a+b+c;};
  op(1);
}

我们上面的op是一个block,在它的内部,可以捕获到全局变量a,以及局部变量b,静态变量c,对于全局变量a以及局部静态变量c,它是可以直接访问并且可以修改的,然而对于局部变量b,它却只能访问,而无法做修改,并且当b的值发生变化的时候,它也无法感知,实际上是因为捕获的时候就已经把b的值代入进栈的block object里了。为了能够更好的捕获自由变量,所以block还引入了一个特殊的修饰符,也就是__block,用于修饰局部非静态变量,被__block修饰的变量是可以在block里读取并修改的,它的值是动态生成的,实质上是每次执行的时候都会去获取被修饰变量的内存区域,从而达到共享变量值的效果。block object还有一个比较重要的地方就是它和其他变量一样,生命周期在定义的函数执行结束之后也就结束了,这样我们需要考虑的就是如何脱离创造它的环境下依旧有效,block引入了Block_copy这一工具函数,用于将栈上的block复制到堆上,这样新的block就可以脱离原有的创造环境了。总之,闭包在各种语言上有着不同的语法语义,其核心要素就是在于自由变量如何捕获,我们在使用闭包的时候需要注意到语言的作用域方式,以及自由变量捕获方式这些特点。

Web最新资讯,请关注微信公众号“一起玩前端”或扫描二维码关注.


著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:李引证
链接:http://zhuanlan.zhihu.com/browsnet/20658538
来源:知乎

相关文章

  • 一篇就够-从底层理解闭包

    闭包 理论中的闭包 闭包是指那些能够访问自由变量的函数 自由变量: 自由变量是指在函数中使用的,但既不是函数参数,...

  • 闭包

    闭包概念 在计算机科学中,闭包, 又称词法闭包或函数闭包, 是引用了自由变量的函数。 这个被引用的自由变量将和这个...

  • JavaScript基础专题之闭包(四)

    定义 MDN 对闭包的定义为: 闭包是指那些能够访问自由变量的函数。 什么又是自由变量呢? 自由变量是指在函数中使...

  • 闭包里的自由变量

    闭包(closure),是一种编程语言特性,它指的是代码块和作用域环境的结合,早期由scehme语言引入这一特性,...

  • 7.JavaScript深入之闭包

    定义 MDN 对闭包的定义为: 闭包是指那些能够访问自由变量的函数。 那什么是自由变量呢? 自由变量是指在函数中使...

  • Javascript 闭包

    闭包 闭包是含有自由变量的函数。自由变量指的是不是函数局部变量,且不是函数参数的变量。比如 上面代码中,a就是函数...

  • 从执行上下文看闭包

    闭包 定义 闭包是引用了自由变量的函数。 自由变量的定义 自由变量是指在函数中使用的,但既不是函数参数也不是函数的...

  • 再学JS--闭包

    MDN对闭包的定义: 闭包是指那些能够访问自由变量的函数 那什么是自由变量? 自由变量是指在函数中使用的,但既不是...

  • 闭包与装饰器

    闭包:引用了自由变量的函数。这个被引用的自由变量将和这个闭包函数一同存在,即使离开了创造它的环境也不例外。 闭包用...

  • js闭包、定时器基础知识问答

    一、问题 (一)、什么是闭包? 有什么作用 闭包是指能够访问自由变量的函数 (变量在本地使用,但在闭包中定义)。换...

网友评论

      本文标题:闭包里的自由变量

      本文链接:https://www.haomeiwen.com/subject/lzgjlttx.html