JavaScript函数式编程

上一篇在这里:

IO

真正的程序总要去接触肮脏的世界

function readLoaclStorage(){
    return window.localStorage;
}

Io跟前面那几个Functor不同的地方在于,他的__value是一个函数。它把不纯的操作(比如IO、网络请求、DOM)包裹到一个函数内,从而延迟这个操作的执行。所以我们认为,IO包含的是被包裹的操作的返回值。

IO其实也算是惰性求值

IO负责了调用链积累了很多很多不纯的操作,带来的复杂性和不可维护性。

import _ from 'lodash';
var compose=_.flowRight;
var IO=function(f){
    this._value=f;
}
IO.of=x=>new IO(_=>x);
IO.prototype.map=function(f){
    return new IO(compose(f,this.__value));
}

三、IO

下面我们的程序要走出象牙塔,去接触外面“肮脏”的世界了,在这个世界里,很多事情都是有副作用的或者依赖于外部环境的,比如下面这样:

function readLocalStorage(){
    return window.localStorage;
}

这个函数显然不是纯函数,因为它强依赖外部的 window.localStorage 这个对象,它的返回值会随着环境的变化而变化。为了让它“纯”起来,我们可以把它包裹在一个函数内部,延迟执行它:

function readLocalStorage(){
    return function(){
        return window.localStorage;   
    }
}

这样 readLocalStorage 就变成了一个真正的纯函数! OvO为机智的程序员鼓掌!

额……好吧……好像确实没什么卵用……我们只是(像大多数拖延症晚期患者那样)把讨厌做的事情暂时搁置了而已。为了能彻底解决这些讨厌的事情,我们需要一个叫
IO 的新的 Functor

import _ from 'lodash';
var compose = _.flowRight;

var IO = function(f) {
    this.__value = f;
}

IO.of = x => new IO(_ => x);

IO.prototype.map = function(f) {
    return new IO(compose(f, this.__value))
};

IO 跟前面那几个 Functor 不同的地方在于,它的 __value
是一个函数。它把不纯的操作(比如
IO、网络请求、DOM)包裹到一个函数内,从而延迟这个操作的执行。所以我们认为,IO
包含的是被包裹的操作的返回值。

var io_document = new IO(_ => window.document);

io_document.map(function(doc){ return doc.title });
//=> IO(document.title)

注意我们这里虽然感觉上返回了一个实际的值
IO(document.title),但事实上只是一个对象:{ __value:
[Function]
}
,它并没有执行,而是简单地把我们想要的操作存了起来,只有当我们在真的需要这个值得时候,IO
才会真的开始求值,这个特性我们称之为『惰性求值』。(培提尔其乌斯:“这是怠惰啊!”)

是的,我们依然需要某种方法让 IO
开始求值,并且把它返回给我们。它可能因为 map
的调用链积累了很多很多不纯的操作,一旦开始求值,就可能会把本来很干净的程序给“弄脏”。但是去直接执行这些“脏”操作不同,我们把这些不纯的操作带来的复杂性和不可维护性推到了
IO 的调用者身上(嗯就是这么不负责任)。

下面我们来做稍微复杂点的事情,编写一个函数,从当前 url
中解析出对应的参数。

import _ from 'lodash';

// 先来几个基础函数:
// 字符串
var split = _.curry((char, str) => str.split(char));
// 数组
var first = arr => arr[0];
var last = arr => arr[arr.length - 1];
var filter = _.curry((f, arr) => arr.filter(f));
//注意这里的 x 既可以是数组,也可以是 functor
var map = _.curry((f, x) => x.map(f)); 
// 判断
var eq = _.curry((x, y) => x == y);
// 结合
var compose = _.flowRight;

var toPairs = compose(map(split('=')), split('&'));
// toPairs('a=1&b=2')
//=> [['a', '1'], ['b', '2']]

var params = compose(toPairs, last, split('?'));
// params('http://xxx.com?a=1&b=2')
//=> [['a', '1'], ['b', '2']]

