函数式 JavaScript 简介

733 查看

你肯定听说过JavaScript是一个函数式语言,或者至少知道他支持函数式编程。但是究竟什么是函数式编程?对于这个问题,如果你打算比较通用的编程范式,函数式的实现跟我们平时写的JavaScript又有什么不同?

好消息是在编程范式方面JavaScript没有那么讲究,你可以混入命令式(Imperative Programming)、面向对象、原型和函数式代码等等,只要你觉得合适,都能得到一样的结果。但是坏消息也因它而起,JavaScript在同样的代码库下同时提供了众多编程风格,所以你需要根据代码的可维护性、可读性和性能选择合适的编程方式。

函数式JavaScript并不是一定要用于整个项目中来体现它的价值。学习一点函数式的方法可以在我们构建项目时提供一些参考和帮助,而不论我们喜欢哪种方式。学习一些函数式模式和技术可以帮助我们写出更整洁、优雅的JavaScript代码,不论我们是不是真的用它。

这一小段代码中做了很多事:定义了一个全局变量;字符串值被传来传去,并被函数修改;还夹杂着原生JavaScript的DOM方法。函数名不是很有描述力,一定程度上因为这一切都依赖于不确定存在或不存在的上下文。但是如果你碰巧在浏览器执行了这段代码,而且网页中定义了一个<button id="main_button">,你会收到可输入文本的对话框提示,会看到那串被首字母大写了的文本。

像这种命令式代码会从顶部开始执行(如果不考虑variable hoisting的情况)。但是我们仍然可以利用JavaScript的面向对象能力来提高代码的可读性和整洁度。

面向对象的JavaScript

几年后,开发者开始注意到像浏览器这样的共享环境下的命令式编程的问题。一段JavaScript代码中定义的全局变量可能会破坏另一个全局变量。代码执行的顺序和效果无法预测,尤其在网络延迟和渲染时间的影响下。

后来出现了一些好办法来封装JS代码,让它能更好地跟DOM工作。比如下面这段代码是前面代码的一种改写,使用了面向对象的方法:

在面向对象的版本中,构造函数模拟了一个类供我们设计我们想要的对象,方法定义在对象的原型中来降低内存使用。所有的代码都封闭在一个匿名的及时调用函数表达式中,所以它不会污染全局作用域。”use strict”强制使用最新的JavaScript引擎,onclick方法被新引入的addEventListner代替,毕竟没人再使用比IE8还古董的浏览器了。这段代码可以被插入在<body>段的最后,使得所有的DOM加载完成后才执行,这时候<button>才可用。

函数式JavaScript

相比我们开始说的命令式代码,面向对象确实能使代码更整洁、模块化更好,但我们看能否通过解决它的一些缺点来使它更完美。如果我们能通过JavaScript的一些内建特性,把函数当“第一类对象”看待,我们的代码会更加干净、稳定、易拓展。

发现这个版本短了多少没?我们制定一了capify和processWords两个函数,每个函数都是pure的,也就是说他们不依赖于调用他们的代码的状态。函数不会改变他们之外的变量。针对给定的任意参数,返回唯一的结果。由于这些改进,新的函数易于测试,也很容易被移植。

刚才那段代码中可能有个函数你没见过,我们在Array上使用map方法,可以对Array上每个元素应用fn函数(fn是传入的参数)。运行在现代浏览器和服务端的实现了ES5的JavaScript为我们提供了这个方便的函数。这里不需要使用for循环,只使用一个map,可以避免使用循环变量,提高代码的整洁度和可读性。

对函数式的思考

你用为了函数范式而抛弃使用的一切,而是在你做下一个项目的时候,可以用函数的思维来考虑一下下面的问题:

  • 我的函数依赖调用上下文吗?他们是独立的吗?
  • 我可以把函数写成对某个输入总有相同返回值的吗?
  • 我的函数不改变函数外的变量吗?
  • 如果我想要在其他项目中使用这个函数,我需要修改他们吗?

这篇介绍仅仅是揭露了函数式JavaScript的表面,但希望它能引起你的探究欲望而学到更多。