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 相关文章推荐
基于JQuery和CSS3实现仿Apple TV海报背景视觉差特效源码分享
Sep 21 Javascript
基于canvas实现的钟摆效果完整实例
Jan 26 Javascript
基于JS判断iframe是否加载成功的方法(多种浏览器)
May 13 Javascript
微信小程序  自定义创建详细介绍
Oct 27 Javascript
懒加载实现的分页&amp;&amp;网站footer自适应
Dec 21 Javascript
浅谈FastClick 填坑及源码解析
Mar 02 Javascript
react项目如何使用iconfont的方法步骤
Mar 13 Javascript
微信小程序云开发之使用云函数
May 17 Javascript
浅谈vue中组件绑定事件时是否加.native
Nov 09 Javascript
JS动态图片的实现方法完整示例
Jan 13 Javascript
node.js使用zlib模块进行数据压缩和解压操作示例
Feb 12 Javascript
一行JavaScript代码如何实现瀑布流布局
Dec 11 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
PHP+FLASH实现上传文件进度条相关文件 下载
2007/07/21 PHP
php数组一对一替换实现代码
2012/08/31 PHP
joomla实现注册用户添加新字段的方法
2016/05/05 PHP
Yii2框架实现注册和登录教程
2016/09/30 PHP
javascript下利用arguments实现string.format函数
2010/08/24 Javascript
jQuery+HTML5实现图片上传前预览效果
2015/08/20 Javascript
js实现拖拽效果(构造函数)
2015/12/14 Javascript
React.js入门实例教程之创建hello world 的5种方式
2016/05/11 Javascript
jQuery的中 is(':visible') 解析及用法(必看)
2017/02/12 Javascript
react性能优化达到最大化的方法 immutable.js使用的必要性
2017/03/09 Javascript
微信小程序实现导航栏选项卡效果
2020/06/19 Javascript
vue-router3.0版本中 router.push 不能刷新页面的问题
2018/05/10 Javascript
关于AOP在JS中的实现与应用详解
2019/05/06 Javascript
JS事件流与事件处理程序实例分析
2019/08/16 Javascript
vuex实现购物车功能
2020/06/28 Javascript
Vue(定时器)解决mounted不能获取到data中的数据问题
2020/07/30 Javascript
Python类方法__init__和__del__构造、析构过程分析
2015/03/06 Python
python中xrange用法分析
2015/04/15 Python
Python爬虫代理IP池实现方法
2017/01/05 Python
Python数据类型之列表和元组的方法实例详解
2019/07/08 Python
使用Python opencv实现视频与图片的相互转换
2019/07/08 Python
OpenCV里的imshow()和Matplotlib.pyplot的imshow()的实现
2019/11/25 Python
python图片剪裁代码(图片按四个点坐标剪裁)
2020/03/10 Python
python中matplotlib实现随鼠标滑动自动标注代码
2020/04/23 Python
python小白学习包管理器pip安装
2020/06/09 Python
python中selenium库的基本使用详解
2020/07/31 Python
Python爬虫+Tkinter制作一个翻译软件的示例
2021/02/20 Python
英国设计的甲板鞋和船鞋:Chatham
2018/12/06 全球购物
sealed修饰符是干什么的
2012/10/23 面试题
小学模范班主任事迹材料
2014/05/13 职场文书
加油口号大全
2014/06/13 职场文书
农行心得体会
2014/09/02 职场文书
街道党风廉政建设调研报告
2015/01/01 职场文书
担保书范文
2015/01/20 职场文书
写给老婆的保证书
2015/02/27 职场文书
Python 用户输入和while循环的操作
2021/05/23 Python