JavaScript闭包_动力节点Java学院整理


Posted in Javascript onJune 27, 2017

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

我们来实现一个对Array的求和。通常情况下,求和的函数是这样定义的:

function sum(arr) {
  return arr.reduce(function (x, y) {
    return x + y;
  });
}

sum([1, 2, 3, 4, 5]); // 15

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!

function lazy_sum(arr) {
  var sum = function () {
    return arr.reduce(function (x, y) {
      return x + y;
    });
  }
  return sum;
}

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()

调用函数f时,才真正计算求和的结果:

f(); // 15

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

f1()f2()的调用结果互不影响。

闭包

注意到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

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

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。

你可能认为调用f1() f2()f3()结果应该是149,但实际结果是:

f1(); // 16
f2(); // 16
f3(); // 16

全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

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

f1(); // 1
f2(); // 4
f3(); // 9

注意这里用了一个“创建一个匿名函数并立刻执行”的语法:

(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创建一个计数器:

'use strict';

function create_counter(initial) {
  var x = initial || 0;
  return {
    inc: function () {
      x += 1;
      return x;
    }
  }
}

它用起来像这样:

var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。

闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2pow3

function make_pow(n) {
  return function (x) {
    return Math.pow(x, n);
  }
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

pow2(5); // 25
pow3(7); // 343

脑洞大开

很久很久以前,有个叫阿隆佐·邱奇的帅哥,发现只需要用函数,就可以用计算机实现运算,而不需要0、1、2、3这些数字和+、-、*、/这些符号。

JavaScript支持函数,所以可以用JavaScript用函数来写这些计算。来试试:

'use strict';

// 定义数字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));
    }
  }
}
Javascript 相关文章推荐
javascript操作JSON的要领总结
Dec 09 Javascript
js查找某元素中的所有图片地址的方法
Jan 16 Javascript
javascript中expression的用法整理
May 13 Javascript
Javascript实现Web颜色值转换
Feb 05 Javascript
深入浅出 jQuery中的事件机制
Aug 23 Javascript
jquery控制页面的展开和隐藏实现方法(推荐)
Oct 15 Javascript
Vue表单验证插件的制作过程
Apr 01 Javascript
javascript浏览器用户代理检测脚本实现方法
Oct 27 Javascript
JS实现百度网盘任意文件强制下载功能
Aug 31 Javascript
vue动画打包后失效问题的解决方法
Sep 18 Javascript
vue组件间的参数传递实例详解
Apr 26 Javascript
JS array数组检测方式解析
May 19 Javascript
JavaScript创建对象_动力节点Java学院整理
Jun 27 #Javascript
JavaScript字符串_动力节点Java学院整理
Jun 27 #Javascript
JavaScript变量作用域_动力节点Java学院整理
Jun 27 #Javascript
详解微信小程序 登录获取unionid
Jun 27 #Javascript
JavaScript定义函数_动力节点Java学院整理
Jun 27 #Javascript
详解vue项目构建与实战
Jun 27 #Javascript
微信小程序 蓝牙的实现实例代码
Jun 27 #Javascript
You might like
如何去掉文章里的 html 语法
2006/10/09 PHP
PHP 文本文章分页代码 按标记或长度(不涉及数据库)
2012/06/07 PHP
php查询mysql数据库并将结果保存到数组的方法
2015/03/18 PHP
微信支付开发告警通知实例
2016/07/12 PHP
PDO::_construct讲解
2019/01/27 PHP
ThinkPHP中图片按比例切割的代码实例
2019/03/08 PHP
如何让您的中波更粗更长 - 中波框形天线制作
2021/03/10 无线电
初窥JQuery(二) 事件机制(1)
2010/11/25 Javascript
JQuery获取文本框中字符长度的代码
2011/09/29 Javascript
JQueryEasyUI datagrid框架的进阶使用
2013/04/08 Javascript
SeaJS入门教程系列之完整示例(三)
2014/03/03 Javascript
jquery实现的随机多彩tag标签随机颜色和字号大小效果
2014/03/27 Javascript
Javascript中的数据类型之旅
2015/10/18 Javascript
JS 滚动事件window.onscroll与position:fixed写兼容IE6的回到顶部组件
2016/10/10 Javascript
es6 字符串String的扩展(实例讲解)
2017/08/03 Javascript
vue路由拦截器和请求拦截器知识点总结
2019/11/08 Javascript
Jquery属性的获取/设置及样式添加/删除操作技巧分析
2019/12/23 jQuery
JS实现前端路由功能示例【原生路由】
2020/05/29 Javascript
element-ui tree结构实现增删改自定义功能代码
2020/08/31 Javascript
python 生成不重复的随机数的代码
2011/05/15 Python
Python下使用Psyco模块优化运行速度
2015/04/05 Python
Python使用pygame模块编写俄罗斯方块游戏的代码实例
2015/12/08 Python
django使用图片延时加载引起后台404错误
2017/04/18 Python
Python实现获取汉字偏旁部首的方法示例【测试可用】
2018/12/18 Python
python实现合并多个list及合并多个django QuerySet的方法示例
2019/06/11 Python
python创建学生管理系统
2019/11/22 Python
css3高级选择器使用方法
2013/12/02 HTML / CSS
使用css3实现的windows8开机加载动画
2014/12/09 HTML / CSS
一款html5 canvas实现的图片玻璃碎片特效
2014/09/11 HTML / CSS
兰蔻美国官网:Lancome美国
2017/04/25 全球购物
英国度假别墅预订:Sykes Cottages
2017/06/12 全球购物
若通过ObjectOutputStream向一个文件中多次以追加方式写入object,为什么用ObjectInputStream读取这些object时会产生StreamCorruptedException?
2016/10/17 面试题
商务邀请函范文
2014/01/14 职场文书
小摄影师教学反思
2014/04/27 职场文书
中文专业求职信
2014/06/20 职场文书
领导干部查摆“四风”问题自我剖析材料思想汇报
2014/10/05 职场文书