这一篇是函数部分的最后一篇。我们来聊聊Curry化。

十、Curry

  这部分我们主要讨论Curry化和部分函数应用的内容。但是在深入讨论之前,我们需要先了解一下函数应用的含义。

函数应用

  在一些纯粹的函数式编程语言中,函数并不描述为被调用(即called或invoked),而是描述为应用(applied)。在JavaScript中,我们可以做同样的事情,使用方法Function.prototype.apply()来应用函数,这是由于JavaScript中的函数实际上是对象,并且它们还具有如下方法。

// 定义函数
var sayHi = function(who) {
console.log("Hello" + (who? ", " + who : "") + "!");
}; // 调用函数
sayHi(); // 输出"Hello"
sayHi('world'); // 输出"Hello, world!" // 应用函数
sayHi.apply(null, ["hello"]); // 输出"Hello, hello!"

  正如上面的例子所看到的,调用(invoking)函数和应用(applying)函数可以得到完全相同的结果。apply()带有两个参数:第一个参数为将要绑定到该函数内部this的一个对象,而第二个参数是一个数组或多个参数变量,这些参数将变成可用于该函数内部的类似数组的arguments对象。如果第一个参数为null(空),那么this将指向全局对象,此时得到的结果就恰好如同调用一个非指定对象时的方法。

  当函数是一个对象的方法时,此时不能传递null引用。这种情况下,这里的对象将成为apply()的第一个参数:

// 定义函数
var alien= {
sayHi: function(who) {
console.log("Hello" + (who? ", " + who : "") + "!");
}
}
alien.sayHi('world'); // 输出"Hello, world!"
sayHi.apply(alien, ["humans"]); // 输出"Hello, humans!"

  在上面的代码中,sayHi()内部的this指向了alien对象。而在之前的例子中,this指向了全局对象。

  正如上面的两个例子所展示的那样,这些都表明我们考虑的“调用函数”并不只是“句法糖(syntactic sugar)”,而是等价于函数应用。

  请注意,除了apply()以外,Function.prototype对象还有一个call()方法,但是这仍然只是建立在apply()之上的语法糖而已。有时候最好使用该语法糖:即当函数仅带有一个参数时,可以根据实际情况避免创建只有一个元素的数组的工作。

// 在这种情况下,第二种更有效率,节省了一个数组
sayHi.apply(alien,["humans"]);
sayHi.call(alien,"humans");

部分应用

  现在我们知道,调用函数实际上就是将一个参数集合应用到一个函数中,那有没有可能只传递部分参数,而不是所有参数?这种情况就和手动处理一个数学函数所常采用的方法是相似的。假定有一个函数add()用以将两个数字加在一起:x和y。下面的代码片段展示了给定x值为5,且y值为4的情况下的解决方案。

// 出于演示的目的
// 并不是合法的JavaScript
function add(x,y) {
return x + y;
}
// 有以下函数
add(5,4); // 第1步,替换一个参数
function add(5, y){
return 5 + y;
} // 第2步,替换其他参数
function add(5, 4) {
return 5 + 4;
}

  再提醒一遍,第1、2步的代码是不合法的,仅演示目的。

  上面的代码段演示了如何手工解决部分函数应用的问题。可以获取第一个参数的值,并且在整个函数中用已知的值5替代未知的x,然后重复同样的步骤直至用完了所有的参数。

  对这个例子中的步骤1可以称为部分应用(partial application),即我们金鹰用了第一个参数。当执行部分应用时,并不会获得结果,相反会获得另一个函数。

  下面的代码片段演示了家乡的partialApply()方法的使用示例:

var add = function (x,y) {
return x + y;
}; // 完全应用
add.apply(null,[5,4]); // // 部分应用
var newadd = add.partialApply(null,[5]);
// 应用一个参数到新函数中
newadd.apply(null,[4]); //

  如上面的代码所示,部分应用向我们提供了另一个新函数,随后再以其他参数调用该函数。这种运行方式实际上与add(5)(4)有一些类似,这是由于add(5)返回了一个可在后来用(4)来调用的函数。

  此外,我们所熟悉的add(5, 4)调用方式可能并不像是“句法糖(syntactic sugar)”,相反,使用add(5)(4)才像是“句法糖(syntactic sugar)”。

  现在,返回到现实,JavaScript中并没有partialApply()方法和函数,默认情况下也并不会出现与上面类似的行为。但是可以构造出这些函数,因为JavaScript的动态性足够支持这种行为。

  使函数理解并处理部分应用的过程就成为Curry过程(Currying)。

Curry化

  这里的curry源于数学家Haskell Curry的名字。Curry化是一个转换过程,即我们执行函数转换的过程。那么,我们如何Curry化一个函数?其他的函数式语言可能已经将这种Curry化转换构建到语言本身中,并且所有的函数已经默认转换过,在JavaScript中,可以将add()函数修改成一个用于处理部分应用的Curry化函数。

  下面,我们来看个例子:

