0、60s In Short
太长不看篇——60s简单总结
- 编程范式是一种编程风格,可分为命令式、函数式(属于声明式)、面向对象等多种范式。
- JS 是一种动态语言,它支持多种范式,具体选择哪种范式,根据业务需求和个人风格做选择。
- 编程范式没有绝对的好坏之分,只有合适和不合适。
- 简单来说下这几种范式的特点。
- 命令式更符合自然逻辑,容易理解。
- 函数式减少了临时变量,容易维护。
- 面向对象更方便扩展,代码复用程度高。
1、编程范式是什么
编程范式是一类典型的编程风格,是指从事软件工程的一类典型的风格(可以对照方法学)。
如:命令式编程、函数式编程、面向对象编程等不同的编程范式。
前端领域的函数式编程的体现
最近函数式编程逐渐又火了起来,我们看看前端领域有什么函数式编程的影子吧
- ES6的箭头函数、map 、reduce 、filter
- React 16.8 的Hook
- Vue3.0的Composition API
2、命令式编程
命令式编程就是关注计算执行步骤,如何执行,注重过程。
大部分命令式编程语言都支持四种基本的语句
- 运算语句;
- 循环语句(for、while);
- 条件分支语句(if else、switch);
- 无条件分支语句(return、break、continue)。
举个例子
常见表格带表头分组的需求,需要单独给每个列表配置宽度,而表格数据是动态获取的,所以要根据接口返回的数据生成一定数量重复的固定宽度数组,用来配置表格宽度。
- 使用命令式的写法
const WIDTHS = ['10px','20px','30px'], LENGTH = 3;
let arr = [];
for (let i = 0; i < LENGTH; i++) {
arr = arr.concat(WIDTHS);
}
// ["10px", "20px", "30px", "10px", "20px", "30px", "10px", "20px", "30px"]
复制代码
- 使用函数式的写法
const WIDTHS = ['10px','20px','30px'], LENGTH = 3;
const arr = Array(LENGTH).fill('').reduce(acc => acc.concat(WIDTHS), []);
// ["10px", "20px", "30px", "10px", "20px", "30px", "10px", "20px", "30px"]
复制代码
命令式编程的优缺点
- 优点
- 性能高,因为有引擎作优化
- 容易理解,因为符合自然编程思路
- 缺点
- 产生大量临时变量
- 代码可读性低,需要通读代码才知道具体做了什么
3、函数式编程(FP)
函数式编程,主要强调如何通过函数的组合变化来解决问题,关注结果。
函数式编程的特性
- 函数是”第一等公民”
- 函数可以像其他数据类型一样操作,如赋值给其他变量、作为函数的入参、作为函数的返回值。
- 惰性计算
- 只在需要的时候执行,不产生无意义的中间变量。
- 没有”副作用”
- 副作用指函数计算结果的过程中,系统状态的变化,或者函数内部和外部进行交互,产生其他影响。
- 常见的副作用:更改全局变量、发送http请求、dom查询。
- 引用透明性
- 即如果提供同样的输入,那么函数总是返回同样的结果。
- 完全不依赖外部状态的变化(无状态),如全局变量,this 指针,IO 操作等。
- PS:没有副作用 + 无状态 又可以称为纯函数。
- 柯里化(Currying)
- 理解为“加工站”,接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数。
- 将一个多元函数转为一个单元函数,可以依次调用
f(x,y,z) → f(x)(y)(z)
。 - 举个例子:求两个数的平方和
// 原始版本
const squares = function(x, y) {
return x * x + y * y;
}
// 柯里化版本
const currySquares = function(x) {
return function(y) {
return x * x + y * y;
}
}
console.log(squares(1,2));
console.log(currySquares(1)(2));
复制代码
- 函数组合(Compose)
- 理解为“流水线”,将多个函数组合成一个新的函数,初始数据通过多个函数依次处理,最后整体输出。
- 把复杂的逻辑拆分成一个个简单任务,最后组合起来完成任务,使得整个过程的数据流更明确、可控。
- 为了保证每个加工站的输出刚好流入下个工作站的输入,必须是单元函数。如果是加工站是多元函数,就需要用到柯里化转为单元函数,再放到流水线上组合使用。
- 两个函数组合示例: 注意compose (从右往左的组合顺序执行 )
const compose = (f, g) => x => f(g(x))
const f = x => x + 1;
const g = x => x * 2;
const fg = compose(f, g);
fg(1) // 3
复制代码
- 多个函数组合示例: 如果需要类似管道pipe(从左往右)的数据流,将reduce换成reduceRight即可。
const compose = (...fns) => {
return fns.reduce((acc,cur) => {
return (...args) => {
return acc(cur(...args))
}
})
}
// 最后返回的函数先执行
const f = x => {
console.log('f: ', x);
return x + 1;
}
const g = x => {
console.log('g: ', x);
return x + 1;
}
const t = x => {
console.log('t: ', x);
return x + 1;
}
compose(f, g, t)(1);
// t: 1
// g: 2
// f: 3
复制代码
函数式编程的优缺点
- 优点
- 代码简洁,开发快速
- 函数复用率很高,减少重复,开发速度快
- 维护方便
- 减少状态变量的声明和维护
- 更少的出错概率
- 强调纯函数,没有副作用
- 代码简洁,开发快速
- 缺点
- 性能不好,容易过度抽象包装
- 比如 使用命令式就只需要 变量+命令式(一层循环),使用函数式,不使用外部变量(双重循环)。
- 在 JS 这种非函数式语言中,函数式的方式比直接写语句指令慢(引擎会针对很多指令做特别优化),如纯循环就比原生map性能快几倍。
- 内存容易占用过高
- 为了实现对象状态的不可变,创建更多新对象。消耗更多内存空间,JS引擎进行垃圾回收有压力。
- 性能不好,容易过度抽象包装
4、面向对象编程(OOP)
面向对象的三个特征
封装
- 封装即隐藏对象的属性和实现细节,仅对外公开API接口。
- 将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体。
class Person {
constructor(name) {
this.name = name;
}
work() {
console.log(`${this.name} is working hard`);
}
static touch() {
console.log("Touch the fish");
}
}
let boy = new Person("Joe");
boy.touch(); // boy.touch is not a function
boy.work(); // Joe is working hard
Person.touch(); // Touch the fish
复制代码
继承
- 继承就是子类可以继承父类,使得子类对象(实例)具有父类公有的属性和方法,不需要写重复的代码。
- 子类继承父类后,子类就会拥有父类的属性和方法,同时子类还可以声明自己的属性和方法。
class Person {
constructor(name) {
this.name = name;
}
work() {
console.log(`${this.name} is working hard`);
}
static touch() {
console.log("Touch the fish");
}
}
class FatMan extends Person {
constructor(name){
super(name);
}
drink() {
console.log("喂!三点几了,饮茶先!");
}
}
let uncle = new FatMan("饮茶哥");
uncle.work(); // 饮茶哥 is working hard
uncle.drink(); // 喂!三点几了,饮茶先!
uncle.touch(); // uncle.touch is not a function
复制代码
多态
- 多态按字面的意思就是“多种状态”,允许将子类类型的指针赋值给父类类型的指针。即同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
- 多态的表现方式有重写,重载和接口,原生 JS 能够实现的多态只有重写。
- 重写:重写是子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想直接地继承父类的方法,自己想做调整和改动,就要重写父类的方法。我们也可以称之为方法覆盖。
class Person {
constructor(name) {
this.name = name;
}
getName() {
console.log(this.name);
}
work() {
console.log(`${this.name} is working hard`);
}
static touch() {
console.log("Touch the fish");
}
}
class FatMan extends Person {
constructor(name){
super(name);
}
work() {
console.log("做碌*啊做!");
}
drink() {
console.log("喂!三点几了,饮茶先!");
}
}
const boy = new Person('Joe');
const uncle = new FatMan('饮茶哥');
boy.getName(); // Joe
boy.work(); // Joe is working hard
uncle.getName(); // 饮茶哥
uncle.work(); // 做碌*啊做!
复制代码
5、如何选择
- 学习编程范式的意义是丰富你的编程思路,在面对特定场景,选择最合适的编程武器,灵活组合使用。
- 命令式、函数式、面向对象本质上并没有优劣之分。我们可以在实际开发中,根据需求选择合适的编程范式。
参考文章
- 浅析JavaScript函数式编程(mp.weixin.qq.com/s/oZd4Rbi9-…)
- 编写高质量可维护的代码:编程范式(mp.weixin.qq.com/s/NmWPziVRn…)
- 简明 JavaScript 函数式编程——入门篇(juejin.cn/post/684490…)
- 面向对象之三个基本特征(javaScript)(segmentfault.com/a/119000001…)
推荐
- 函数式编程库(ramda.cn/)
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END