函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个对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()结果应该是1,4,9,但实际结果是:
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,我们可以利用闭包创建新的函数pow2和pow3:
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
# 闭包问题
# 通俗易懂地解释JS中的闭包
# JS继承与闭包及JS实现继承的三种方式
# 浅谈JavaScript作用域和闭包
# JS闭包的几种常见形式实例详解
# JS实现闭包中的沙箱模式示例
# JavaScript闭包的简单应用
# 通过示例彻底搞懂js闭包
# JavaScript闭包和回调详解
# 浅谈JS封闭函数、闭包、内置对象
# 深入理解Javascript中的作用域链和闭包
# JS闭包可被利用的常见场景小结
# 利用js的闭包原理做对象封装及调用方法
# javascript闭包功能与用法实例分析
# JavaScript中闭包的详解
# JS闭包用法实例分析
# 图解Javascript——作用域、作用域链、闭包
# 轻松理解JavaScript闭包
# js中的闭包学习心得
# 可以用
# 一个函数
# 创建一个
# 还可以
# 当我们
# 会报
# 绑定
# 都是
# 有个
# 在这个
# 是这样
# 不需要
# 说了
# 这么多
# 只需
# 很久
# 要在
# 而不
# 用了
# 注意到
相关文章:
如何在Windows服务器上快速搭建网站?
如何通过宝塔面板实现本地网站访问?
网站专业制作公司有哪些,做一个公司网站要多少钱?
高端企业智能建站程序:SEO优化与响应式模板定制开发
湖南网站制作公司,湖南上善若水科技有限公司做什么的?
定制建站策划方案_专业建站与网站建设方案一站式指南
如何在橙子建站中快速调整背景颜色?
如何在IIS服务器上快速部署高效网站?
如何在西部数码注册域名并快速搭建网站?
建站主机功能解析:服务器选择与快速搭建指南
建站之星2.7模板:企业网站建设与h5定制设计专题
c++ stringstream用法详解_c++字符串与数字转换利器
C++ static_cast和dynamic_cast区别_C++静态转换与动态类型安全转换
建站ABC备案流程中有哪些关键注意事项?
企业网站制作公司网页,推荐几家专业的天津网站制作公司?
香港服务器网站推广:SEO优化与外贸独立站搭建策略
详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)
如何高效完成自助建站业务培训?
如何在阿里云完成域名注册与建站?
高防服务器:AI智能防御DDoS攻击与数据安全保障
C#怎么使用委托和事件 C# delegate与event编程方法
如何通过PHP快速构建高效问答网站功能?
高防网站服务器:DDoS防御与BGP线路的AI智能防护方案
如何用美橙互联一键搭建多站合一网站?
MySQL查询结果复制到新表的方法(更新、插入)
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
武汉网站如何制作,黄黄高铁武穴北站途经哪些村庄?
娃派WAP自助建站:免费模板+移动优化,快速打造专业网站
家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?
教育培训网站制作流程,请问edu教育网站的域名怎么申请?
如何在阿里云通过域名搭建网站?
子杰智能建站系统|零代码开发与AI生成SEO优化指南
Android滚轮选择时间控件使用详解
大连 网站制作,大连天途有线官网?
电商网站制作公司有哪些,1688网是什么意思?
独立制作一个网站多少钱,建立网站需要花多少钱?
如何在IIS7上新建站点并设置安全权限?
网站视频怎么制作,哪个网站可以免费收看好莱坞经典大片?
如何选择建站程序?包含哪些必备功能与类型?
香港服务器如何优化才能显著提升网站加载速度?
黑客如何通过漏洞一步步攻陷网站服务器?
如何在Mac上搭建Golang开发环境_使用Homebrew安装和管理Go版本
建站主机选哪种环境更利于SEO优化?
打鱼网站制作软件,波克捕鱼官方号怎么注册?
Python lxml的etree和ElementTree有什么区别
江苏网站制作公司有哪些,江苏书法考级官方网站?
PHP 500报错的快速解决方法
建站之星安装失败:服务器环境不兼容?
建站主机无法访问?如何排查域名与服务器问题
红河网站制作公司,红河事业单位身份证如何上传?
*请认真填写需求信息,我们会在24小时内与您取得联系。