编写高质量可维护的代码:编程范式

👆   这是第 92  篇 不掺水的原创 ,想要了解更多 ,请戳上方蓝色字体: 政采云前端团队  关注咱们吧~

本文首发于政采云前端团队博客:编写高质量可维护的代码:编程范式前端

https://www.zoo.team/article/program-paradigm

什么是编程范式呢?web

编程范式(Programming paradigm)是一类典型的编程风格,是指从事软件工程的一类典型的风格(能够对照方法学)。如:函数式编程、过程式编程、面向对象编程、指令式编程等等为不一样的编程范式。正则表达式

JS 是一种动态的基于原型和多范式的脚本语言,而且支持面向对象(OOP,Object-Oriented Programming)、命令式和声明式(如函数式 Functional Programming)的编程风格。数据库

那么面向对象,命令式,声明式编程究竟是什么呢?他们有什么区别呢?编程

命令式编程

命令式编程是一种描述计算机所需做出的行为的编程典范,即一步一步告诉计算机先作什么再作什么。举个简单的🌰:找出全部人中年龄大于 35 岁的,你就须要这样告诉计算机:json

  1. 建立一个新的数组 newArry 存储结果;
  2. 循环遍历全部人的集合 people;
  3. 若是当前人的年龄大于 35,就把这我的的名字存到新的数组中;
const people = [
    { name'Lily'age33 },
    { name'Abby'age36 },
    { name'Mary'age32 },
    { name'Joyce'age35 },
    { name'Bella'age38 },
    { name'Stella'age40 },
  ];
  const newArry = [];
  for (let i = 0; i < people.length; i++) {
    if (people[i].age > 35) {
      newArry.push(people[i].name);
    }
  }

命令式编程的特色是很是易于理解,按照实际的步骤实现,优势就在于性能高,可是会依赖,修改较多外部变量,可读性低。segmentfault

声明式编程
数组

声明式编程与命令式编程是相对立的,只须要告诉计算机要作什么而没必要告诉他怎么作。声明式语言包括数据库查询语(SQL),正则表达式,逻辑编程,函数式编程和组态管理系统。上边的例子用声明式编程是这样的:安全

const peopleAgeFilter = (people) => {
return people.filter((item) => item.age > 35)
}

函数式编程

什么是函数式编程呢?微信

函数式编程这里的函数并非咱们所知道的 Function,而是数学中的函数,即变量之间的映射,输入经过函数都会返回有且只有一个输出值。

