函数式编程是React的精髓,在正式讲解React之前,有必要先了解一下函数式编程,有助于更好的理解React的特点。函数式编程(Functional Programming)不是一种新的框架或工具,而是一种以函数为主的编程范式。编程范式也叫编程范型,是一类编程风格,除了函数式编程,常用的还有面向对象编程、命令式编程等。

一、声明式编程

  声明时编程也是一种范式,但它是一个比较大的概念,函数式编程是它的一个子集。声明式编程能指定每一步操作,而不用向计算机描述具体的实现细节。与之相对立的是命令式编程,它会命令计算机每一步该怎么做。以数组的元素翻倍为例,先用命令式编程实现,如下所示。

var arr = [1, 2, 3],
length = arr.length,
doubles = [];
for (let i = 0; i < length; i++) {
doubles.push(arr[i] * 2);
}

  在命令式的代码中,先用for循环遍历整个数组,然后让每个元素乘以二,再将计算结果插入到doubles数组中,直至将所有的元素计算完才终止整套操作。改用声明式编程可以像下面这样实现相同的功能。

var doubles = [1, 2, 3].map(value => value * 2);

  在声明式的代码中,用map()方法替代了循环语句(即不指明流程的控制方式),既不用再维护计数器,也不用再通过索引访问数组的元素,配合ES6的箭头函数让整套操作变得非常简洁。

  除了这些表面区别之外,还有个最本质的区别,那就是声明式编程会避免用变量保存程序的状态,从而能提高代码的无状态性。在命令式的代码中,每次迭代都会修改doubles变量,这是个状态变量,而在声明式的代码中,改用返回值保存程序的状态。

二、函数优先

  函数式编程强调在程序中使用函数。由于JavaScript中的函数是一等公民,它既可以是变量的值,也可以作为另一函数的参数或返回值,因此通过函数可构建一层抽象以替代流程控制或解决复杂的逻辑操作。例如对数组中的数字进行排序和过滤,可以像下面这样运用函数式编程的思想实现。

[4, 1, 5, 2, 3].sort((a, b) => a > b).filter(value => value > 2);        //[3, 4, 5]

  函数式编程旨在将复杂的运算分解成一系列嵌套的函数,逐层推导,不断渐进,直至完成运算。

三、纯函数

  纯函数(Pure Function)是一种没有副作用、引用透明的函数,它是函数式编程的基本概念,接下来会重点讲解它的三个特征。

1)函数的副作用

  函数在读写外部资源或执行不确定的操作时就会产生副作用,例如修改函数外的变量、调用Date.now()或Math.random()、更新cookie信息等。副作用不仅会降低程序整体的可读性,有时候还会带来意料之外、难以排查的错误,下面是一个副作用的例子。

var digit = 1;
function increment() {
digit += Math.random();
return digit;
}

  在上面的代码中,increment()函数产生了副作用,因为每次调用它都会更新外部的digit变量,并且每次得到的计算结果也无法预知。

2)引用透明

  如果传递给函数相同的参数,始终能得到相同的结果,那么就能说这个函数是引用透明(Referential Transparent)的。简单的说就是,函数的运行只受其输入值的影响,如下代码所示,传递给add()函数固定的参数会返回固定的值。

function add(a, b) {
return a + b;
}

3)参数值不可变

  传递给纯函数的参数值是不允许在内部将其改变的,换句话说,在函数内部使用的是参数值的副本。如果参数值是基本类型的,那么传递给函数的就是其副本;但如果参数值是对象类型的,那么需要注意,传递给函数的是引用对象的指针。

  下面用一个示例说明,addDigit()函数的参数是一个数组,它的功能是为数组的每个元素加一,在执行addDigit(digits)之后,由于digits变量是一个数组,因此它的元素会随着函数的调用而被改变。

var digits = [1, 2, 3];
function addDigit(arr) {
for (let i = 0, len = arr.length; i < len; i++) {
arr[i] += 1;
}
return arr;
}
addDigit(digits);
console.log(digits); //[2, 3, 4]

  接下来修改addDigit()函数,使之能满足纯函数的要求,如下所示。

var digits = [1, 2, 3];
function addDigit(arr) {
return arr.map(value => value + 1);
}
addDigit(digits);
console.log(digits); //[1, 2, 3]

  在addDigit()函数内部,用map()方法替代for循环,使得在不改变参数的前提下,完成元素加一的功能。

四、优点

  函数式编程有许多优点,本节只列出了其中的两点。

  (1)函数式编程可将复杂的任务分解成一个个既简单又独立的纯函数,有利于提高代码的模块化、复用性、预测性以及可测试性。

  (2)函数式编程有很高的自由度,可以采用更符合人类思维习惯的链式写法,以此提高代码的可读性。

  接下来会用两种函数式的写法操作一个数组,为了便于演示省略了函数的具体实现,首先是普通的函数式写法,如下所示。

elementDouble(filterEven(arr, filterFn), doubleFn);

  两个函数都有两个参数,第一个是数组,第二个是相应的回调函数。具体的执行过程是先通过filterEven()函数过滤掉数组中偶数位置的元素,再用elementDouble()函数把每个元素翻倍,下面改成链式的写法。

