JavaScript函数式编程(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-10 14:40:39

点击下载

作者:[美]Michael Fogus 佛格斯

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

JavaScript函数式编程

JavaScript函数式编程试读:

前言

什么是Underscore

Underscore.js(以下简称Underscore)是支持函数式编程的JavaScript库。Underscore网站是这样描述的:

Underscore为JavaScript提供了大量的函数式编程的支持,类似Prototype.js(或Ruby)的utility-belt,但没有扩展JavaScript内置对象。“utility belt”指的是一套能帮助你解决很多常见问题的工具。获取Underscore

Underscore网站上有最新的版本。你可以从网站上下载并放入应用目录。使用Underscore

你可以像使用所有其他库一样在你的项目中使用Underscore。然而,有几点需要注意的是,首先,默认情况下,Underscore定义了一个包含其所有函数的全局对象。要调用一个Underscore的函数,只需要调用“_”里的方法,如下面的代码:

很简单吧?

但如果你已经定义一个全局_变量,事情就没这么简单了。在这种情况下,Underscore提供了一个_.noConflict函数,将重新绑定旧的_,并返回Underscore的引用。_.noConflict使用方式如下:

本书会介绍更多Underscore的细节,但记住,虽然我广泛使用(并认可)Underscore,但这并不是一本关于Underscore的书。函数式JavaScript的源代码

许多年前,我想写基于函数式编程技术的JavaScript库。跟许多程序员一样,我曾通过实验、实践以及阅读Douglas Crockford的文章对JavaScript有所认识。虽然我继续完成了我的函数式库(Doris),但甚至连我自己都很少用它。

完成Doris后,我继续尝试广泛的函数式编程语言如Scala和Clojure。此外,我花了很多时间编写ClojureScript,特别是它的JavaScript编译器。基于这些经验,我非常了解函数式编程技术。因此,我决定尝试使用随后这几年学到的技术复活Doris。我将其命名为Lemonad,最后几乎是与本书同时完成的。

本书中大多数函数都是为了教学目的,但我扩展了一些并贡献到我的Lemonad库,以及Underscore-contrib库。本书中的代码

本书的源代码可以在GitHub上获取。此外,你也可以进入本书的网址使用上面的REPL试试本书所有定义的函数。符号约定

在编写本书(和一般编写JavaScript)的过程中,我得出以下比较好的约定。● 避免多次赋值变量。[1]● 不要使用eval。● 不要修改内核对象如Array和Function。● 优先使用函数而不是方法。● 如果项目一开始就定义函数,那么在接下来的阶段里也应该如

此。

此外,我在本书中还用到了一些约定。● 零参数的函数用于表示该参数并不重要。● 在一些例子中,……用来表示其周围的代码段可忽略。● inst#method表示实例方法引用。● Object.method表示类型方法。● 我倾向于用单行if/else语句,避免使用大括号。这样可以节省宝

贵的垂直空间。● 我喜欢用分号。

基本上除了函数式方面,这本书中的JavaScript代码就像现实中的大多数的JavaScript代码。本书目标读者

我在几年前写一本Scheme编程语言的函数式编程的入门书籍的时候,就产生了写这本书的想法。尽管Scheme和JavaScript有一些共同的特点,但在许多重要方面截然不同。我想撇开语言来说说函数式编程。我写这本书介绍函数式编程是什么,什么是不可能在JavaScript中找到的。

我期望读者对JavaScript有基本的理解。可以通过很多书籍以及网上的资源和讨论来学习这门语言,这里就不占用本书篇幅介绍了。我还期望读者能对面向对象编程有所了解,如Java和Ruby,Python和JavaScript。了解面向对象编程可以帮助你理解我偶尔使用的一些短语,但并不需要专家级的了解。

本书的合适读者是希望了解函数式编程的JavaScript程序员,或希望学习JavaScript的函数式程序员。对于后一种读者,还可以研究一些JavaScript的……古怪的部分,特别是可以参考Douglas Crockford的《JavaScript精粹》(O’Reilly出版)。最后,这本书还适合任何希望了解函数式编程,包括不打算使用JavaScript的读者。本书组织结构

下面是JavaScript函数式编程的大纲。第1章 JavaScript函数式编程简介

这本书通过引入一些主题来开始,包括函数式编程和Underscore.js。第2章 一等函数与Applicative编程

第2章定义了一等函数,展示如何使用它们,并介绍了一些常见的应用。其中介绍了一个特别的技术,即利用一等函数实现Applicative编程。本章结尾还对软件开发中函数式编程的重要途径,即“数据思想”进行了探讨。第3章 变量的作用域和闭包

第3章是一个过渡章,涵盖要了解函数式JavaScript编程需要注意的两个核心主题。通过覆盖变量作用域,包括在JavaScript中使用的方式:词法作用域,动态作用域和函数作用域。本章以闭包的介绍结尾,解释了工作原理,以及如何和为什么可能需要使用闭包。第4章 高阶函数

本章建立在第2、3章基础上,介绍了一个重要的一等函数:高阶函数。虽然“高阶函数”听起来很复杂,本章会说明它其实是很直白的。第5章 由函数构建函数

本章介绍了如何用其他函数“组合”新函数。组合函数是函数式编程的重要技术,本章将引导你了解这项技术。第6章 递归

第6章是另一个过渡章节,将讨论递归,即一个直接或间接调用自身的函数。因为递归在JavaScript中是有局限的,因此不被经常使用;但是,本章会介绍几个绕过这些局限的方法。第7章 纯度、不变性和更改政策

第7章介绍如何编写不会改变任何东西的函数。简单地说,函数式编程的便利性来源于不可变变量。本章将带你理解其中的含义。第8章 基于流的编程

第8章涉及如何将任务甚至是整个系统,看作变换数据的“装配线”。第9章 无类编程

最后一章的重点是介绍函数式编程是完全不同于基于类的面向对象编程的结构化应用程序的方式。

在这些章节之后补充了附录A。第1章JavaScript函数式编程简介

本章是本书后续内容的基础。本章将介绍什么是Underscore以及如何开始使用它;除此之外,也将对后续用到的术语和本书的目标进行解释。1.1 JavaScript案例“为什么选择JavaScript”,这个问题的答案很简单:灵活。换句话说,或许除了Java,目前没有比JavaScript更加流行的语言了。所有浏览器以及现有新兴技术的大范围支持,使得JavaScript成了满足可移植性的不错的选择,甚至是唯一的选择。

随着客户端服务和单页布局应用架构的再度出现,JavaScript越来越广泛地应用于附加在大量网络服务中的分离式应用当中(如单页布局)。比如所有的谷歌应用程序都由JavaScript编写,这也是单页布局应用的范例。

如果在学习JavaScript之前,你已对函数式编程有所研究,那么好消息是JavaScript“天然”支持函数式技术(例如,函数是JavaScript的一个核心概念)。举个例子,如果你对JavaScript有所了解,那么应该见过下面的代码:

Array#forEach方法于ECMA-262语言标准第5版加入,它接收一个函数(本例中的alert)并将数组中的每一个元素依次交给该函数执行。除此之外,JavaScript提供大量的能够以其他函数为参数的方法和函数。在本书的后续内容中,我将进一步以此类编程风格进行讨论。

JavaScript有坚实的语言原语基础,这是非常好的事情,但同时也是一把双刃剑。从函数、闭包、原型,到相当不错的动态核心,[1]JavaScript提供了一系列非常好的工具集。此外,JavaScript也提供了一种非常开放和灵活的执行模型。举一个小例子:所有的JavaScript函数都有一个apply方法,它使得我们可以用一个数组来调用函数,其中,数组的元素作为函数的参数。使用apply,我们可以创建一个名为splat的函数。它接受一个函数fun,并返回另一个函数,该函数接受一个数组并用apply来执行函数fun。这样一来,传入函数splat的数组的元素是函数fun的参数:

这是我们的函数式编程初试——一个返回函数的函数——我们待会再来仔细研究。需要注意的是,JavaScript是一种非常灵活的语言,apply仅仅只是它实现函数式编程的一种方法而已。

另外一个展现JavaScript灵活性的地方是,我们可以随时以任意多个任意类型的参数来调用任意一个函数。我们可以创建一个与splat功能相反的函数unsplat,它接受一个函数fun并返回另一个接受任意多个参数的函数,将参数转为数组传入函数fun并调用它:

每个JavaScript函数都可以访问一个名为arguments的局部对象,它以类似于数组的形式存储了调用本函数时的实际参数。arguments非常强大,并能够产生惊人的效果。另外,call方法与apply方法类似,只不过apply方法将参数放到数组中来调用函数,而call方法则是直接将参数逐一传递给函数。Apply、call和arguments的同时存在只是JavaScript强大灵活性的一个小例子。

随着用JavaScript来创建各种规模应用的趋势,你可能会担心该语言本身发展及其运行时支持会停滞不前。但是随意阅读一下ECMAScript.next就可以发现,JavaScript是一种不断发展(尽管速度[2]缓慢)的语言。同样,会不断有像V8引擎那样经得起时间考验的新兴技术来改进和提升JavaScript的速度和效率。JavaScript的一些局限

从JavaScript的出现、演变、发展到普遍存在的角度来讲,JavaScript的局限性很小。很多人会诟病JavaScript的种种奇怪用法和鲁棒性缺陷,但事实上,JavaScript确实存活了下来,并且将会一直存在下去。但无论如何,我们需要承认JavaScript是一门存在缺陷的[3]语言。事实上,目前最流行的介绍JavaScript的书:Douglas Crockford的JavaScript: The Good Parts(O’Reilly出版),花了大量的篇幅来讨论JavaScript的不好的部分。这门语言确实有古怪之处,而且总体来说在表达方面也不是很简洁。然而,修复JavaScript中存在的问题恐怕会“破坏网络世界”,这恐怕也是不能被大众所接受的。也正是因为这些问题的存在,针对于JavaScript的编译平台不断增多;[4]这确实是一片非常多产的领域。

从语言支持角度来讲,经过时间的选择,我们发现命令式语言技术和对全局作用域的依赖使得JavaScript存在不安全性。这是因为,若在创建程序时将关键点放在处理易变性上,会给程序扩展带来潜在的混乱。同样,这门语言也提供了很多可用来实现其他语言中默认存在的高级功能的方法。如在主要版本的ECMAScript 6之前,JavaScript没有提供模块系统,但其实可用原生对象来简便地创建模块。这个版本的JavaScript提供了一系列松散的、互不兼容的基本部分集合,可以用来保证一系列自定义模块的实现。

古怪的语言、不安全的功能以及一系列互相竞争的库,这三个理由使得我们很难考虑选择JavaScript。然而,希望还是有的。利用一系列的规范和规约,JavaScript代码可以做到不仅安全,而且容易理解和测试,除此之外,也能够成比例缩减代码库大小。本书将带你掌握这样的方法:函数式编程。1.2 开始函数式编程

或许你已经从最喜欢的新闻聚合网站上听说过函数式编程,也可能你已使用过支持函数式编程的技术。如果你写过JavaScript代码(在本书中,我们默认读者写过),那么你确实已使用过支持函数式编程的语言。然而,有种情况是,你可能没有从函数式编程的角度使用过JavaScript。本书突出了函数式编程风格,它可以帮助我们简化自己的库和应用程序,并帮助我们驯服那只使得JavaScript变得复杂的“野兽”。

我们可以用下面一句话来直白地描述函数式编程:

函数式编程通过使用函数来将值转换成抽象单元,接着用于构建软件系统。

这是一种简单粗糙的解释,但对于本书的前面部分来说已够用。本书使用Underscore作为库来实现函数式表达式,并且大部分内容也都遵循上面的定义。然而,这个定义并没有解释清楚“为什么”使用函数式编程。1.2.1 为什么函数式编程很重要

对我来说,重大演变还是向更加函数式的风格的发展,它使得我们放弃很多旧的习惯,并从一些面向对象思想中逐渐退出。

——John Carmack

如果你熟悉面向对象编程,那么你可能会同意它的主要目标是问题分解,如图1-1 所示(Gamma,1995)。图1-1 将一个问题分解为面向对象的几个部件

同样,如图1-2 所示,这些部件/对象可以被聚集在一起,并组合成更大的部件。图1-2 对象“组合”在一起形成更大的对象

基于这些部件和它们之间的组合关系,我们就可以从部件之间的交互和值来描述一个系统,如图1-3所示。图1-3 一个描述面向对象系统及其交互的序列图

这里只对如何构建面向对象系统进行了简单粗糙的解释,但这种抽象的解释已经能够说明问题。

相比较而言,用严格的函数式编程的方法来解决问题,也会将一个问题分成几部分(函数)来解决,如图1-4所示。图1-4 将一个问题分解成几个函数式的部分

与面向对象方法将问题分解成多组“名词”或对象不同,函数式[5]方法将相同的问题分解成多组“动词”或函数。与面向对象编程类似的是,函数式编程也通过“黏结”或“组合”其他函数的方式来构建更大的函数,以实现更加抽象的行为,如图1-5 所示。图1-5 通过函数组合来实现更多的行为

最终,一种将函数式的部件组成一个完整的系统的方法(见图1-6 )是取一个值,逐渐地将它“改变”——通过一个原始的或组合的函数——成另一个值。图1-6 一个通过数据转换进行交互的函数式系统

在一个面向对象系统的内部,我们发现对象间的交互会引起各个对象内部状态的变化,而整个系统的状态转变则是由许许多多小的、细微的状态变化混合来形成的。这些相互关联的状态变化形成了一个概念上的“变化网”,我们时不时会因为它而感到困惑。当需要了解其带来的微妙且广泛的状态变化时,这种困惑就会成为一个问题。

相比之下,函数式系统则努力减少可见的状态修改。因此,向一个遵循函数式原则的系统添加新功能就成了理解如何在存在局限的上下文环境中——无破坏性的数据转换(例如原始数据永不发生变化)——来实现新的函数。然而,我并不愿意在函数式风格和面向对象风格之间画一条明显的界线,说它们应该是对立关系。因为既然JavaScript同时支持这两种模式,那就说明一个系统可以也应该由这两种模式共同组成。如何平衡函数式风格和面向对象风格是一件需要技巧的事情,我们将会在第9章讨论mixin时解答这个问题。然而,既然本书是在介绍函数式编程在JavaScript中实现,那么我们将会将大量篇幅放在函数式风格而非面向对象风格上。

这样说来,一个美好的基于函数式原则而构建的系统将是一个能够从输入终端接收未加工原料并逐渐从输出终端生产出产品的装配线设备(见图1-7 )。图1-7 函数式程序类似于一个用来转换数据的机器

当然,这种装配线的类比并不完全准确,因为我们知道每个机器生产产品都需要消耗加工原料。相比之下,函数式编程以命令式的方式构建系统,并通过将显性的状态改变缩减到最小来变得更加模块化(Hughes,1984)。实践中的函数式编程并不以消除状态改变为主要目的,而是将任何已知系统中突变的出现尽量压缩到最小区域中去。1.2.2 以函数为抽象单元

抽象方法是指隐藏了实现细节的函数。事实上,函数是一种非常好的工作单元,它使得我们能够坚持一句由巴特勒兰普森提出的、在UNIX社区长期奉行的格言:

使之运行,使之正确,使之快速。

同样,抽象函数使得我们能够完全理解Kent Beck的关于测试驱动开发(TDD)的类似的说法:

使之运行,再使之正确,再使之快速。

例如,对于错误和警告的报告,我们可以写出如下代码:

虽然这个函数并不能全面地解析年龄字符串,但却是一个好例子。我们可以用如下方法来调用parseAge:

parseAge函数工作正常,但如果我们想修改输出错误、信息和警告呈现的方式,那么就需要修改相应的代码行,以及其他地方的类似输出模式。一个较好的方法是将错误、信息和警告的概念抽象成不同的函数:

有了这些函数,我们就可将parseAge函数改写成:

下面是新函数的行为:

新的行为与旧的行为差别不大,不同的是现在报告错误、信息和警告的想法已经被抽象化了。错误、信息和警告的报告结果也因此完全被修改:

因此,由于行为包含在单一的函数中,所以函数可以被能够提供类似行为的新函数取代,或直接被完全不同的行为所取代(Abelson and Sussman,1996)。1.2.3 封装和隐藏

多年来,我们一直被教导说封装是面向对象的基石。在面向对象术语中,封装是指一种将若干个数据与用来操纵它们的特定操作包装起来的方式,如图1-8所示。图1-8 大多数面向对象语言使用对象边界来包装数据元素和它们的操作;因此,一个Stack类将一个元素的数组和用来操作这个数组的push、pop和peek方法包装在一起

JavaScript提供了一个对象系统,它也确实能够封装数据与操作。然而,有时封装被用来限制某些元素的可见性,称为数据隐藏。在JavaScript的对象系统中,并没有提供直接隐藏数据的方式,因此使用一种叫做闭包的方式来隐藏数据,如图 1-9所示。图1-9 使用闭包来封装数据是一种函数式的向客户端隐藏细节的方式

在第3章之前,我们不会深入介绍闭包,但现在需要你记住的是,闭包也一种函数。通过使用包含了闭包的函数式技术,我们能够与大多数面向对象语言一样,实现有效的数据隐藏,尽管我不愿说函数式封装和面向对象式封装究竟谁更好。虽然在实践中它们是不同的,但它们实际上都提供了建立某种抽象的类似的方法。事实上,本书并不鼓励大家为了学习函数式编程而扔掉曾经学到的一切,从而喜欢学习函数式编程;相反,我们旨在就其本身来讨论函数式编程,这样你就可以确定它是否合适你的需求。1.2.4 以函数为行为单位

隐藏数据和行为(通常不方便于快速修改)只是一种将函数作为抽象单元的方式。另外一种方式是提供一种简单地存储和传递基本行为的离散单元。举个例子,用JavaScript语法来索引数组中的一个值:

虽然数组索引是JavaScript的一个核心行为,但并没有办法可以在不把它放到函数里的前提下,获取这个行为并根据需要来使用它。因此,举一个函数的简单例子就是抽象数组索引行为,我们称它为nth。nth的简单实现如下所示:

或许正如你所猜测的,nth的主逻辑工作正常:

然而,当传入意想不到的值时,nth就会出错:

因此,如果想围绕nth来实现函数抽象,我们或许会设计出下面的声明:nth返回一个存储在允许索引访问的数据类型中的有效元素。这段声明的关键在于索引数据类型的概念。为了判断什么是索引的数据类型,我们可以创建一个isIndexed函数,实现如下所示:

函数isIndexed也是一个提供了判断某个数据是否是字符串或数组的函数抽象。在抽象之上实现新的抽象,nth的实现也就如下所示:

完整的nth函数的使用方法如下所示:

与我们从index抽象中构建nth函数抽象的方式一样,我们也可以以同样的方式来构建一个second抽象:

函数second允许我们在一个不同但相关的情况下,正确使用nth函数:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载