一提闭包,前端首先想到的肯定就是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闭包
闭包的特性也不止于此,包括函数科里化,递归中也有使用等