filerEven(arr, filterFn).elementDouble(arr, doubleFn);

  通过两段代码的对比可以看出,链式的写法更容易让人理解,代码意图也更清晰。

React躬行记(1)——函数式编程的更多相关文章

  1. React躬行记(10)——高阶组件

    高阶组件(High Order Component,简称HOC)不是一个真的组件,而是一个没有副作用的纯函数,以组件作为参数,返回一个功能增强的新组件,在很多第三方库(例如Redux.Relay等)中 ...

  2. React躬行记(11)——Redux基础

    Redux是一个可预测的状态容器,不但融合了函数式编程思想,还严格遵循了单向数据流的理念.Redux继承了Flux的架构思想,并在此基础上进行了精简.优化和扩展,力求用最少的API完成最主要的功能,它 ...

  3. React躬行记(12)——Redux中间件

    Redux的中间件(Middleware)遵循了即插即用的设计思想,出现在Action到达Reducer之前(如图10所示)的位置.中间件是一个固定模式的独立函数,当把多个中间件像管道那样串联在一起时 ...

  4. React躬行记(8)——样式

    由于React推崇组件模式,因此会要求HTML.CSS和JavaScript混合在一起,虽然这与过去的关注点分离正好相反,但是更有利于组件之间的隔离.React已将HTML用JSX封装,而对CSS只进 ...

  5. React躬行记(5)——React和DOM

    React实现了一套与浏览器无关的DOM系统,包括元素渲染.节点查询.事件处理等机制. 一.ReactDOM 自React v0.14开始,官方将与DOM相关的操作从React中剥离,组成单独的rea ...

  6. React躬行记(3)——组件

    组件(Component)由若干个React元素组成,包含属性.状态和生命周期等部分,满足独立.可复用.高内聚和低耦合等设计原则,每个React应用程序都是由一个个的组件搭建而成,即组成React应用 ...

  7. React躬行记(2)——JSX

    JSX既不是字符串,也不是HTML,而是一种类似XML,用于描述用户界面的JavaScript扩展语法,如下代码所示.在使用JSX时,为了避免自动插入分号时出现问题,推荐在其最外层用圆括号包裹,并且必 ...

  8. React躬行记(4)——生命周期

    组件的生命周期(Life Cycle)包含三个阶段:挂载(Mounting).更新(Updating)和卸载(Unmounting),在每个阶段都会有相应的回调方法(也叫钩子)可供选择,从而能更好的控 ...

  9. React躬行记(6)——事件

    React在原生事件的基础上,重新设计了一套跨浏览器的合成事件(SyntheticEvent),在事件传播.注册方式.事件对象等多个方面都做了特别的处理. 一.注册事件 合成事件采用声明式的注册方式, ...

随机推荐

  1. Caffe源码-Solver类

    Solver类简介 Net类中实现了网络的前向/反向计算和参数更新,而Solver类中则是对此进行进一步封装,包含可用于逐次训练网络的Step()函数,和用于求解网络的优化解的Solve()函数,同时 ...

  2. springIOC及设计模式

    一.IOC的概念: 控制反转(inversion of control)和依赖注入(dependency injection)其实是同一个概念.当某个方法需要另外一个对象协助的时候,传统的方法就是有调 ...

  3. 6. abp中的拦截器

    abp拦截器基本定义 拦截器接口定义: public interface IAbpInterceptor { void Intercept(IAbpMethodInvocation invocatio ...

  4. 集合框架关于<list接口><map接口>的运用

    集合: 集合就是一个容器,他可以存储对象,我们说集合就是一个可变的数组 集合框架特点 1.list和set集合同时实现了collection接口 2.set集合存储唯一,无序的对象. 3.list 存 ...

  5. 【前端学习】网页tab键的实现 01

    友情提醒:阅读本文需要了解一些基本的html/Css/Javascript知识 前端常用tab键的实现,用到的原理是当点击一个元素时,通过javascript操作css的display属性,达到控制另 ...

  6. Crow’s Foot Notation

    http://www2.cs.uregina.ca/~bernatja/crowsfoot.html Crow’s Foot Notation A number of data modeling te ...

  7. CODING 代码多仓库实践

    关于代码的管理问题已经讨论多年,随着企业业务的复杂度提高.软件行业技术栈的选择度变宽泛,现代软件的代码仓库也变得越来越庞大和复杂.一个中型项目,将测试代码.核心业务代码.编译构建.部署打包等基础设施的 ...

  8. phper使用MySQL 针对千万级的大表要怎么优化?

    有需要学习交流的友人请加入交流群的咱们一起,群内都是1-7年的开发者,希望可以一起交流,探讨PHP,swoole这块的技术 或者有其他问题 也可以问,获取swoole或者php进阶相关资料私聊管理即可 ...

  9. Linux ipv6 无状态 设置为 eui64

    Linux ipv6 无状态 设置为 eui64 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-08-22. 无状态的ipv6有eui64和stable-privacy模式, 在家 ...

  10. Jmeter 查看结果树之界面功能介绍 [8]

    查看结果树显示所有请求响应的树,通过它可以查看任何请求的响应.除了显示响应之外,还可以查看获取响应所花费的时间以及一些响应代码.需要通过"查看结果树"来查看服务器处理请求之后的返回 ...