JavaScript 函数式编程基础
纯函数
对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。
无副作用,对于固定的输入,有固定的输出
const arr = [1,2,3,4,5];
// Array.slice 无副作用
arr.slice(0,3);
//=> [1,2,3]
arr.slice(0,3);
//=> [1,2,3]
// Array.splice 有副作用
arr.splice(0,3);
//=> [1,2,3]
arr.splice(0,3);
//=> [4,5]
arr.splice(0,3);
//=> []
不依赖外部环境
// 不纯,该函数的行为需要由外部环境决定
const CONST_AGE = 18;
const checkage = age => age > CONST_AGE;
// 纯,硬编码,扩展性较差,可通过 curry 来解决
const checkage = age => age > 18;
柯里化
传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
预加载函数,是一种对参数的缓存
// 柯里化两个纯函数
const match = curry((reg, str) => str.match(reg));
const filter = curry((func, arr) => arr.filter(func));
// 判断字符串里有没有空格
const haveSpace = match(/\s+/g);
haveSpace("ffffffff");
//=> null
haveSpace("a b");
//=> [" "]
// 过滤出有空格的字符串
const haveSpaceFilter = filter(haveSpace);
haveSpaceFilter(["abcdefg", "Hello World"]);
//=> ["Hello world"]
实现 curry 函数
function curry(func) {
const funcArgsLen = func.length;
return function curryWrap(...args) {
if(args.length >= funcArgsLen) {
return func.apply(null, args);
} else {
return function(...argsWrap) {
return curryWrap.apply(null, args.concat(argsWrap));
};
}
};
}
组合
解决形如 f(g(h(x))) 的函数嵌套问题。
将多个函数组合起来,除了第一个参数,其他函数接受上一函数的返回值,初始函数参数为 多元
,其余函数参数为 一元
。
function add(a, b) {
return a + b;
}
function square(n) {
return n * n;
}
const addSquare = compose(square, add);
addSquare(1, 2);
//=> 9
实现 compose 函数
function compose(...funcs) {
const length = funcs ? funcs.length : 0;
return function(...args) {
let index = 0;
let result = length ? funcs[length - 1].apply(this, args) : args[0];
while (++index < length) {
result = funcs[length - 1 - index].call(this, result);
}
return result;
};
}
Functor
Functor(函子) 是实现了 map 并遵守一些特定规则的容器类型。容器是一个对于函数调用的抽象,我们赋予容器自己去调用函数的能力,这样容器就可以自由地选择何时何地如何操作函数,以致于其拥有惰性求值、错误处理、异步调用等非常厉害的特性。
// 实现一个简单的容器
function Container(x) {
this.__value = x;
}
Container.of = x => new Container(x);
Container.prototype.map = function(func) {
return Container.of(func(this.__value));
};
console.log(Container.of(1).map(x => x + 1).map(x => `Result is ${x}`));
Maybe
// 为 map 函数添加一个检查空值的特性,我们称之为 Maybe
function Maybe(x) {
this.__value = x;
}
Maybe.of = function(x) {
return new Maybe(x);
};
Maybe.prototype.isNothing = function() {
return (this.__value === null || this.__value === undefined);
};
Maybe.prototype.map = function(func) {
return this.isNothing() ? Maybe.of(null) : Maybe.of(func(this.__value));
};
const curriedAdd = curry((a, b) => a + b);
const addTen = curriedAdd(10);
const curriedProp = curry((propName, obj) => obj[propName]);
const getAgeProp = curriedProp('age');
Maybe.of({ name: 'Rose' }).map(getAgeProp).map(addTen)
//=> Maybe { __value: null }
Maybe.of({ name: 'Rose', age: 10 }).map(getAgeProp).map(addTen)
//=> Maybe { __value: 20 }
Either,不只是用来做错误处理
const Left = function(x) {
this.__value = x;
}
const Right = function(x) {
this.__value = x;
}
// same
Left.of = function(x) {
return new Left(x);
}
Right.of = function(x) {
return new Right(x);
}
// different!
Left.prototype.map = function(func) {
return this;
}
Right.prototype.map = function(func) {
return Right.of(func(this.__value));
}
const getAge = user => user.age ? Right.of(user.age) : Left.of('ERROR!');
console.log(getAge({ name: 'Rose', age: 10 }).map(age => `Age is ${age}`));
//=> Right { __value: 'Age is 10' }
console.log(getAge({ name: 'Rose' }).map(age => `Age is ${age}`))
//=> Left { __value: 'ERROR!' }
IO
// IO 包含的是被包裹的操作的返回值,�可以将不纯的操作(如 IO�,网络请求,DOM)包裹到一个函数内,
// 延迟操作的执行,当被调用时才开始求值,这个特性应用我们称之为惰性求值。
// 示例:从当前 url 解析参数
function IO(func) {
this.__value = func;
}
IO.of = x => new IO(_ => x);
IO.prototype.map = function(func) {
return new IO(compose(func, this.__value));
};
// 一些基础函数
const split = curry((char, str) => str.split(char));
const first = arr => arr[0];
const last = arr => arr[arr.length - 1];
const filter = curry((func, arr) => arr.filter(func));
// x 类型为 Array 或 Functor
const map = curry((func, x) => x.map(func));
const eq = curry((x, y) => x === y);
const toPairs = compose(map(split('=')), split('&'));
// toPairs('a=1&b=2')
//=> [['a', '1'], ['b', '2']]
const params = compose(toPairs, last, split('?'));
// params('http://xxx.com?a=1&b=2')
//=> [['a', '1'], ['b', '2']]
const getParam = url => key => map(compose(Maybe.of, filter(compose(eq(key), first)), params))(url);
// const url = new IO(_ => window.location.href);
const url = new IO(_ => 'http://xxx.com?a=1&b=2');
const findParam = getParam(url);
// 上述代码都是纯函数,以下求值的过程是非纯的
console.log(findParam('a').__value());
//=> Maybe { __value: [ [ 'a', '1' ] ] }
Monad,用它来实现一个 Promise
首先来实现一个 Linux 命令行下的 cat 命令。
const fs = require('fs');
const readFile = filename => new IO(_ => fs.readFileSync(filename, 'utf-8'));
const print = x => new IO(_ => {
console.log(x);
return x;
});
const cat = compose(map(print), readFile);
cat('./file.js');
//=> IO { __value: IO { __value: _ => 'file\'s content.' } }
这里涉及两个 IO,所以我们得到了两层 IO,想要运行它的话,只能调用 cat('./file.js').__value().__value()
。我们可以通过实现一个 join
方法,来剥开每一层的 Functor,把里面的东西暴露给我们。
const join = x => x.join();
IO.prototype.join = function() {
return this.__value ? this.__value() : IO.of(null);
};
const cat = compose(join, map(print), readFile);
cat('./file.js').__value();
join
方法可以把 Functor 拍平(flatten),我们一般把具有这种能力的 Functor 称之为 Monad
。我们不可能总是在 map 之后手动调用 join 来剥离多余的包装,否则代码会长得像这样,
const doSomething = compose(join, map(f), join, map(g), join, map(h));
所以我们需要一个叫 chain 的方法来实现我们期望的链式调用,它会在调用 map 之后自动调用 join 来去除多余的包装,
const chain = _.curry((func, functor) => functor.chain(func));
IO.prototype.chain = function(func) {
return this.map(func).join();
}
// Promise Like
someMonad.chain(f).chain(g).chain(h)
前端应用
- RxJS
- cycleJS
- lodashJS
- underscoreJS
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: JavaScript 深拷贝 和 浅拷贝
下一篇: 谈谈自己对于 AOP 的了解
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论