跨界闭包之javascript,java,groovy

一提闭包,前端首先想到的肯定就是javascript的闭包,接着就是其特性,闭包里的变量常驻内存不会消失,外部函数可以访问内部函数的变量,似乎是摆脱了作用域的限制。

那么就先说说前端js的闭包,最简单的一个例子

  function closure1() {
      var tmp = 'hello world';
      return function() {
          return 'this say ' + tmp;
      }
  }
  //运行
  var result1 = closure1();
  //result1 等于 this say hello world

这样函数的局部变量就不限于函数体内,同时局部变量也不是唯一可以返回的,函数的参数也可以被返回捕获

    //数组每一项乘以multiple, multiple被捕获
    function closure2(multiple) {
        return function(ary) {
            return ary.map(function(item, i) {
                return item * multiple
            })
        }
    }
    var multipleClosure = closure2(9);
    var result = multipleClosure([1,2,4,5,7]);
    //或者  result = closure2(9)([1,2,4,5,7]);
    console.log(result)
     //result == [9, 18, 36, 45, 63]

multiple变量一直保存在返回的函数体内,必能在调用返回的multipleClosure是访问到。

基于对html元素的访问时闭包也是个好东西,尤其是通过getElementsByTagName或这个getElementsByClassName时

    <p class="closure">closure1</p>     
    <p class="closure">closure2</p>     
    <p class="closure">closure3</p>     
    <p class="closure">closure4</p>     
    <p class="closure">closure5</p>  
    function init() {     
        var pAry = document.getElementsByClassName("closure");
        for( var i = 0, item; item = pAry[i++]; ) {
             item.onclick = function() {     
                 console.log(i) // i 永远输出6
               }     
         }     
    }  
      init()

以上无论你点击哪个p元素都是输出6,这样情况下在事件上包裹一层匿名闭包,让i以局部变量方式访问到

    function init() {     
        var pAry = document.getElementsByClassName("closure");
         for( var i = 0, item; item = pAry[i++]; ) {
             (function() {
               var tmp = i
                  item.onclick = function() {     
                    console.log(tmp)
                   }     
            })()
        }
    }  

有时需要实现一个类让其具有私有属性,并能对操作访问,比如实现一个Stack

const items = new WeakMap();
class Stack {
    constructor() {
      items.set(this, [])
    }
    push(ele) {
        let s = items.get(this);
        s.push(ele)
    }
    pop() {
        let s = items.get(this);
        let r = s.pop();
        return r;
    }    
}

let stack = new Stack()
stack.push(121)
console.log(items)

这样做items不是私有属性任何一个方法都可以改动他,于是可以在外Stack用闭包包起来

let Stack = (function() {
    const items = new WeakMap();
    class Stack {
        constructor() {
          items.set(this, [])
        }
        push(ele) {
            let s = items.get(this);
            s.push(ele)
        }
        pop() {
            let s = items.get(this);
            let r = s.pop();
            return r;
        }
        getItems() {
            return items;
        }
    }
    return Stack;
})();

let stack = new Stack()
stack.push(121)
console.log(stack.getItems())

这样做有个弊端,扩展无法继承私有属性


前端或以为闭包只是js的专利,其实不然,闭包轻量级,短小,简洁的特性在其他语言中更具有优越性,尤其是在函数式编程中,可以避免代码冗长
如下面java实现的闭包

public class closureTest {
    public static Runnable closure() {
        String string = "hello closure";
        Runnable runnable = () -> System.out.println(string);

        System.out.println("exiting closure");
        return runnable;
    }

    public static void main(String[] args) {
        Runnable runnable = closure();

        System.out.println("go to closure");
        runnable.run();
    }
}

上列中,closure 方法有一个局部变量 value,该变量的寿命很短:只要我们退出 closure,它就会消失。closure 内创建的闭包在其词法范围中引用了这个变量。在完成 closure 方法后,该方法将闭包返回给 main 中的调用方。在此过程中,它从自己的堆栈中删除变量 value,但是lambda 表达式将会执行。

exiting closure
go to closure
hello closure

有时需要数值计算,比如计算数值的能否被整除,和倍数

import static java.util.stream.Collectors.toList;

import java.util.Arrays;
import java.util.List;


