原文地址:http://www.moye.me/2014/12/29/closure_higher-order-function/

引子

最近发现一个问题:一部分写JS的人,其实对于函数式编程的概念并不是太了解。如下的代码片断,常常让他们觉得不可思议:

OAuth2Server.prototype.authCodeGrant = function (check) {
var self = this; return function (req, res, next) {
new AuthCodeGrant(self, req, res, next, check);
};
};

上述片断来自开源项目node-oauth2-server,这个authCodeGrant原型函数涉及到JS编程中经常用到的两个概念:闭包 和 高阶函数(check变量在这个函数中被闭包,authCodeGrant能返回函数,因此是一个高阶函数。

闭包

闭包就是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

如何来理解这个自由变量呢?

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量

什么样的变量是自由变量呢?如下片断中的freeVar对inner()来说就是个自由变量:

function wrapper() {
var freeVar = 42;
function inner() {
return 2 * freeVar;
}
return inner;
}

自由变量在闭包生成之前,并不是函数的一部分。在函数被调用时,闭包才会形成,函数将这个自由变量纳入自己的作用域,也就是说,自由变量从此与定义它的容器无关,以函数被调用那一刻为时间点,成为函数Context中的成员。

来看一个困惑前端的示例,循环添加事件:

    <button>第1条记录</button>
<button>第2条记录</button>
<button>第3条记录</button>
<button>第4条记录</button>
<button>第5条记录</button>
<button>第6条记录</button>
<script type="text/javascript">
var buttonst_obj = document.getElementsByTagName("button");
for (var i = 0, len = buttonst_obj.length; i < len; i++) {
buttonst_obj[i].onclick = function() {
alert(i);
};
}
</script>

上述片断的结果是:每个Button弹出的都是6。因为没有形成有效的闭包,因为闭包是有延迟求值特性的,所以在函数得到执行时,i === 6。

如果我们将它改成这样,i 做为外层函数的参数而被内层函数闭包,结果也是我们想要的:

var buttonst_obj = document.getElementsByTagName("button");
for (var i = 0, len = buttonst_obj.length; i < len; i++) {
buttonst_obj[i].onclick = clickEvent(i);
}
function clickEvent(i){
return function () {
console.log(i);
}
}

Why? 因为这个clickEvent(i) 高阶函数,它将 i 作为自由变量(注意:i 并不是内函数的参数,也不是内函数的一部分)传递,在 click 时闭包已经形成并被传递。

闭包的作用域

虽然自由变量从闭包时起 “将和这个函数一同存在,即使已经离开了创造它的环境也不例外”,但我们必须搞清楚,闭包产生时的作用域,看个例子:

var scope = 'global';
function echo(){
console.log(scope);
}
function wrapper(){
var scope = 'inner';
echo();
}
echo(); // 输出global
wrapper(); // 输出global  

为什么在wrapper内部的echo()调用,会输出全局scope?因为:echo定义的位置,只能闭包到全局的scope,它的外层作用域就是全局空间,即便是延迟求值也如此。

把这段代码稍加改造,就能看得更清楚:

var scope = 'global';
function echo(){
console.log(scope);
}
function wrapper(){
var scope = 'inner';
function echo(){
console.log(scope);
}
echo();
}
echo(); //输出global
wrapper(); //输出inner

闭包的自由变量来自何处,和它的外层作用域(被定义的位置)也是有关系的。

高阶函数

上述循环事件片断中的 clickEvent(i) 即为一个高阶函数。

高阶函数满足:要么接受一个或多个函数作为输入;要么输出一个函数

为什么会用到高阶函数?粗糙的说,就是为了闭包。

接受函数作为输入的高阶函数

这种高阶函数可作为一种模式的构造器,比如:我有快速排序/堆排序/希尔排序 等若干个排序函数,那么我只需要提供一个高阶函数,就能生成基于这若干种排序函数的排序器:

//排序器
var sortingGenerator = function(sortFunc){
return function(args){
var arguments = [].slice.call(args);
return sortFunc(arguments);
}
};
//引入排序算法
var heapSort = require('heapSort');
var heapSorter = sortingGenerator(heapSort);
//使用算法
heapSorter(4, 22, 44, 66, 77);

当然,其实这个高阶函数也输出了函数

输出函数的高阶函数

和上例一样,高阶函数输出一个函数也很好理解:先闭包自由变量,根据它在将来调用时产生不一样的输出。

比如,我需要一个函数,既可以算平方,也可以算立方,最好什么方都能算,这时我就需要一个如下片断的高阶函数:

//计算m的N次方
var powerOfN = function(n){
return function(m){
var res = 1;
for(var i = 0; i < n; ++i){
res *= m;
}
return res;
} ;
};
//按需生成
var powerOf2 = powerOfN(2);
var powerOf3 = powerOfN(3);
//调用传参
console.log(powerOf2(3));
console.log(powerOf3(2)); 

小结

通过闭包和高阶函数的组合运用,我们可以提炼出这样一种编程模式:通过分离>=2次的参数传递,以最少的代码实现动态的算法生成器。

更多文章请移步我的blog新地址: http://www.moye.me/

[Node.js] 闭包和高阶函数的更多相关文章

  1. Javascript 闭包与高阶函数 ( 一 )

    上个月,淡丶无欲 让我写一期关于 闭包 的随笔,其实惭愧,我对闭包也是略知一二 ,不能给出一个很好的解释,担心自己讲不出个所以然来. 所以带着学习的目的来写一写,如有错误,忘不吝赐教 . 为什么要有闭 ...

  2. JavaScript ES6函数式编程(一):闭包与高阶函数

    函数式编程的历史 函数的第一原则是要小,第二原则则是要更小 -- ROBERT C. MARTIN 解释一下上面那句话,就是我们常说的一个函数只做一件事,比如:将字符串首字母和尾字母都改成大写,我们此 ...

  3. JavaScript之闭包与高阶函数(一)

    JavaScript虽是一门面向对象的编程语言,但同时也有许多函数式编程的特性,如Lambda表达式,闭包,高阶函数等. 函数式编程是种编程范式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是 ...

  4. Javascript 闭包与高阶函数 ( 二 )

    在上一篇 Javascript 闭包与高阶函数 ( 一 )中介绍了两个闭包的作用. 两位大佬留言指点,下来我会再研究闭包的实现原理和Javascript 函数式编程 . 今天接到头条 HR 的邮件,真 ...

  5. Python 进程线程协程 GIL 闭包 与高阶函数(五)

    Python 进程线程协程 GIL 闭包 与高阶函数(五) 1 GIL线程全局锁 ​ 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的 ...

  6. JS中的高阶函数

    JS中的高阶函数 高阶函数是指以函数作为参数的函数,并且可以将函数作为结果返回的函数. 1. 高阶函数 接受一个或多个函数作为输入 输出一个函数 至少满足以上一个条件的函数 在js的内置对象中同样存在 ...

  7. 理解运用JS的闭包、高阶函数、柯里化

    JS的闭包,是一个谈论得比较多的话题了,不过细细想来,有些人还是理不清闭包的概念定义以及相关的特性. 这里就整理一些,做个总结. 一.闭包 1. 闭包的概念 闭包与执行上下文.环境.作用域息息相关 执 ...

  8. JS的闭包、高阶函数、柯里化

    本文原链接:https://cloud.tencent.com/developer/article/1326958 https://cloud.tencent.com/developer/articl ...

  9. javascript设计模式学习之三—闭包和高阶函数

    一.闭包 闭包某种程度上就是函数的内部函数,可以引用外部函数的局部变量.当外部函数退出后,如果内部函数依旧能被访问到,那么内部函数所引用的外部函数的局部变量就也没有消失,该局部变量的生存周期就被延续. ...

随机推荐

  1. Android--网络请求

    1.Android 上发送HTTP 请求的方式一般有两种,HttpURLConnection 和 HttpClient: 2.HttpURLConnection 的用法: 1)获取 HttpURLCo ...

  2. UML学习备忘

    两大类UML图: 行为图(behavior diagrams)和结构图(structure diagrams)     行为图将引导系统分析员分析且理清"系统该做些什么"?系统分析 ...

  3. [Asp.net 开发系列之SignalR篇]专题四:使用SignalR实现发送图片

    一.引言 在前一篇博文已经介绍了如何使用SignalR来实现聊天室的功能,在这篇文章中,将实现如何使用SignalR来实现发送图片的功能. 二.实现发送图片的思路 我还是按照之前的方式来讲述这篇文章, ...

  4. 解决.NET Core中MailKit无法使用阿里云邮件推送服务的问题

    在博问中(.net core怎么实现邮件发送)知道了MailKit无法使用阿里云邮件推送服务发送邮件的问题,自已实测也遇到同样的问题,而用自己搭建的邮件服务器没这个问题. 于是,向阿里云提交了工单.. ...

  5. 页面动态加入<script>标签并执行代码

    在页面中动态追加html片段的时候,有时候动态添加的代码会含有<script>标签,比如用了一些模板引擎,或者你的代码有些复杂的时候.然而我们用DOM提供的innerHTML方式来添加代码 ...

  6. MySQL--将MySQL数据导入到SQL Server

    随着时代的进步,社会的发展,各种技术层出不穷五花八门乱七八糟数不胜数(写作文呢!!!) 不扯废话,简单而言,很多公司都会同时使用多种数据库,因此数据在不同数据库之间导入导出就成为一个让人蛋疼的问题,对 ...

  7. 十分钟搞定CSS选择器

    在最近的web开发中是不是就会用到一些选择器,发现很多尤其是CSS3新增的不太熟悉,在此总结一下. 优先级 不同级别 1. 在属性后面使用 !important 会覆盖页面内任何位置定义的元素样式. ...

  8. UWP入门教程1——UWP的前世今生

    目录 引言 设备族群 UI 和通用输入模式 通用控件和布局面板 工具 自适应扩展 通用输入处理 引言 在本篇文章中,可以掌握以下知识: 设备族群,如何决定目标设备 新的UI控件和新面板帮助你适应不同的 ...

  9. PHP面向对象04_串行化

    oop04复习 2014-9-3 10:48:45 要点: --1.克隆对象 --2.__toString( ) --3. __call( ) --4.自动加载类 --5.对象串行化 1.克隆对象以及 ...

  10. fir.im Weekly - 从 iOS 10 SDK 新特性说起

    从 iOS 7 翻天覆地的全新设计,iOS 8 中 Size Classes 的出现,应用扩展,以及 Cloud Kit 的加入,iOS 9 的分屏多任务特性,今年的 WWDC iOS 10 SDK ...