《JavaScript 模式》读书笔记(4)— 函数5
这一篇是函数部分的最后一篇。我们来聊聊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的更多相关文章
- JavaScript模式读书笔记 第4章 函数
2014年11月10日 1.JavaScript函数具有两个特点: 函数是第一类对象 函数能够提供作用域 函数即对象,表现为: -1,函数能够在执行时动态创建,也 ...
- JavaScript模式读书笔记 文章3章 文字和构造
1.对象字面量 -1.Javascript中所创建的自己定义对象在任务时候都是可变的.能够从一个空对象開始,依据须要添加函数.对象字面量模式能够使我们在创建对象的时候向其加入函数. ...
- 《你不知道的javascript》读书笔记2
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...
- 《编写可维护的javascript》读书笔记(中)——编程实践
上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...
- C语言深度解剖读书笔记(6.函数的核心)
对于本节的函数内容其实就没什么难点了,但是对于函数这节又涉及到了顺序点的问题,我觉得可以还是忽略吧. 本节知识点: 1.函数中的顺序点:f(k,k++); 这样的问题大多跟编译器有关,不要去刻意追求 ...
- Javascript & JQuery读书笔记
Hi All, 分享一下我学JS & JQuery的读书笔记: JS的3个不足:复杂的文档对象模型(DOM),不一致的浏览器的实现和便捷的开发,调试工具的缺乏. Jquery的选择器 a. 基 ...
- 《你不知道的javascript》读书笔记1
概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...
- JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)
this是什么? this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件.this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式.当一个函数被调用时,会 ...
- JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)
前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...
- SQL反模式读书笔记思维导图
在写SQL过程以及设计数据表的过程中,我们经常会走一些弯路,会做一些错误的设计.<SQL反模式>这本书针对这些经常容易出错的设计模式进行分析,解释了错误的理由.允许错误的场景,并给出更好的 ...
随机推荐
- nGrinder 介绍与安装
nGrinder是基于Grinder开源项目,但由NHN公司的nGrinder开发团队进行了重新设计和完善(所以叫做nGrinder). 它是由一个controller和连接它的多个agent组成,用 ...
- rbenv、fish 與 VSCode 設置之路
在最新的 VSCode 1.3.1 版裡,Integrated Terminal 變得更加好用,但由於上游套件 xterm.js 的緣故,zsh 還是有無法捲動的問題.不過作為一個 Rails 開發者 ...
- [ASP.NET Core 3框架揭秘] 服务承载系统[1]: 承载长时间运行的服务[上篇]
借助.NET Core提供的承载(Hosting)系统,我们可以将任意一个或者多个长时间运行(Long-Running)的服务寄宿或者承载于托管进程中.ASP.NET Core应用仅仅是该承载系统的一 ...
- 何用Java8 Stream API进行数据抽取与收集
上一篇中我们通过一个实例看到了Java8 Stream API 相较于传统的的Java 集合操作的简洁与优势,本篇我们依然借助于一个实际的例子来看看Java8 Stream API 如何抽取及收集数据 ...
- 【每日一包0017】pretty-ms
[github地址:https://github.com/ABCDdouyae...] pretty-ms 将毫秒转换为容易读取的时间:1337000000 → 15d 11h 23m 20s 普通用 ...
- iOS下的 Fixed BUG
input 光标位置乱窜 固定式浮层内的输入框光标会发生偏移.即 fixed 定位的容器中输入框光标的位置显示不正确,没有正常地显示在输入框中,而是偏移到了输入框外面 可触发条件 页面body出现滚动 ...
- vue.js 中使用(...)运算符报错
今天在起别人项目的时候, 发现报错. 这个错误是,项目中不识别es6的扩展运算符, 解决方式很简单. // 第一步 cnpm install babel-plugin-transform-object ...
- JZOJ 5307. 【NOIP2017提高A组模拟8.18】偷窃 (Standard IO)
5307. [NOIP2017提高A组模拟8.18]偷窃 (Standard IO) Time Limits: 1000 ms Memory Limits: 262144 KB Description ...
- 致远·面向人工智能-逐浪CMS v8.1.2全面发布[全球首个基于dotNET core3.0的中文CMS]
原文:https://www.z01.com/down/3484.shtml 再远, 我都不会停息, 因为技术而生, 因为技术而强, 这是逐浪软件的命与根! 全新打造, 三百多项超级功能, 助你十分钟 ...
- def跨域+jwt
1.跨域 由于浏览器具有“同源策略”的限制.如果在同一个域下发送ajax请求,浏览器的同源策略不会阻止.如果在不同域下发送ajax,浏览器的同源策略会阻止. 总结 域相同,永远不会存在跨域. crm, ...