// curry化的add()函数
// 接受部分参数列表
function add(x,y) {
var oldx = x,oldy = y;
if(typeof oldy === 'undefined') { // 部分
return function(newy) {
return oldx + newy;
};
}
// 完全应用
return x + y;
} // 测试
console.log(typeof add(5)); // 输出“function”
add(3)(4); //
// 创建并存储一个新函数
var add2000 = add(2000);
add2000(19); //输出2010

  在上面的代码段中,当第一次调用add()时,它为返回的内部函数创建了一个闭包。该闭包将原始的x和y值存储到私有变量oldx和oldy中。第一个私有变量oldx将在内部函数执行的时候使用。如果没有部分应用,并且同时传递x和y值,该函数则继续执行,并简单将其相加。这种add()实现与实际需求相比显得比较冗长,在这里只是出于演示的目的这样实现。下面将显示一个更为精简的实现版本。其中并没有oldx和oldy,仅是因为原始x隐式的存储在闭包中,并且还将y作为局部变量复用,而不是像之前那样创建一个新的变量newy:

// curry化的add()函数
// 接受部分参数列表
function add(x, y) {
if(typeof y === 'undefined') { //部分
return function(y) {
return x + y;
};
}
// 完全应用
return x + y;
}

  在这些例子中,函数add()本身负责处理部分应用。但是能够以更通用的方式执行相同给的任务么?也就是说,是否可以将任意的函数转换成一个新的可以接收部分参数的函数?

function schonfinkelize(fn) {
var slice = Array.prototype.slice,
stored_args = slice.call(arguments,1);
return function () {
var new_args = slice.call(arguments),
args = stored_args.concat(new_args);
return fn.apply(null,args);
}
}

  schonfinkelize()函数可能不应该有这么复杂,只是由于JavaScript中arguments并不是一个真实的数组。从Array.prototype中借用slice()方法可以帮助我们将arguments变成一个数组,并且使用该数组更加方便。当schonfinkelize()第一次调用时,它存储了一个指向slice()方法的私有引用(名为slice),并且还存储了调用该方法后的参数(存入stored_args中),该方法仅剥离了第一个参数,这是因为第一个参数是将被curry化的函数。然后,schonfinkelize()返回了一个新函数。当这个新函数被调用时,它访问了已经私有存储的参数stored_args以及slice引用。这个新函数必须将原有的部分应用参数(stored_args)合并到新参数(new_args),然后再将它们应用到原始函数fn中(也仅在闭包中私有可用)。

  我们来测试下上面的转换方法:

function schonfinkelize(fn) {
var slice = Array.prototype.slice,
stored_args = slice.call(arguments,1);
return function () {
var new_args = slice.call(arguments),
args = stored_args.concat(new_args);
return fn.apply(null,args);
}
} // 普通函数
function add(x, y){
return x + y;
} // 将一个函数curry化并获得一个新的函数
var newadd = schonfinkelize(add,5);
console.log(newadd(4)); //输出9 // 另一种选择,直接调用新函数
console.log(schonfinkelize(add,6)(7)); //输出13 // 转换函数并不局限于单个参数或者单步Curry化
// 普通函数
function addSome(a, b, c, d, e) {
return a + b + c + d + e;
} // 可运行于任意数量的参数
console.log(schonfinkelize(addSome,1,2,3)(5,5)); // 两步curry化
var addOne = schonfinkelize(addSome,1);
console.log(addOne(10,10,10,10)); //
var addSix = schonfinkelize(addOne,2,3);
console.log(addSix(5,5)); //

  上面是完整的例子和测试。

  那什么时候适合使用Curry化呢?当发现正在调用同一个函数,并且传递的参数绝大多数都是相同的,那么该函数可能是用于Curry化的一个很好的候选参数。可以通过将一个函数集合部分应用到函数中,从而动态创建一个新函数。这个新函数将会保存重复的参数(因此,不必每次都传递这些参数),并且还会使用预填充原始函数所期望的完整参数列表。

小结

  在JavaScript中,有关函数的部分是十分重要的,我们本系列文章相关的主要函数部分已经到此告一段落了。本篇讨论了有关函数的背景和术语。学习了JavaScript中两个重要的特征。即:

  • 函数是第一类对象,可以作为带有属性和方法的值以及参数进行传递。
  • 函数提供了局部作用域,而其他打括号并不能提供这种局部作用域(当然现在的let是可以的)。此外还需要记住的是,声明的局部变量可被提升到局部作用域的顶部。

  创建函数的语法包括:

  • 1.  函数命名表达式。
  • 2. 函数表达式(与上面的相同,但是缺少一个名字),通常也称为匿名函数。
  • 3. 函数声明,与其他语言中的函数的语法类似。

  在涵盖了函数的背景和语法之后,我们学习了一些有用的模式:

  1、API模式,它们可以帮助您为函数提供更好且更整洁的接口:

    回调模式:将函数作为参数进行传递。

    配置对象:有助于保持受到控制的函数的参数数量。

    返回函数:当一个函数的返回值是另一个函数时。

    Curry化:当新函数是基于现有函数,并加上部分参数列表创建时。

  2、初始化模式,它们可以帮助您在不污染全局命名空间的情况下,使用临时变量以一种更加整洁、结构化的方式执行初始化以及设置任务(当涉及web网页和应用程序时是非常普遍的)。这些模式包括:

    即时函数:只要定义之后就立即执行。

    即时对象初始化:匿名对象组织了初始化任务,提供了可被立即调用的方法。

    初始化时分支:帮助分支代码在初始化代码执行过程中仅检测一次,这与以后在程序生命周期内多次检测相反。

  3、性能模式,可以帮助加速代码运行,这些模式包括:

    备忘模式:使用函数属性以便使得计算过的值无须再次计算。

    自定义模式:以新的主体重写本身,以使得在第二次或以后调用时仅需执行更少的工作。

  好了,函数部分到此结束了。我们下面会开始学习对象模式部分。加油!fighting!

  

