js函数-5之闭包
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果返回值。
来实现一个对Array的求和,一般函数这样定义:
<script> 'use strict'; function sum(arr) { return arr.reduce(function(x,y){ return x + y; }); } var Sum = sum([1,2,3,4,5]); console.log(Sum); </script>
结果:25
如果我们不想立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数
<script> 'use strict'; function lazy_sum(arr){ var sum = function(){ return arr.reduce(function(x,y){ return x + y; }); } return sum; } var f = lazy_sum([1,2,3,4,5,6]); //变量f只是一个求和的函数 console.log(f);//输出求和函数 console.log(f());//调用求和函数 </script>
结果:

我们在函数lazy_sum中定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
还需要注意一点,调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
<script> 'use strict'; function lazy_sum(arr){ var sum = function(){ return arr.reduce(function(x,y){ return x + y; }); } return sum; } var f1 = lazy_sum([1,2,3,4,5,6]); var f2 = lazy_sum([1,2,3,4,5,6]); console.log(f1); console.log(f1); console.log(f1 === f2); </script>
结果:

看到了吧,即使是同样的参数,每次返回都是一个新的函数,彼此之间调用互不影响。
闭包
上面返回的函数在其定义内部引用了局部变量arr,所以当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,因此,闭包用起来简单,但是实现起来并不是很容易。
另一个需要注意的问题是,返回的函数并没有立即执行,而知当调用了f()时才执行。
我们来看一个小例子:
<script> 'use strict'; function count(){ var arr = []; for (var i=1;i<=3;i++){ arr.push(function(){ return i * i; }) } return arr; } var results = count(); console.log(results); console.log(results[0]); console.log(results[1]); console.log(results[2]); </script>

该例子中,每次循环都创建了一个新的函数,然后把创建的3个函数都添加到了一个Array中并返回。
<script> 'use strict'; function count(){ var arr = []; for (var i=1;i<=3;i++){ arr.push(function(){ return i * i; }) } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; console.log(f1()); console.log(f2()); console.log(f3()); </script>
该输出会是什么呢?你可能会认为是1,4,9吧。但是我们来看一下结果:

结果是都16,为什么呢?这是因为返回的函数引用了变量i,但是它并非是立刻执行。等到所有函数都返回完毕时,它们引用的变量i已经变成了4,所以结果都为16。
返回闭包是要牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?
再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
<script> 'use strict'; function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push((function (n) { return function () { return n * n; } })(i)); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; console.log(f1()); console.log(f2()); console.log(f3()); </script>
这里使用了一个“创建一个匿名函数并立刻执行”的语法:
(function (x) { return x * x; })(3); // 9
理论上讲,创建一个匿名函数并立刻执行可以这么写:
function (x) { return x * x } (3);
但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来
(function (x) { return x * x }) (3);
通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:
(function (x) { return x * x; })(3);
闭包有非常强大的功能。举个例子:
说了这么多,难道闭包就是为了返回一个函数然后延迟执行吗?
当然不是!闭包有非常强大的功能。举个例子:
在面向对象的程序设计语言里,比如Java和C++,要在对象内部封装一个私有变量,可以用private修饰一个成员变量。
在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量。我们用JavaScript创建一个计数器:
<script> 'use strict'; function create_counter(initial){ var x = initial || 0; return { inc: function(){ x += 1; return x; } } } var c1 = create_counter(); console.log(c1.inc()); console.log(c1.inc()); console.log(c1.inc()); </script>
在返回的对象中,实现了一个闭包,该包携带了局部变量x,并且从外部代码根本无法访问到变量x。也就是说,闭包是携带状态的函数,并且它的状态可以完全对外影藏起来。
闭包还可以把多参数的函数变成单参数的函数。
例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x²或x³,我们可以利用闭包创建新的函数pow2和pow3:
<script> 'use strict'; function make_pow(n){ return function(x){ return Math.pow(x, n); } } var pow2 = make_pow(2); var pow3 = make_pow(3); console.log(pow2(5)); console.log(pow3(7)); </script>
结果:


浙公网安备 33010602011771号