函数式编程
编程范式
常见的编程范式有:面向过程(POP)、面向对象(OOP)、函数式编程(FP)
- 面向过程编程 - 分析出解决问题所需要的步骤,利用函数将步骤逐一实现出来,并依次调用。
- 面向对象编程 - 将解决问题分解成各个对象,每个对象表示的是事物在整个解决问题的步骤中的行为, 通过封装、继承、多态来演示事物事件的联系。
- 函数式编程 - 与面向过程类似,但存在数学概念中的映射关系。
高阶函数
- 函数作为参数传递给另一个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// forEach function forEach(array, fn) { for (let i = 0; i < array.length; i++) { fn(array[i], i) } } // filter function filter(array, fn) { let results = []; for( let i = 0; i < array.length; i++) { if(fn(array[i])) { results.push(array[i]) } } return results }
- 函数作为另一个函数的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//once function once(fn) { let flag = false; return function () { if(!flag) { flag = true; return fn.apply(this, arguments) } } } const pay = once(function (money) { console.log(money); }); pay(5); pay(6); pay(7); //执行输出:5
补充:apply、call、bind:
用来改变函数的this指向,且第一个参数都是this要指向的对象,区别在于以下:
- bind会返回一个新的函数,而apply、call是对函数的直接调用。
- apply的第二个参数是以数组的形式进行传参,而call是一个一个行进传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let p1 = { name: '张三', work: '前端开发', say: function (place) { console.log(`${this.name}在${place}担任${this.work}`); } } let p2 = { name: '李四', work: '后端开发' } p1.say.apply(p2, ['腾讯']);//李四在腾讯担任后端开发 p1.say.call(p2, '华为');//李四在华为担任后端开发 const say = p1.say.bind(p2); say('金蝶');//李四在金蝶担任后端开发
闭包
一个函数在执行完后,在该函数作用域下的内部成员会被标记清除。
闭包是将函数和其周围的状态的引用捆绑在一起,使得可以在另一个作用域下调用一个函数的内部函数并可以访问该函数的内部成员。
闭包的本质:函数在执行时会被放在一个执行栈上,等执行完成后会从执行栈中移除,但因为外部引用了堆上的的内部成员,因为被引用的内部成员不会被移除。
纯函数
相同的输入返回相同的输出,没有任何可观察的副作用。
- 如果函数依赖于外部的状态就无法保证相同的输出,所产生的变化就是副作用。
- lodash是纯函数的代表
- 若有硬编码,用柯里化解决
硬编码是将数据直接嵌入代码逻辑中,导致变量很难修改,不利于维护
好处:
- 可缓存
- 方便测试
- 并行环境 —— 纯函数不依赖于共享的内存数据,所以在并行环境下可任意执行纯函数。
lodash
- memoize函数
1
2
3
4
5
6
7
8
9
10
11
12const _ = require("lodash"); function getArea(r) { console.log("r", r); return Math.PI * r * r; } const getAreaWitnMemory = _.memoize(getArea); getAreaWitnMemory(5); getAreaWitnMemory(5); getAreaWitnMemory(5); // 输出只打印一次:r 5 // 结果返回:78.53981633974483
1
2
3
4
5
6
7
8
9
10// 模拟memoize函数 function memoize(fn) { let cache = {}; return function () { let args = JSON.stringify(arguments); cache[args] = cache[args] || fn.apply(fn, arguments); return cache[args] } }
- curry函数
1
2
3
4
5
6
7
8const _ = require("lodash"); function checkAgeLodash(min, age) { return age >= min; } const check19 = _.curry(checkAgeLodash)(19); console.log(check19(23)) // 输出:true
1
2
3
4
5
6
7
8
9
10
11
12// 模拟curry函数 function curry(fn) { return function curriedFn(...args) { if(args.length < fn.length) { return function () { return curriedFn(...args.concat(Array.from(arguments))); } } return fn(...args); } }
柯里化
一个函数有多个需要传递的参数时,先传递后续执行中不会改变的参数, 然后返回一个新的函数来接受剩余参数,并返回结果。
函数组合
当一个函数比较复杂时,可以把函数才分为多个函数,默认从右到左执行,最终得到结果。
1
2
3
4
5
6
7const compose = (f, g) => ((val) => (f(g(val)))); const reverse = (val) => val.reverse(); const first = val => val[0]; const fn = compose(first, reverse); console.log(fn([1,2,3,4])); // 输出:4
lodash中的组合函数:flow()、flowRight()
1
2
3
4
5
6
7
8const _ = require("lodash"); const reverse = arr => arr.reverse(); const first = arr => arr[0]; const toUpper = val => val.toUpperCase(); const fn = _.flowRight(toUpper, first, reverse); //从右往左执行 console.log(fn(['jack', 'tony', 'jane'])); // 输出:JANE
模拟flowRight方法(实现原理)
1
2
3
4
5
6
7
8
9
10function compose(...args) { return function (val) { return args.reverse().reduce(function (acc, cur) { return cur(acc); }, val); } } // ES6-箭头函数 const compose = (...args) => (val => (args.reverse().reduce((acc, cur) => (cur(acc)), val));
补充:数组中的reduce方法
- reduce() 方法对数组中的每个元素执行一个reducer函数(升序执行),将其结果汇总为单个返回值。
- reducer函数接收4个参数: acc-累计器、cur-当前值、idx-当前索引、src-原数组。
1
2
3
4
5
6
7
8
9
10
11
12
13var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']; var countedNames = names.reduce(function (allNames, name) { if (name in allNames) { allNames[name]++; } else { allNames[name] = 1; } return allNames; }, {}); // countedNames is: // { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
lodash/fp模块
lodash中的方法是数据优先,函数在后,而lodash/fp模块已对方法做了柯里化处理,是函数优先,数据在后。
functor函子
函子是一个特殊的容器,包含值和值的变形关系(函数), 通过一个对象来实现,这个对象具有map方法,这个方法是对值的处理,最终将处理后的值映射到新的容器中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Container { // of 静态方法,目的是不需要使用new 来创建是对象 static of (value) { return new Container(value); } constructor(value) { this._value = value; } map (fn) { return Container.of(fn(this._value)); } } let res = Container.of(4).map(val => val + 2).map(val => val * val); console.log(res);//36
- Maybe函子——对外部的null或undefined做处理,控制副作用在允许范围内
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Maybe { static of(value) { return new Maybe(value); } constructor(value) { this._value = value; } map(fn) { return this.isNothing ? Maybe.of(null) : Maybe.of(fn(this._value)); } isNothing () { return this._value === null || this._value === undefined; } }
- Either函子——用作异常处理并记录异常信息
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
32class Container { constructor(value) { this._value = value; } } class Left extends Container { static of(value) { return new Left(value); } map () { return this; } } class Right extends Container { static of(value) { return new Right(value); } map (fn) { return Right.of(fn(this._value)); } } const parseJSON = (str) => { try { return Right.of(JSON.parse(str)) }catch(e) { return Left.of({error: e.message}) } } let res = parseJSON('{"name": "str"}').map((x => x.name.toUpperCase())); console.log(res); // Right { _value: 'STR' }
学习总结:
今年(2021)3月报名了拉勾教育的前端高薪训练营,报名的原因是因为在我准备跳槽的时候,深刻地发现自己的原理知识很薄弱,而只关注项目功能实现,而不去了解原理性的内容,这样对于项目的性能优化方面其实是很不友好的。目前刚学完part1任务一的内容,主要就是讲述函数式编程,在上课之前只大概听过这个编程范式,但在以往学习中并没有深入了解,所以这部分的内容还是需要我反复多看几遍。之后也会坚持写学习笔记,在笔记中会扩展补充些遇到的但不是很深刻的知识点,也欢迎大家多指教本萌新。
最后
以上就是能干水壶最近收集整理的关于学习笔记:函数式编程与 JS 异步编程、手写 Promise的全部内容,更多相关学习笔记:函数式编程与内容请搜索靠谱客的其他文章。
发表评论 取消回复