public class test {
    public static void call() {
      // numbers
    }

    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 3, 5, 6, 8);
        List result = numbers.stream()
                .filter(e -> e % 2 == 0)    //过滤出可以被2整除的
                .map(e -> e * 2)    //过滤出返回的 在乘以2
                .collect(toList());
        System.out.println(result);
    }
}

map接收的是一个单纯的lambda表达式,通常倍数是变化的,就需一种方式把变量传递给lambda表达式,那换成闭包如下

import static java.util.stream.Collectors.toList;

import java.util.Arrays;
import java.util.List;


public class test {
    public static void call() {
      // numbers
    }

    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 3, 5, 6, 8);
        int factor = 5;
        List result = numbers.stream()
                .filter(e -> e % 2 == 0)
                .map(e -> e * factor)    //map接受一个闭包。 闭包接收参数e ,同时捕获携带 factor 变量的状态
                .collect(toList());
        System.out.println(result);
    }
}

运行 javap -p test.class

Compiled from "test.java"
public class test {
    public test();
    public static void main(java.lang.String[]);
    private static java.lang.Integer lambda$main$1(int, java.lang.Integer);
    private static boolean lambda$main$0(java.lang.Integer);
}

可以看出java编译器为闭包创建一个 lambda$main$ 方法捕获一个int类型数据,
在看看字节码
javap -c -p test.class 来检查字节码

public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: anewarray     #2                  // class java/lang/Integer
       4: dup
       5: iconst_0
       6: bipush        6
       8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      11: aastore
      12: invokestatic  #4                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
      15: astore_1
      16: bipush        9
      18: istore_2
      19: aload_1
      后面省略。。。。。

private static java.lang.Integer lambda$main$1(int, java.lang.Integer);
    Code:
       0: aload_1
       1: invokevirtual #15                 // Method java/lang/Integer.intValue:()I
       4: iload_0
       5: imul
       6: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       9: areturn

把变量9 int类型值存入局部变量2,从局部变量1中装载引用类型值,然后状态变量被lambda$main$1加载并传递到为闭包创建的函数中。可以看出闭包能捕获并携带状态,并将状态从定义上下文携带到执行点


再看看groovy的闭包实现

//计算1-n的和
def sum(n) {
    total = 0
    for (int i = 1; i <= n; i++) {
        total += i
    }
    total
}
//输出3
println sum(2)

//计算1-n的乘
def multi(n) {
    total = 1
    for (int i = 1; i <= n; i++) {
        total *= i;
    }
    //return 可以省略
    return total
}
//输出6
println multi(3) 

上面的运算中,都有一个共同的循环,循环内部不一样,修改下我们把

def com(n, inside) {
    for (int i = 1; i <= n; i++) {
       inside(i)
    }
}

total = 0

com(2, {
    total += it
})
println total 

求和如此,那么求乘积如下


total = 1

com(3, {
    total *= it
})
println total

可以看出,com方法是以一个函数作为参数,返回函数作为结果。以上方法把中在循环时,把i值传递给了匿名代码块。在Groovy中就称这类匿名代码块为闭包。
可以换种写法

com(2) {
    total += it
}

这个看着像不像前面js的

 closure2()()

inside中保存了一个指向闭包的引用,{}内部的代码又被传递给了inside

给闭包传递多个参数

def parames(closure) {
    closure 20, "dollars, so rich"
}

parames() {
    money, msg -> println "I have ${money} ${msg} "
}

调用closure中,parames有一个数字,一个字符串参数,闭包分别用 money和msg引用指向它们
输出

I have 20 dollars, so rich 

复合闭包

def filterAryValue(closure) {
    def ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    def len = ary.size() - 1
    for (int i = 0; i <= len; i++) {
        ary[i] = closure(ary[i])
    }
    ary
}

def subtract = {it ->  it - 1}
def multi = {it ->  it * 2 }
println filterAryValue({it ->  multi(subtract(it)) })
//[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

第一次使用闭包subtract 返回的值再传递给multi闭包


闭包的特性也不止于此,包括函数科里化,递归中也有使用等

上一篇:高性能计算GPU解决方案系列课程一--高性能计算简介


下一篇:vue中动态加入ECharts图表时,ECharts宽度自适应/不能100%撑开