// 这里会有些难懂=。= 慢慢看
// 1.首先,getParam是一个接受IO(url),返回一个新的接受 key 的函数;
// 2.我们先对 url 调用 params 函数,得到类似[['a', '1'], ['b', '2']]
//   这样的数组;
// 3.然后调用 filter(compose(eq(key), first)),这是一个过滤器,过滤的
//   条件是 compose(eq(key), first) 为真,它的意思就是只留下首项为 key
//   的数组;
// 4.最后调用 Maybe.of,把它包装起来。
// 5.这一系列的调用是针对 IO 的,所以我们用 map 把这些调用封装起来。
var getParam = url => key => map(compose(Maybe.of, filter(compose(eq(key), first)), params))(url);

// 创建充满了洪荒之力的 IO!!!
var url = new IO(_ => window.location.href);
// 最终的调用函数!!!
var findParam = getParam(url);

// 上面的代码都是很干净的纯函数,下面我们来对它求值,求值的过程是非纯的。
// 假设现在的 url 是 http://xxx.com?a=1&b=2
// 调用 __value() 来运行它!
findParam("a").__value();
//=> Maybe(['a', '1'])

AP因子

函子里面包含的值,完全可能是函数。我们可以想象这样一种情况,一个函子的值是数值,另一个函子的值是函子。

class Ap extends Functor{
    ap(F){
        return Ap.of(this.val(F.val));
    }
}
Ap.of(addTwo).ap(Functor.of(2));

实例

function Functor(val){
    this.__val=val;
}
Functor.of=function(val){
    return new Functor(val);
}
Functor.prototype.map=function(fn){
    return Functor.of(fn(this.__val));
}
function addTwo(x){
    return x+2;
}
function Ap(val){
    Functor.call(this,val);
}
Ap.of=function(val){
    return new Ap(val);
}
var __proto=Object.create(Functor.prototype);
__proto.constructor=Ap.prototype.constructor;
Ap.prototype=__proto;
Ap.prototype.ap=function(F){
    return Ap.of(this.__val(F.__val));
}

const A=Functor.of(2);
const B=Ap.of(addTwo);
console.log(B.ap(A));  //4
//console.log(B.ap(A).ap(A));此时会报错,B.ap(A)其中A不是个函数

拖延症了好久,第二篇终于写出来了。

Monad

Monad就是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤。你只要提供下一步运算所需的函数,整个运算就会自动进行下去。

Promise就是一种Monad

Monad糖我们避开了嵌套地狱,可以轻松地进行深度嵌套的函数式编程,比如IO和其它异步任务

Maybe.of(
    Maybe.of(
        Maybe.of({name:'Mulburry',number:99})
    )
)
class Monad extends Functor{
    join(){
        return this.val;
    }
    flatMap(f){
        return this.map(f).join();
    }
}

Monad
函子的作用是,总是返回一个单层的函子。它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,他会取出后这内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

如果函数f返回的是一个函子,那么this.map(f)就会生成一个嵌套的函子。所以,join方法保证了flatMap方法总是返回一个单层的函子。这意味着嵌套的汉子会被铺平(flatMap)

四、总结

如果你还能坚持看到这里的话,不管看没看懂,已经是勇士了。在这篇文章里,我们先后提到了
MaybeEitherIO 这三种强大的
Functor,在链式调用、惰性求值、错误捕获、输入输出中都发挥着巨大的作用。事实上
Functor
远不止这三种,但由于篇幅的问题就不再继续介绍了(哼才不告诉你其实是因为我还没看懂其它
Functor 的原理)

