函数式编程基本概念
写在之前,这些内容参考自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
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。