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³,我们可以利用闭包创建新的函数pow2pow3

<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>

 

结果:

 

posted @ 2017-08-12 14:58  ocean_boy  阅读(44)  评论(0)    收藏  举报