一提闭包,前端首先想到的肯定就是javascript的闭包,接着就是其特性,闭包里的变量常驻内存不会消失,外部函数可以访问内部函数的变量,似乎是摆脱了作用域的限制。
那么就先说说前端js的闭包,最简单的一个例子
1
2
3
4
5
6
7
8
9function closure1() { var tmp = 'hello world'; return function() { return 'this say ' + tmp; } } //运行 var result1 = closure1(); //result1 等于 this say hello world
这样函数的局部变量就不限于函数体内,同时局部变量也不是唯一可以返回的,函数的参数也可以被返回捕获
1
2
3
4
5
6
7
8
9
10
11
12
13//数组每一项乘以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时
1
2
3
4
5<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>
1
2
3
4
5
6
7
8
9function 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以局部变量方式访问到
1
2
3
4
5
6
7
8
9
10
11function 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const 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用闭包包起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25let 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实现的闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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
有时需要数值计算,比如计算数值的能否被整除,和倍数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import 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表达式,那换成闭包如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import 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
1
2
3
4
5
6
7
8Compiled 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 来检查字节码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//计算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)
上面的运算中,都有一个共同的循环,循环内部不一样,修改下我们把
1
2
3
4
5
6
7
8
9
10
11
12def com(n, inside) { for (int i = 1; i <= n; i++) { inside(i) } } total = 0 com(2, { total += it }) println total
求和如此,那么求乘积如下
1
2
3
4
5
6
7total = 1 com(3, { total *= it }) println total
可以看出,com方法是以一个函数作为参数,返回函数作为结果。以上方法把中在循环时,把i值传递给了匿名代码块。在Groovy中就称这类匿名代码块为闭包。
可以换种写法
1
2
3
4com(2) { total += it }
这个看着像不像前面js的
1closure2()()
inside中保存了一个指向闭包的引用,{}内部的代码又被传递给了inside
给闭包传递多个参数
1
2
3
4
5
6
7def parames(closure) { closure 20, "dollars, so rich" } parames() { money, msg -> println "I have ${money} ${msg} " }
调用closure中,parames有一个数字,一个字符串参数,闭包分别用 money和msg引用指向它们
输出
1I have 20 dollars, so rich
复合闭包
1
2
3
4
5
6
7
8
9
10
11
12
13def 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闭包
闭包的特性也不止于此,包括函数科里化,递归中也有使用等
最后
以上就是忧虑月光最近收集整理的关于跨界闭包之javascript,java,groovy的全部内容,更多相关跨界闭包之javascript内容请搜索靠谱客的其他文章。
发表评论 取消回复