本文最后更新于 2024-03-22T23:32:32+00:00
函数式编程:鼓励使用纯函数(Pure Functions),即对于相同的输入,始终产生相同的输出,并且没有副作用(没有改变外部状态的行为)
发展历程
命令式 => 面向对象 => 面向函数
面试题:
将数组:[‘process&%coding’, ‘object&%coding’, ‘function&%coding’]
转为 JSON:[{ name: ‘Process Coding’}, { name: ‘Object Coding’}, { name: ‘Function Coding’}]
命令式
代码举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const arr = ["process&%coding", "object&%coding", "function&%coding"]; const parseArr = []; const Parser = (arr, parseArr) => { arr.map((item) => { const newItem = []; item.split("&%").map((_item) => { newItem.push(_item[0].toUpperCase() + _item.slice(1)); }); parseArr.push({ name: newItem.join(" ") }); }); };
Parser(arr, parseArr);
|
对象式
代码举例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Parser = { toJson(arr, parseArr) => { arr.map((item) => { const newItem = []; item.split("&%").map((_item) => { newItem.push(_item[0].toUpperCase() + _item.slice(1)); }); parseArr.push({ name: newItem.join(" ") }); }); }; }
Parser.toJson(arr, parseArr);
|
函数式
代码举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
const arr = ["process&%coding", "object&%coding", "function&%coding"];
const createObj = (key, anyValue) => { const obj = { [key]: anyValue } return obj }
const strSplit = (str, splitKey) => str.split(splitKey)
const capitalize = str => { return str[0].toUpperCase() + str.slice(1) }
const stringFormat = str => { return strSplit(str, '&%').map(item => capitalize(item)).join(' ') }
const objHelper = (str) => { return createObj('name', str) }
const arrToJSON = (arr) => { return arr.map(item => objHelper(stringFormat(item))) }
|
函数式编程的原理、特点
什么是函数式编程?
将复杂功能“因式分解”,然后使用“加法结合律”组装完成功能的思维形式
理论思想
函数是一等公民
每个原子功能,就是一个采用命令式编程的函数,并且属于惰性执行(需要的时候才会执行)
惰性执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function createPersonNoLazy() { console.log("[ createPersonNoLazy person is created ] >"); return { name: "xx", age: 30, }; }
const preson1 = createPersonNoLazy(); const preson2 = createPersonNoLazy();
function createPersonLazy() { console.log("[ createPersonLazy person is created ] >"); const person = { name: "xx", age: 30, };
createPersonLazy = () => { console.log("[ createPersonLazy person is existed ] >"); return person; };
return person; }
const person3 = createPersonLazy(); const person4 = createPersonLazy();
|
函数式编程的要求
无状态
指函数在执行时不依赖或修改外部状态。它的行为仅由输入参数决定,并且对于相同的输入,总是产生相同的输出,不受外部环境的影响。
无副作用
指函数在执行过程中不对外部环境产生可观察的影响,即不会对输入的[参数、外部变量]进行修改。
函数式编程的实际开发
纯函数改造
满足无状态 && 无副作用的函数就是纯函数
1 2 3 4 5 6 7 8 9 10 11
| const a = 1
const add = x => a + x
const add = (a, x) => a + x
const add = obj => obj.x++
const add = obj => { ...obj, x: obj.x + 1}
|
函子
定义:一个类/构造函数
,具有map
方法,每次调用map
会生成新的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const Box = function(val) { this.val = val; }
Box.of = function(val) { return new Box(val); } Box.prototype.map = function(func) { return this.isNull() ? Box.of(null) : Box.of(func(this.val)); }
Box.prototype.isNull = function() { return this.val === null || this.val === undefined
}
const add = x => x + 1 const square = x => x * x const setNull = () => null
Box.of(1).map(add).map(setNull).map(square).val === null
|
函子的作用:适合消除副作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Monad { constructor(value) { this.value = value; }
bind(transform) { return transform(this.value); }
value() { return this.value; } }
const readFile = function (filename) { const content = fs.readFileSync(filename, "utf-8"); return new Monad(content); };
const print = function (x) { console.log(x); return new Monad(x); };
const tail = function (x) { const lastLine = x.split('\n').pop(); return new Monad(lastLine); };
const monad = readFile('./xxx.txt').bind(tail).bind(print);
monad.value();
|
图解 Monad - 阮一峰的网络日志
加工 & 组装
加工 - 柯里化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const add1 = (x, y, z) => x + y + z add(1, 2, 3)
const add2 = (x) => { return y => { return z => x + y + z } } add2(1)(2)(3)
const toKLH = (fn) => { const KLH = (...arg) => { if (arg.length === fn.length) { return fn(...arg); } else { return (...arg2) => { return KLH(...arg.concat(arg2)); }; } };
return KLH; };
const add10 = toKLH(add1)(10) const add20 = toKLH(add1)(20)
add10(1)(3) add20(1)(3)
|
为什么需要柯里化,为了函数的输入输出单值化(单元函数,更利于组合),更加方便操作多值函数
组装 - 高阶函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const sum1 = x => x + 1 const sum2 = x => x + 2
const compose = (f, g) => { return x => { return f(g(x)) } } const sum1_2 = compose(sum1, sum2)(value)
sum2(sum1(value))()
valueInstance.sum1().sum2()
|
补充知识
高阶函数
定义:函数为参的函数
黑话:逻辑外壳
toString()、valueOf()
当转为字符串时,先调用 toString()
若返回的是基本类型,则直接调用 String()
否则再调用 valueOf(),若返回的是基本类型,则再调用 String(),否则就报错Uncaught TypeError: Cannot convert object to primitive value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| var obj={ "user":"张三", "toString":function () { console.log('1.执行了toString()方法'); return {}; },
"valueOf":function (){ console.log('2.执行了valueOf()方法'); return 12 } }
console.log(String(obj));
var obj={ "user":"张三", "toString":function () { console.log('1.执行了toString()方法'); return {}; },
"valueOf":function (){ console.log('2.执行了valueOf()方法'); return {} } }
console.log(String(obj));
|
面试题
如何使用正确的遍历
数组:for、find、findIndex、forEach、map、filter、reduce、sort、some、every
对象:for in
类数组:for
可遍历:for、for of
为什么数组有这么多的遍历方法?
- for:通用
- find:找到某个值
- findIndex:找到某个值的下标
- forEach:遍历进行逻辑处理
- map:生成新数组,顺带进行逻辑处理
- filter:过滤满足条件的值,并生成新数组
- reduce:累积
- sort:排序
- some:是否 >=1 个满足条件
- every:是否所有满足条件
本质逻辑是:满足函数式编程,让每个函数有自己应该做的事情
JS 里面的副作用函数有哪些?
- split:不会改变原数据
- slice:不会改变原数据
- splice:会改变原数据
- pop:会改变原数据
- push:会改变原数据
- shift:会改变原数据
- unshift:会改变原数据
- reverse:会改变原数据
- sort:会改变原数据
- …
柯里化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const add = (...args1) => { const inner = (...args2) => { if (args2.length) { args1.push(...args2); return inner; } else { return args1.reduce((total, curr) => (total += curr), 0); } };
return inner; }
add(1)() add(1)(2)() add(1)(2)(3)() add(1)(2)(3)(4)() add(1, 2, 3, 4)()
|
柯里化与闭包的关系
“孪生子”
闭包定义:返回函数的函数,其中内部函数使用了外部函数定义的变量,形成了闭包
柯里化定义:将多参数的函数转为接受单/部分参数的函数,并且返回接受剩余参数和返回结果的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const add10 = () => { const num = 10 return (x) => x + num } const addInit = add10() addInit(10)
const add1 = (x, y) => { return x + y } add1(10, 10)
const add2 = (x) => { return y => { return x + y } } add2(10)(10)
|
柯里化的运用
防抖、节流
防抖
定义:触发后,x 时间后才生效(每次触发都重新计时),适用于:onresize、输入框搜索等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const debounce = (fn, delay) => { delay = delay || 200 let timer = null return function() { clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, arguments) }, delay) } }
let count = 0 window.onresize = debounce((e) => { console.log('e.type', e.type) console.log('count', ++count) }, 500)
|
节流
定义:x 时间内只会触发一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const throttle = (fn, delay) => { delay = delay || 200; let timer = null; return function () { if (!timer) { timer = setTimeout(() => { fn.apply(this, arguments); timer = null; }, delay); } }; };
let count = 0 window.onresize = debounce((e) => { console.log('e.type', e.type) console.log('count', ++count) }, 1000)
|
缓存计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const calculateFn = (num)=>{ const startTime = new Date() for(let i=0;i<num;i++){ } const endTime = new Date() console.log(endTime - startTime) return "Calculate big numbers" }
calculateFn(10_000_000_000) calculateFn(10_000_000_000) calculateFn(10_000_000_000)
const caches = (fn) => { const cacheResult = {}; return function (num) { if (!cacheResult[num]) { cacheResult[num] = fn(num); } return cacheResult[num]; }; };
const calculateCacesFn = caches(calculateFn);
calculateCacesFn(10_000_000); calculateCacesFn(10_000_000); calculateCacesFn(20_000_000); calculateCacesFn(20_000_000);
|
实际业务
业务需求:某个函数调用 n 次后,不再调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const beforDo = (deNum, fn) => { let count = 0 let result return function() { if(count < deNum) { result = fn.apply(this, arguments) count++ } return result } } const fn = beforDo(3, (x) => console.log(x)) fn(1) fn(2) fn(3) fn(4)
|