什么是编程范式呢?

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

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

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

命令式编程

命令式编程是一种描述计算机所需作出的行为的编程典范,即一步一步告诉计算机先做什么再做什么。举个简单的🌰:找出所有人中年龄大于 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);
    }
  }

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

声明式编程

声明式编程与命令式编程是相对立的,只需要告诉计算机要做什么而不必告诉他怎么做。声明式语言包括数据库查询语(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);
});
 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;

例如:

双函数情况:

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

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

面向对象编程

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

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

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。

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 😊

招贤纳士

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

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

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

06-17 08:45