函数式编程(Functional programming)是一种编程范式,利用函数把整个程序的执行过程封装起来,通过组合各种函数来获得程序的执行结果。
举个例子,这个例子是将一段英文文本转换为每个单词的首字母均大写(对应Python中的.title()方法):
let str = "My name is UltraMar1ne, I've 18 years old." str = str.split(" ") .map(word => word.slice(0, 1).toUpperCase() + word.slice(1)) .join(" "); console.log(str)
这段代码的执行结果是:
My Name Is UltraMar1ne, I’ve 18 Years Old.
解读一下上面的代码:
先通过.split()
方法将文本分割成单词数组;然后通过.map()
方法返回一个新的数组,这个新数组的每个单词通过.toUpperCase()
方法将切片后的第一个字符大写;最后再将其通过.join()
方法用空格将其拼接
通过上面的例子,我们仔细观察一下,可以发现函数式编程有以下特点:
首先来解释一下这两个概念:
if
, while
, for
等举个例子,现有一系列自定义对象,其中一个的定义如下:
let student = { name, age, studentId, score, ... };
然后我想取出所有对象的studentId
属性,两种编程方式分别如何去做呢?(这里将以返回值的形式展现)
// 声明式编程 let getStudentId = (arr) => { return arr.map(student => student.studentId); } // 命令式编程 let getStudentId = (arr) => { let res = []; arr.forEach(student => { res.push(student.studentId); }); return res; }
在规模较小、较为简单的程序中,两者其实并没有什么太大的差别。
但是随着程序规模的增长,函数式编程采用表达式的思想将会贯穿代码始终,这也是函数式编程的基础。
调用函数时不会修改外部状态
即,无论调用多少次,其返回值状态不可能会发生改变
let a = 1; // 含副作用,a被改变了 function add() { return ++a; } // 无副作用 function add(a) { return a+1; }
函数只会用到传递给它的变量以及自己内部创建的变量,不会使用到其他变量
let a = 1, b = 2; // 使用的并不是自己的变量,容易造成污染 function add() { return a+b; } // 内部使用到的变量 function add(a) { return a+1; }
一个变量一旦创建后,就不能再进行修改,任何修改都会生成一个新的变量。使用不可变变量最大的好处是线程安全。多个线程可以同时访问同一个不可变变量,让并行变得更容易实现。 由于 JavaScript 原生不支持不可变变量,需要通过第三方库来实现。 (如 Immutable.js
,Mori
等等)
let obj = Immutable({ a: 1 }); let obj2 = obj.set('a', 2); console.log(obj); // Immutable({ a: 1 }) console.log(obj2); // Immutable({ a: 2 })
在JavaScript中,函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值
如果一个函数引用了自由变量,那么该函数就是一个闭包
自由变量是指不属于该函数作用域的变量(所有全局变量都是自由变量,严格来说引用了全局变量的函数都是闭包,但这种闭包并没有什么用,通常情况下我们说的闭包是指函数内部的函数)
闭包形成需要什么条件呢?
闭包有什么用呢?
闭包可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量
举个例子:
// 简单的缓存工具 // 匿名函数创造了一个闭包 const cache = (function() { const store = {}; return { get(key) { return store[key]; }, set(key, val) { store[key] = val; } } }()); console.log(cache) //{get: ƒ, set: ƒ} cache.set('a', 1); cache.get('a'); // 1
上述例子中,创建了一个存储对象store
,由于在返回时形成了闭包,这样使得store
可以一直被引用,不会被回收
由此也可见闭包的弊端:持久化变量不会被回收,导致内存占用,很容易造成资源浪费
高阶函数这个概念自我从Python学起时,就一直有被提及,当时还觉得就是很高级的函数,其实不然
高阶函数是指一个函数以函数为参数,或以函数为返回值,或同时存在上述两种情况
高阶函数经常用于:
JavaScript是支持高阶函数的,而且也自带了一些我们熟知的高阶函数如:Array.prototype.map
, Array.prototype.filter
, Array.prototype.reduce
等,具体用法请移步红宝书(《JavaScript高级程序设计(第4版)》)
函数柯里化又称部分求值,柯里化函数会接收一些参数,但是不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值
举个例子:
// 普通函数 function add(x,y){ return x + y; } add(1,2); // 3 // 函数柯里化 let add = function(x) { return function(y) { return x + y; }; }; let increment = add(1); increment(2); // 3 // 也可以如此调用 let increment = add(1)(2); // 3
上文有提到过,可以通过函数的链式调用来进行表达式的求取。但是,随着链式调用的规模不断增大,代码的可读性也会随之降低,有没有一种方法可以解决上述问题?
有,就是函数组合
例如,假设有一个 compose 函数,它可以接受多个函数作为参数,然后返回一个新的函数。当我们为这个新函数传递参数时,该参数就会「流」过其中的函数,最后返回结果:
// 两个函数的组合 let compose = function(f, g) { return function(x) { return f(g(x)); }; }; // 或者 let compose = (f, g) => (x => f(g(x))); let add1 = x => x + 1; let mul5 = x => x * 5; compose(mul5, add1)(2);// =>15