函数式编程基本概念
写在之前, 这些内容参考自 O`REILLY 系列图书React 学习手册
在 React 中, UI 是用纯函数表示的, 并且在构造 DOM 时, 是以声明式 (与此相对的, 是命令式) 的方式. 而声明式的编程是函数式编程更广义的一部分. 所以, 先熟悉函数式的编程, 很有必要.
结合了如下所述, 我发现了 es6 中数组的新语法, 像 map,filter,reduce 这些, 都满足了不可变性, 数据转换, 纯函数, 高阶函数等要点, 很精髓.
1, 不可变性
数据在应用程序中, 在不改变原生数据结构的前提下, 在此基础上拷贝后进行编辑.
在此基础上, 就会有浅拷贝和深拷贝的选择.
比如, 在原有对象上添加属性, 而不影响原对象
- let person = {
- name: 'Beckham',
- age: 33
- }
- let player = (person, sex) => ({
- ...person,
- sex
- })
- console.log(person)
- console.log(player(person, 'male'))
2, 纯函数
结果只依赖输入参数
函数至少接收一个参数
返回一个值或其他函数
不应该修改或影响传递给它的参数
不会产生副作用, 比如修改了全局变量, 影响了程序的状态
3, 数据转换
3.1, 定义
从其他数据源创建一个新的数据集.
3.2, 作用
为了使用转换后的副本
所以, map,filter,reduce 这些函数都是必不可少的.
基本用法
对象转为数组
- let country = {
- "beijing": 10,
- "shanghai": 6,
- "shenzhen": 9
- }
- let obj = Object.keys(country).map(key =>
- ({
- name: key,
- value: country[key]
- })
- )
- console.log(obj)
数组中, 寻找最大值
- let ages = [11,66,33,22,11,55]
- let maxAge = ages.reduce((max, age) => {
- if (max> age) {
- return max
- } else {
- return age
- }
- }, 0)
- console.log(maxAge)
数组去重, 思路: 有的话不变, 没有的话添加.
- let colors = ['red','green','blue','red','green','blue']
- const distinctColor = colors.reduce((distinct, color) => (
- (distinct.indexOf(color) !== -1) ? distinct : [...distinct, color]
- ),
- []
- )
- console.log(distinctColor)
4, 高阶函数
定义: 将函数作为参数传递, 或返回一个函数
所以, 数组的 map,filter,reduce 都是高阶函数
5, 递归
目的: 在涉及到循环时, 递归可提供一种替代性的方案
在浏览器的调用堆栈中, 会依次放入当前执行的函数, 在出栈时, 后进先出.
打印输出 10~0
- let countDown = (count, fn) => {
- fn(count)
- return (count> 0) ? countDown(count-1, fn) : count
- }
- countDown(10, count => console.log(count))
倒计时输出 10~0
- let countDown = (count, fn, delay = 1000) => {
- fn(count)
- return (count> 0) ? setTimeout(() => countDown(count-1, fn), delay) : count
- }
- countDown(10, count => console.log(count))
6, 合成
6.1 定义
将具体的业务逻辑拆解为小型纯函数, 用户会在特定条件下合成它们, 以串联或并联的方式进行调用.
6.2 目标
通过整合若干简单函数, 构造一个更高阶的函数
链式调用, 就是合成技术之一
- let template = 'hh:mm:ss tt'
- let clockTime = template.replace('hh','09')
- .replace('mm','06')
- .replace('ss', '52')
- .replace('tt', 'PM')
- console.log(clockTime) // 09:06:52 PM
7, 综上应用
获取当前时间, 实现实时时钟.
- // 计时器时间
- const oneSecond = () => 1000
- // 获取当前时间
- const getCurrentTime = () => new Date()
- // 清除控制台输出
- const clear = () => console.clear()
- // 控制台打印内容
- const log = message => console.log(message)
- // 构造一个时钟对象, 包含时分秒
- const abstractClockTime = date =>
- ({
- hours: date.getHours(),
- minutes: date.getMinutes(),
- seconds: date.getSeconds()
- })
- // 接收一个时钟对象,
- // 该方法用于指定 12 小时制
- const civilianHours = clockTime =>
- ({
- ...clockTime,
- hours: (clockTime.hours> 12) ? clockTime.hours - 12 : clockTime.hours
- })
- // 接收一个时钟对象
- // 该方法用于, 为时钟对象添加一个属性, 用于表示 am 或 pm
- const appendAMPM = clockTime =>
- ({
- ...clockTime,
- ampm: (clockTime.hours>= 12) ? "PM" : "AM"
- })
- /*
- * @target 目标函数(本例中就是 log 函数)
- * @time 时钟对象
- * 返回的函数, 会将时间发送给目标函数
- * */
- const display = target => time => target(time)
- /*
- * @format 模板字符串 "hh:mm:ss:tt"
- * @time 时钟对象
- * 返回的函数, 将时间进行格式化
- * */
- const formatClock = format =>
- time =>
- format.replace("hh", time.hours)
- .replace("mm", time.minutes)
- .replace("ss", time.seconds)
- .replace("tt", time.ampm)
- /*
- * @key 时钟对象的属性
- * @clockTime 时钟对象
- * 返回的函数, 将时钟对象的属性, 包括时分秒, 当 < 10 时, 加 0
- * */
- const prependZero = key => clockTime =>
- ({
- ...clockTime,
- [key]: (clockTime[key] <10) ? "0" + clockTime[key] : clockTime[key]
- })
- /*
- * 接收参数为多个函数, 返回一个独立的函数
- * compose 函数被调用, 返回的独立函数进行调用时, 如果不传参,
- * 就以 arg 作为 reduce 的起始值, arg 在使用时是 undefined,
- * 而一个函数在定义时, 如果没有设置形参, 该函数在调用时, 传递的参数无效.
- * 在这个栗子中,...fns 为多个函数组成的数组.
- *
- * 也就是说, arg 作为第一个函数的参数, 如果该函数定义时没有指定形参, arg 将被忽略,
- * 第一个函数执行的结果, 作为第二个函数执行的参数, 依次类推.
- * */
- const compose = (...fns) =>
- (arg) =>
- fns.reduce(
- (composed, f) => f(composed),
- arg
- )
- const convertToCivilianTime = clockTime =>
- compose(
- appendAMPM,
- civilianHours
- )(clockTime)
- const doubleDigits = civilianTime =>
- compose(
- prependZero("hours"),
- prependZero("minutes"),
- prependZero("seconds")
- )(civilianTime)
- /*
- * compose 已经被调用了, 之后每隔 1s, 调用一次 compose 的执行结果
- * 注意参与合成的函数的顺序.
- *
- * 清除打印台, 获取时间, 构造时钟对象, 添加 am 或 pm,12 小时制, 加 0, 格式化时分秒, 发送给打印函数
- * */
- const startTicking = () =>
- setInterval(
- compose(
- clear,
- getCurrentTime,
- abstractClockTime,
- convertToCivilianTime,
- doubleDigits,
- formatClock("hh:mm:ss tt"),
- display(log)
- ),
- oneSecond()
- )
- startTicking()
来源: https://juejin.im/post/5b30f134e51d4558ca6733d7