// js 中的 function
function fun(data, value, type{
  // 逻辑代码
}
// 函数
y=f(x)

早在 1958 年,随着被创造出来的  LISP (https://baike.baidu.com/item/lisp%E8%AF%AD%E8%A8%80/2840299?fr=aladdin),函数式编程就已经问世。在近几年,在前端领域也逐渐出现了函数式编程的影子:箭头函数、map、reduce、filter,同时 Redux 的 Middleware 也已经应用了函数式编程...

函数式编程的特性
  • 函数是"第一等公民"

    所谓"第一等公民",指的是函数与其余数据类型同样,处于平等地位,能够赋值给其余变量,也能够做为参数,传入另外一个函数,或者做为别的函数的返回值。例如:

let fun = function(i){
  console.log(i);
}
[1,2,3].forEach(element => {
  fun(element);
});
  • 惰性计算

    在惰性计算中,表达式不是在绑定到变量时当即计算,而是在求值程序须要产生表达式的值时进行计算。即函数只在须要的时候执行。

  • 没有"反作用"

    "反作用"指的是函数内部与外部互动(最典型的状况,就是修改全局变量的值),产生运算之外的其余结果。因为 JS 中对象传递的是引用地址,即便咱们使用 const 关键词声明对象时,它依旧是能够变的。这样就会致使咱们可能会随意修改对象。例如:

 const user = {
  name'jingjing',
}
const changeName = (obj, name) => obj.name = name;
const changeUser = changeName(user, 'lili');
console.log(user); // {name: "lili"} user 对象已经被改变

改为无反作用的纯函数的写法:

const user = {
  name'jingjing',
}
// const changeName = (obj, name) => obj.name = name;
const changeName = (obj, name) => ({...user, name }); 
const changeUser = changeName(user, 'lili');
console.log(user); // {name: "jingjing"}, 此时user对象并无改变
  • 引用透明性

    即若是提供一样的输入,那么函数老是返回一样的结果。就是说,任什么时候候只要参数相同,引用函数所获得的返回值老是相同的。

在函数式编程中柯里化(Currying)函数组合(Compose)是必不可少。

  • 柯里化

网上关于柯里化的文章不少,这里再也不赘述,能够参考:函数柯里化Currying  (https://juejin.cn/post/6844903748137926669)。

柯里化 (https://zh.wikipedia.org/wiki/%E6%9F%AF%E9%87%8C%E5%8C%96)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。简单来讲,就是只传递给函数一个参数来调用它,让它返回一个函数去处理剩下的参数。即:

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));

在柯里化版本中,实际的执行以下:

currySquares(1) = function(y){
  return 1 + y * y;
}
currySquares(1)(2) = 1 + 4 = 5;
  • 函数组合(Compose)

    函数组合就是将两个或多个函数组合起来生成一个新的函数。

    在计算机科学中,函数组合是将简单函数组合成更复杂函数的一种行为或机制。就像数学中一般的函数组成同样,每一个函数的结果做为下一个函数的参数传递,而最后一个函数的结果是整个函数的结果。因此说柯里化是函数组合的基础。

例如:

双函数状况:

const compose = (f, g) => x => f(g(x))
const f = x => x * x;
const g = x => x + 2;
const composefg = compose(f, g);
composefg(1//9

对于多函数状况,简单实现以下:

const compose = (...fns) => (...args) => fns.reduceRight((val, fn) => fn.apply(null, [].concat(val)), args);
const f = x => x * x;
const g = x => x + 2;
const h = x => x - 3;
const composefgh = compose(f, g, h);
composefgh(5); // 16

声明式编程的特色是不产生“反作用”,不依赖也不会改变当前函数之外的数据,优势在于:

  1. 减小了可变变量,程序更加安全;
  2. 相比命令式编程,少了很是多的状态变量的声明与维护,自然适合高并发多现成并行计算等任务,这也是函数式编程近年又大热的重要缘由;
  3. 代码更为简洁,接近天然语言,易于理解,可读性更强。可是函数编程也有自然的缺陷:
  4. 函数式编程相对于命令式编程,每每会对方法过分包装,致使性能变差;
  5. 因为函数式编程强调不产生“反作用”,因此他不擅长处理可变状态;

面向对象编程

面向对象的程序设计把计算机程序视为一组对象的集合,而每一个对象均可以接收其余对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

面向对象的两个基本概念:

  1. 类:类是对象的类型模板;例如:政采云前端 ZooTeam 是一个类;
  2. 实例:实例是根据类建立的对象;例如:ZooTeam 能够建立出刘静这个实例;面向对象的三个基本特征:封装、继承、多态:注⚠️:如下例子均采用 ES6 写法。
  • 封装:封装即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象获得的数据和行为(或功能)相结合,造成一个有机的总体。根据个人理解,其实就是把子类的属性以及公共的方法抽离出来做为公共方法放在父类中。
class Zcy {
  constructor(name){
      this.name = name;
  }
  doSomething(){
      let {name} = this;
      console.log(`${name}9点半在开晨会`);
  }
  static soCute(){
      console.log("Zcy 是一个你们庭!");   
  }
}
let member = new Zcy("jingjing"18);
member.soCute();   // member.soCute is not a function
member.doSomething();  // jingjing9点半在开晨会
Zcy.soCute();  // Zcy 是一个你们庭!

Zcy 的成员都有名字和年龄,九点半时都在开晨会,因此把名字和年龄看成共有属性, 九点半开晨会看成公共方法抽离出来封装起来。static 表示静态方法,静态方法只属于 Zcy 这个类,因此当 member 调用 soCute 方法时,控制台报错。

  • 继承:继承就是子类继承父类的特征和行为,使得子类对象(实例)具备父类的属性和方法,或子类从父类继承方法,使得子类具备父类相同的行为。子类继承父类后,子类就会拥有父类的属性和方法,可是同时子类还能够声明本身的属性和方法,因此子类的功能会大于等于父类而不会小于父类。
class Zcy {
  constructor(name){
      this.name = name;
  }
  doSomething(){
      let {name} = this;
      console.log(`${name}9点半在开晨会`);
  }
  static soCute(){
      console.log("Zcy 是一个你们庭!");   
  }
}
class ZooTeam extends Zcy{
    constructor(name){
        super(name);
    }
    eat(){
      console.log("周五一块儿聚餐!");
    }
}
let zooTeam = new ZooTeam("jingjing");
zooTeam.doSomething(); // jingjing9点半在开晨会
zooTeam.eat(); // 周五一块儿聚餐!
zooTeam.soCute();    // zooTeam.soCute is not a function

ZooTeam 继承了 Zcy 的属性和方法,可是不能继承他的静态方法;并且 ZooTeam 声明了本身的方法 eat。

  • 多态:多态按字面的意思就是“多种状态”,容许将子类类型的指针赋值给父类类型的指针。即同一操做做用于不一样的对象,能够有不一样的解释,产生不一样的执行结果。多态的表现方式有重写,重载和接口,原生 JS 可以实现的多态只有重写。
  • 重写:重写是子类可继承父类中的方法,而不须要从新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想做必定的修改,这就须要采用方法的重写。方法重写又称方法覆盖。
class Zcy {
  constructor(name){
      this.name = name;
  }
  getName(){
    console.log(this.name);
  }
  doSomething(){
      let {name} = this;
      console.log(`${name}9点半在开晨会`);
  }
  static soCute(){
      console.log("Zcy 是一个你们庭!");   
  }
}
class ZooTeam extends Zcy{
    constructor(name){
        super(name);
    }
    doSomething(){
      console.log("zooTeam周五要开周会!");
    }
}
const zcy = new Zcy('jingjing');
const zooTeam = new ZooTeam('yuyu');
zcy.doSomething(); // jingjing9点半在开晨会
zcy.getName(); // jingjing
zooTeam.doSomething(); // zooTeam周五要开周会!
zooTeam.getName(); // yuyu

ZooTeam 为了知足本身的需求,继承了父类的 doSomething 方法后重写了 doSomething 方法,因此调用 doSomething 方法以后获得了不一样的结果,而 getName 方法只是继承并无重写。

面向对象编程的特色是抽象描述对象的基本特征,优势在于对象易于理解和抽象,代码容易扩充和重用。可是也容易产生无用代码,容易致使数据修改。

总结

命令式、声明式、面向对象本质上并无优劣之分,面向对象和命令式、声明式编程也不是完成独立、有严格的界限的,在抽象出各个独立的对象后,每一个对象的具体行为实现仍是有函数式和过程式完成。在实际应用中,因为需求每每是特殊的,因此仍是要根据实际状况选择合适的范式。

参考文章

面向对象之三个基本特征 (https://segmentfault.com/a/1190000018239556)

简明 JavaScript 函数式编程——入门篇 (https://juejin.cn/post/6844903936378273799#heading-0)

一文读懂JavaScript函数式编程重点-- 实践 总结 (https://zhuanlan.zhihu.com/p/67624686)

JavaScript 中的函数式编程:函数,组合和柯里化 (https://segmentfault.com/a/1190000023616150)

看完两件事

若是你以为这篇内容对你挺有启发,我想邀请你帮我两件小事

1.点个「在看」,让更多人也能看到这篇内容(点了在看」,bug -1 😊

2.关注公众号「 政采云前端团队」,持续为你推送精选好文

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com

本文分享自微信公众号 - 政采云前端团队(Zoo-Team)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。