《JavaScript 模式》读书笔记(4)— 函数5的更多相关文章

  1. JavaScript模式读书笔记 第4章 函数

    2014年11月10日 1.JavaScript函数具有两个特点: 函数是第一类对象    函数能够提供作用域         函数即对象,表现为:         -1,函数能够在执行时动态创建,也 ...

  2. JavaScript模式读书笔记 文章3章 文字和构造

    1.对象字面量     -1.Javascript中所创建的自己定义对象在任务时候都是可变的.能够从一个空对象開始,依据须要添加函数.对象字面量模式能够使我们在创建对象的时候向其加入函数.       ...

  3. 《你不知道的javascript》读书笔记2

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...

  4. 《编写可维护的javascript》读书笔记(中)——编程实践

    上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...

  5. C语言深度解剖读书笔记(6.函数的核心)

    对于本节的函数内容其实就没什么难点了,但是对于函数这节又涉及到了顺序点的问题,我觉得可以还是忽略吧. 本节知识点: 1.函数中的顺序点:f(k,k++);  这样的问题大多跟编译器有关,不要去刻意追求 ...

  6. Javascript & JQuery读书笔记

    Hi All, 分享一下我学JS & JQuery的读书笔记: JS的3个不足:复杂的文档对象模型(DOM),不一致的浏览器的实现和便捷的开发,调试工具的缺乏. Jquery的选择器 a. 基 ...

  7. 《你不知道的javascript》读书笔记1

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...

  8. JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)

    this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...

  9. JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)

    前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...

  10. SQL反模式读书笔记思维导图

    在写SQL过程以及设计数据表的过程中,我们经常会走一些弯路,会做一些错误的设计.<SQL反模式>这本书针对这些经常容易出错的设计模式进行分析,解释了错误的理由.允许错误的场景,并给出更好的 ...

随机推荐

  1. Java运行 Unsupported major.minor version 51.0 错误

    今天写了简单的Java程序,运行的时候不知道为啥出现这个问题 happy@happy-HP-Compaq-dx7518-MT:~/Study/CrazyJava$ java FieldTest Exc ...

  2. 网络编程之C10K

    网络编程之C10K 虽然在过去的十几年里C10K问题已经可以很好的解决,但学习网络编程时研究C10K问题仍然价值巨大,因为技术的发展都是有规律和线索可循的,了解C10K问题及其解决思路,通过举一反三, ...

  3. C++走向远洋——56(项目二1、动物这样叫、虚函数)

    */ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.cpp * 作者:常轩 * 微信公众号:Worldhe ...

  4. in和exists比较

    in是把外表和内表作hash 连接,而exists 是对外表作loop 循环,每次loop 循环再对内表进行查询. 一直以来认为exists 比in 效率高的说法是不准确的.如果查询的两个表大小相当, ...

  5. CVE-2019-0708 远程桌面漏洞复现

    漏洞影响Windows版本: Windows XP SP3 x86Windows XP Professional x64 Edition SP2Windows XP Embedded SP3 x86W ...

  6. 分布式系统一致性问题与Raft算法(下)

    上一篇讲述了什么是分布式一致性问题,以及它难在哪里,liveness和satefy问题,和FLP impossibility定理.有兴趣的童鞋可以看看分布式系统一致性问题与Raft算法(上). 这一节 ...

  7. 关于AJAX方法

    ajax的方法每次都记不住这次特意找了资料做了归总: 在这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为String类型的参数, ...

  8. 微信小程序中图片上传阿里云Oss

    本人今年6月份毕业,最近刚在上海一家小公司实习,做微信小程序开发.最近工作遇到一个小问题. 微信小程序图片上传阿里云服务器Oss也折腾了蛮久才解决的,所以特意去记录一下. 第一步:配置阿里云地址: 我 ...

  9. Nginx server name配置子域名二级域名

    绑定子域名到不同目录(子站) 网站的目录结构为 /var/www/html: ├── fx └── blog└── photo html为nginx的默认网站目录. sudo vi /etc/ngin ...

  10. agent判断用户请求设备