在了解JAVA闭包的过程中偶然读到JS的闭包文章,讲解的很明了,所以就先从JS的闭包入手学习。文章出自廖雪峰的网站
#高价函数
JS中函数可以通过变更引用,当把函数的变量引用作为参数传递给其他参数时就构成了高价函数。比如以下add方法,将其中f指定为一个函数如Math.abs,则方法体中的f(x) + f(y)则可以转换成Math.abs(y) + Math.abs(y)。当x=-1,y=2时,add方法体计算过程为Math.abs(-1) + Math.abs(2)=1+2=3。
1 | function add(x, y, f) { |
##map/reduce
map/reduce方法是Array类中的方法,其中map方法需要传入一个函数,他作用于数组中的每一个元素。比如定义pow函数,计算一个数的平方,传入map后,map方法会针对每个元素调用该pow函数,并将结果返回
1 | function pow(x) { |
reduce方法同样需要传入一个函数,比如我们要对数组元素作累加操作,定义一个函数f = function(x,y){return x+y},函数的返回值与下一个元素合作为两个参数再将调用函数f。下边的例子可以分解成[1, 2, 3, 4].reduce(f) = f(f(f(1, 2), 3), 4)= 10。有点儿递归的意思
1 | var arr = [1,2,3,4]; |
reduce方法的完整定义如下(参考文档):
1 | arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]) |
- previousValue
上一次调用回调返回的值,或者是提供的初始值(initialValue) - currentValue
数组中当前被处理的元素 - index
当前元素在数组中的索引 - array
调用 reduce 的数组 - initialValue
作为第一次调用 callback 的第一个参数。
回调函数第一次执行时,previousValue 和 currentValue 可以是一个值,如果 initialValue 在调用 reduce 时被提供,那么第一个 previousValue 等于 initialValue ,并且currentValue 等于数组中的第一个值;如果initialValue 未被提供,那么previousValue 等于数组中的第一个值,currentValue等于数组中的第二个值。
如果数组为空并且没有提供initialValue, 会抛出TypeError 。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue, 或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。
更多的使用就不作展开了,这里帖下polyfill,可以更好的理解其中的处理机制
1 | if(!Array.prototype.reduce){ |
同时,Array还有其他很多的高阶函数,比如filter(),sort(),every(),find()等等。
#闭包
上边介绍了高阶函数,即把函数当作参数传递给方法。而闭包则是方法返回函数。先看下reduce求和的例子
1 | function sum(arr) { |
当执行sum([1, 2, 3, 4, 5]); 时,则立即计算出结果,如果把sum定义成一个函数,当执行sum()时才进行计算结果,这时需要怎么做呢?操作如下:
1 | function lazy_sum(arr) { |
当我们调用lazy_sum时返回的是一个函数,并没有真正的进行计算,只有当调用f()函数才开始执行,计算求和。在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中这就构成强大的闭包。这里特别注意的sum函数的定义使用到了其作用域外的变量,即arr,当我们调用lazy_sum方法时返回的f函数,其内部保存了传递的参数[1, 2, 3, 4, 5]。换个角度说当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。看个例子:
1 | function count() { |
count方法返回一个数组,每个数组元素是一个函数,这个函数引用了外部变量i,当count方法执行完毕后,i变成了4,这个时候再调用f1时,这个比引用的变量i已经变成了4,所以执行结果为16,其他元素也一样。并不是期许的1、4、9。如果说要达到1、4、9的效果需要怎么做呢?再看个例子:
1 | function count() { |
ft函数返回一个函数类型的返回值,这个函数引用的变量是ft的参数n。当执行for循环时,每次push进去的分别是ft(1)、ft(2)、ft(3),所以当调用f1()、f2()、f3()分别返回的是1、4、9。这里为啥i的变化没有影响到(传递给)求积函数呢?是因为i作为变量传递给了方法ft,ft重新分配内存,生成临时变量,这个临时变量被求积函数引用。而上一个例子中每次push进去的求积函数引用的变量i都是同一个,当for循环完了他们共同引用的变量已经变成了4,所以每个执行结果都是16。
这里有两个点需要注意:
- 闭包最大的特点就是返回函数引用了外层变量,所以当外层函数执行完,变量并没有销毁,还在被返回函数持有,或者说被返回函数hang住了,这就是闭包最厉害的地方。
- 返回函数直接引用的外层变量,外层变量发生变化,对于返回函数来说做不了任何事情,他既不能记录外层变量的变化状态,也不能持有某一时刻该变量的值,他只能眼吧吧的看着。只有,当,返回函数被调用执行时他才获取并使用此刻的外层变量,这时的外层变量是啥就是啥。
- 当返回函数改变外层变量时,当前的返回函数变量是可以感知的,例子如下(为啥c2的执行不是4、5、6?因为c2是重新执行函数create_counter()返回的对象,与c1无任何关系):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_counter();
console.log(c1.inc());//1
console.log(c1.inc());//2
console.log(c1.inc());//3
var c2 = create_counter();
console.log(c2.inc());//1
console.log(c2.inc());//2
console.log(c2.inc());//3
#脑洞大开
很久很久以前,有个叫阿隆佐·邱奇的帅哥,发现只需要用函数,就可以用计算机实现运算,而不需要0、1、2、3这些数字和+、-、*、/这些符号。
JavaScript支持函数,所以可以用JavaScript用函数来写这些计算。来试试:
// 定义数字0:
var zero = function (f) {
return function (x) {
return x;
}
};
// 定义数字1:
var one = function (f) {
return function (x) {
return f(x);
}
};
// 定义加法:
function add(n, m) {
return function (f) {
return function (x) {
return m(f)(n(f)(x));
}
}
}
// 计算数字2 = 1 + 1:
var two = add(one, one);
// 计算数字3 = 1 + 2:
var three = add(one, two);
// 计算数字5 = 2 + 3:
var five = add(two, three);
// 你说它是3就是3,你说它是5就是5,你怎么证明?
// 呵呵,看这里:
// 给3传一个函数,会打印3次:
(three(function () {
console.log('print 3 times');
}))();
// 给5传一个函数,会打印5次:
(five(function () {
console.log('print 5 times');
}))();
``
*上边说了半天闭包,但是正式的概念不好给出,是一种设计模式?还是语法特性?还是什么?所以也就不知道整体叫闭包函数,还是返回函数叫闭包函数,因此本文描述中使用了外层变量、返回函数这样的字眼*