但依然有问题困扰着我们:

  1. 如何处理嵌套的 Functor澳门新葡萄京官网首页, 呢?(比如 Maybe(IO(42))

  2. 如何处理一个由非纯的或者异步的操作序列呢?

在这个充满了容器和 Functor
的世界里,我们手上的工具还不够多,函数式编程的学习还远远没有结束,在下一篇文章里会讲到
Monad
这个神奇的东西(然而我也不知道啥时候写下一篇,估计等到实习考核后吧OvO)。

  1. 函数是第一等公民
  2. 只用表达式,不用语句
  3. 没有副作用
  4. 不修改状态
  5. 引用透明(函数运行只靠参数)

一、容器、Functor

如果你熟悉 jQuery 的话,应该还记得,$(…) 返回的对象并不是一个原生的
DOM 对象,而是对于原生对象的一种封装:

var foo = $('#foo'); 
foo == document.getElementById('foo'); 
//=> false

foo[0] == document.getElementById('foo'); 
//=> true

这在某种意义上就是一个“容器”(但它并不函数式)。

接下类我们会看到,容器为函数式编程里普通的变量、对象、函数提供了一层极其强大的外衣,赋予了它们一些很惊艳的特性,就好像
Tony Stark 的钢铁外衣,Dva 的机甲,明日香的2号机一样。

下面我们就来写一个最简单的容器吧:

var Container = function(x) {
  this.__value = x;
}
Container.of = x => new Container(x);

//试试看
Container.of(1);
//=> Container(1)

Container.of('abcd');
//=> Container('abcd')

我们调用 Container.of 把东西装进容器里之后,由于这一层外壳的阻挡,普通的函数就对他们不再起作用了,所以我们需要加一个接口来让外部的函数也能作用到容器里面的值:

Container.prototype.map = function(f){
  return Container.of(f(this.__value))
}

我们可以这样使用它:

Container.of(3)
    .map(x => x + 1)                //=> Container(4)
    .map(x => 'Result is ' + x);    //=> Container('Result is 4')

没错!我们仅花了 7
行代码就实现了很炫的『链式调用』,这也是我们的第一个 Functor

Functor(函子)是实现了 map 并遵守一些特定规则的容器类型。

也就是说,如果我们要将普通函数应用到一个被容器包裹的值,那么我们首先需要定义一个叫
Functor 的数据类型,在这个数据类型中需要定义如何使用 map
来应用这个普通函数。

把东西装进一个容器,只留出一个接口 map
给容器外的函数,这么做有什么好处呢?

本质上,Functor
是一个对于函数调用的抽象,我们赋予容器自己去调用函数的能力。当 map
一个函数时,我们让容器自己来运行这个函数,这样容器就可以自由地选择何时何地如何操作这个函数,以致于拥有惰性求值、错误处理、异步调用等等非常牛掰的特性。

举个例子,我们现在为 map
函数添加一个检查空值的特性,这个新的容器我们称之为
Maybe(原型来自于Haskell):

var Maybe = function(x) {
  this.__value = x;
}

Maybe.of = function(x) {
  return new Maybe(x);
}

Maybe.prototype.map = function(f) {
  return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}

Maybe.prototype.isNothing = function() {
  return (this.__value === null || this.__value === undefined);
}

//试试看
import _ from 'lodash';
var add = _.curry(_.add);

Maybe.of({name: "Stark"})
    .map(_.prop("age"))
    .map(add(10));
//=> Maybe(null)

Maybe.of({name: "Stark", age: 21})
    .map(_.prop("age"))
    .map(add(10));
//=> Maybe(31)

看了这些代码,觉得链式调用总是要输入一堆 .map(…) 很烦对吧?这个问题很好解决,还记得我们上一篇文章里介绍的柯里化吗?

有了柯里化这个强大的工具,我们可以这样写:

import _ from 'lodash';
var compose = _.flowRight;
var add = _.curry(_.add);

// 创造一个柯里化的 map
var map = _.curry((f, functor) => functor.map(f));

var doEverything = map(compose(add(10), _.property("age")));

var functor = Maybe.of({name: "Stark", age: 21});
doEverything(functor);
//=> Maybe(31)

Either 函子

条件运算if…else
是常见的运算之一,函数式编程里面,使用Either函子表达。Either函子内部有两个值:左值(left)和右值(right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。

class Either extends Functor{
    constructor(left,right){
        this.left=left;
        this.right=right;
    }
    map(f){
        //右值存在变右值,否则变左值
        return this.right?Either.of(this.left,f(this.right)):Either.of(f(this.left),this.right);
    }
}
Either.of=function(left,right){
    return new Either(left,right);
}

var addOne=function(x){
    return x+1;
}
Either.of(5,6).map(addOne); //Either(5,7);
Either.of(1,null).map(addOne);  //Either(2);
Either
    //右值中有address这个属性,则覆盖原来的xxx,否则使用默认的xxx
    .of({address:'xxx'},currentUser.address)    .map(updateField);

es5写法

错误处理、Either

var Left=function(x){
    this.__value=x;
}
var Rigth=function(x){
    this.__value=x;
}
Left.of=function(x){
    return new Left(x);
}
Right.of=function(x){
    return new Right(x);
}
Left.prototype.map=functin(f){
    return this;
}
Right.prototype.map=function(f){
    return Right.of(f(this.__value));
}

Left和Right唯一的区别就在于map方法的实现,Right.map的行为和我们之前提到的map函数一样。但是Left.map就很不同了:它不会对容器做任何事情,只是很简单地把这个容器拿进来又扔出去。这个特性意味着,Left可以用来传递一个错误消息。

var getAge = user => user.age ? Right.of(user.age):Left.of("Error");;
getAge({name:'stark',age:'21'}).map(age=>'Age is '+age);
//Right('Age is 21');
getAge({name:'stark'}).map({age=>'Age is '+age});
//Left('Error');

Left
可以让调用链中任意一环的错误立即返回到调用链的尾部,这给我们错误处理带来了很大的方便,再也不用一层又一层的Try/catch

但是实际的编程中,特别是前端的编程范畴里,“不依赖外部环境”这个条件是根本不可能的,我们总是不可避免地接触到
DOM、AJAX
这些状态随时都在变化的东西。所以我们需要用更强大的技术来干这些脏活。

范畴论Category Theory

上一篇文章里我们提到了纯函数的概念,所谓的纯函数就是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态(我偷懒复制过来的)。

函数的柯里化

传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

用柯里化来改造上面的不纯函数

var checkage=min=>(age=>age>min);
var checkage18=checkage(18);
checkage18(20);

五、参考

1、https://github.com/MostlyAdequate/mostly-adequate-guide

2、http://www.ibm.com/developerworks/cn/web/1006_qiujt_jsfunctional/

3、《JavaScript函数式编程》【美】迈克尔·佛格斯

专业术语

JavaScript函数式编程(一)

高阶函数

函数当参数,把传入的函数做一个封装,然后返回这个封装函数,达到更高程度的抽象

//命令式
var add=function(a,b){
    return a+b;
};
funtion math(func,array){
    return func(array[0],array[1]);
}
math(add,[1,2]); //3

二、错误处理、Either

现在我们的容器能做的事情太少了,它甚至连做简单的错误处理都做不到,现在我们只能类似这样处理错误:

try{
    doSomething();
}catch(e){
    // 错误处理
}

try/catch/throw 并不是“纯”的,因为它从外部接管了我们的函数,并且在这个函数出错时抛弃了它的返回值。这不是我们期望的函数式的行为。

如果你对 Promise 熟悉的话应该还记得,Promise 是可以调用 catch 来集中处理错误的:

doSomething()
    .then(async1)
    .then(async2)
    .catch(e => console.log(e));

对于函数式编程我们也可以做同样的操作,如果运行正确,那么就返回正确的结果;如果错误,就返回一个用于描述错误的结果。这个概念在
Haskell 中称之为 Either 类,Left 和 Right是它的两个子类。我们用 JS
来实现一下:

// 这里是一样的=。=
var Left = function(x) {
  this.__value = x;
}
var Right = function(x) {
  this.__value = x;
}

// 这里也是一样的=。=
Left.of = function(x) {
  return new Left(x);
}
Right.of = function(x) {
  return new Right(x);
}

// 这里不同!!!
Left.prototype.map = function(f) {
  return this;
}
Right.prototype.map = function(f) {
  return Right.of(f(this.__value));
}

下面来看看 Left 和 Right 的区别吧:

Right.of("Hello").map(str => str + " World!");
// Right("Hello World!")

Left.of("Hello").map(str => str + " World!");
// Left("Hello")

LeftRight 唯一的区别就在于 map 方法的实现,Right.map
的行为和我们之前提到的 map 函数一样。但是 Left.map
就很不同了:它不会对容器做任何事情,只是很简单地把这个容器拿进来又扔出去。这个特性意味着,Left
可以用来传递一个错误消息。

var getAge = user => user.age ? Right.of(user.age) : Left.of("ERROR!");

//试试
getAge({name: 'stark', age: '21'}).map(age => 'Age is ' + age);
//=> Right('Age is 21')

getAge({name: 'stark'}).map(age => 'Age is ' + age);
//=> Left('ERROR!')

是的,Left
可以让调用链中任意一环的错误立刻返回到调用链的尾部,这给我们错误处理带来了很大的方便,再也不用一层又一层的
try/catch

LeftRightEither 类的两个子类,事实上 Either
并不只是用来做错误处理的,它表示了逻辑或,范畴学里的
coproduct。但这些超出了我们的讨论范围。

纯函数

对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。

var xs=[1,2,3,4,5];
//Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的
xs.slice(0,3); //[1,2,3]
xs.slice(0,3);  //[1,2,3]
xs.splice(0,3);   //[1,2,3]
xs.splice(0,3);  //[4,5]

import _ from 'lodash';
var sin=_.memorize(x=>Math.sin(x));
var a=sin(1); //第一次计算的时候会稍慢一点
var b=sin(1); //第二次有了缓存,速度极快
//纯函数不仅可以有效降低系统的复杂度,还有很多很棒的特性,比如可缓存性
//惰性函数

不纯

var min=18;
var checkage=function(age){
    return age>min; //依赖于外部的min,导致不纯
}

惰性求值

function fn(){
    if(IE){//IE时
        fn=a;
    }else{//chrome时
        fn=b;
    }
    return fn;
}

第一次执行时会走if,然后fn重新赋值,第二次执行fn时,直接赋值不用判断,提高执行效率

1.oop(面向对象编程);

2.aop(面向切面编程);

3.函数式编程(JavaScript Functional Programming);

现在大公司的编程方式有:

Point Free

  1. 把一些对象自带的方法转化成纯函数,不要命名转瞬即逝的中间变量
  2. 这个函数中,我们使用了str作为我们的中间变量,但这个中间变量除了让代码变得长了一点以外是毫无意义的
    const f=str=>str.toUpperCase().split('')

应用

var toUpperCase=word=>word.toUpperCase();
var split=x=>(str=>str.split(x));
var f=compose(split('').toUppercase);
f("abcd efgh");

这种风格能够帮助我们减少不必要的命名,让代码保持简洁和通用

  1. 纯函数
  2. 函数的柯里化
  3. 函数组合
  4. Point Free
  5. 声明式命令式代码
  6. 惰性求值

范畴与容器

  1. 我们可以把“范畴”想象成是一个容器,里面包含两样东西。值(value)、值的变形关系,也就是函数。
  2. 范畴论使用函数,表达范畴之间的关系。
  3. 伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今天的“函数式编程”。
  4. 本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式式同一类东西,都是数学方法,只是碰巧他能用来写程序。为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。

函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

  1. 函数式编程是范畴论的数学分支是一门很复杂的数学,认为世界上所有概念体系都可以抽象出一个个范畴
  2. 彼此之间存在某种关系概念、事物、对象等等,都构成范畴。任何事物只要找出他们之间的关系,就能定义
  3. 箭头表示范畴成员之间的关系,正式的名称叫做“态射”(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的“变形”(transformation)。通过“态射”,一个成员可以变形成另一个成员

函数式编程5大特点

Maybe 函子

函子接受各种函数,处理容器内部的值,这里就有一个问题,容器内部的值可能是一个空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

Functor.of(null).map(function(s){
    return s.toUpperCase();
});
//TypeError
class Maybe extends Functor{
    map(f){
        return this.val?Maybe.of(f(this.val)):Maybe.of(null);
    }
}
Maybe.of(null).map(function(s){
    return s.toUpperCase();
});
//Maybe(null) //报错,未定义

var Maybe=function(x){
    this.__value=x;
}
Maybe.of=function(x){
    return new Maybe(x);
}
Maybe.prototype.map=function(f){
    return this.isNothing()?Maybe.of(null):Maybe.of(f(this.__value));
}
Maybe.prototype.isNothing=function(){
    return (this.__value===null||this.__value===undefined);
}
Maybe(null) //不会报错了
//新的容器我们称为Maybe

容器、Functor(函子)

  1. $(…)返回的对象并不是一个原生的DOM对象,而是对于原生对象的一种封装,这在某种意义上就是一个”容器”(但它并不函数式)
  2. Functor(函子)遵守一些特定规则的容器类型
  3. Functor是一个对于函数调用的抽象,我们赋予容器自己去调用函数的能力。把东西装进一个容器,只留出一个接口map给容器外的函数,map一个函数时,我们让容器自己来运行这个函数,这样容器就可以自由地选择何时何地如何操作这个函数,以致于拥有惰性求值、错误处理、一步调用等非常牛掰的特性

var Container=function(x){
    this.__value=x;
}
//函数式编程一般约定,函子有一个of方法
Container.of=x=>new Container(x);
//Container.of('abcd);
//一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
Container.prototype.map=function(f){
    return Container.of(f(this.__value));
}
Container.of(3)
    .map(x=>x+1)                //Container(4)
    .map(x=>'Result is '+x);        //Container('Result is 4')

闭包

自己领会

尾调用优化

指函数内部的最后一个动作是函数调用。该调用的返回值,直接返回给函数。函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归需要保存大量的调用记录,很容易发生栈溢出错误,如果使用尾递归优化,将递归变为循环,那么只需要保存一个调用记录,这样就不会发生栈溢出错误了。

//不是尾递归,无法优化
function factorial(n){
    if(n===1) return 1;
    return n*factorial(n-1);
}
//尾递归
function factorial(n,total){
    if(n===1) return total;
    return factorial(n-1,n*total);
}//ES6强制使用尾递归

普通递归时,内存需要记录调用的堆栈所出的深度和位置信息。在最低层计算返回值,再根据记录的信息,跳会上一层级计算,然后再跳回到更高一层,依次运行,直到最外层的调用函数。在cpu计算和内存会消耗很多,而且当深度过大时,会出现堆栈溢出

function sum(x){
    if(x===1) return 1;
    return x+sum(x-1);
}
sum(5);  //15  递归

function sum(x,total){
    if(x===1) return x+total;
    return sum(x-1,x+total);
}
sum(5,0); 
sum(4,5);
sum(3,9);
sum(2,12);
sum(1,14);
15 //尾递归,每次执行之后,函数重新传入参数,直到结束

整个计算过程是线性的,调用一次sum(x,total)后,会进入下一个栈,相关的数据信息和跟随进入,不再放在堆栈上保存。当计算完最后的值之后,直接返回到最上层的sum(5,0).这能有效的防止堆栈溢出。
在ECMAScript6,我们将迎来尾递归优化,通过尾递归优化,javascript代码在解释成机器码的时候,将会向while看起,也就是说,同时拥有数学表达能力和while的效能。

函数式编程比较火热的库

  • Rxjs //截流与仿抖
  • cyclejs
  • lodashjs
  • underscorejs //开始学最佳的库
  • ramadajs

需要学习

声明式与命令式代码

命令式代码的意思就是,我们通过编写一条又一条指令去让计算机执行一些动作,这其中一般都会涉及到很多繁杂的细节。而声明式就要优雅很多了,我们通过写表达式的方式来声明我们想干什么,而不是通过一步一步的指示。

//命令式
let CEOs=[];
for(var i=0;i<companies.length;i++){
    CEOs.push(companies[i].CEO);
}
//声明式
let CEOs=companies.map(c=>c.CEO);

优缺点

函数式编程的一个明显的好处就是这种声明式的代码,对于无副作用的纯函数,我们完全可以不考虑函数内部是如何实现的,专注于编写业务代码。优化代码时,目光只需要集中在这些稳定坚固的函数内部即可。

相反,不纯的函数式的代码会产生副作用或者依赖外部系统环境,使用他们的时候总是要考虑这些不干净的副作用。在复杂的系统中,这对于程序员的心智来说是极大的负担。

发表评论

电子邮件地址不会被公开。 必填项已用*标注