前端编程范式

1,208 阅读3分钟

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

前言

最近在学习函数式编程,在学习的过程中顺便整理下当前常用的编程范式。

编程范式

计算机编程的基本风格或典范模式。

编程范式和编程语言关系

  1. 抽象和具体。编程范式是抽象的,只有在编程语言中才能体现出来。
  2. 多对多关系。
  3. 主导范式。一种语言通常会有一两种主导范式,成为这门语言的风格特征。

分类

我们常用的编程范式有命令式编程,声明式编程, 面向对象编程,函数式编程,泛型编程等。对于JS来讲,前四种算是比较常用。

1. 命令式编程

命令式编程就是传统的编程思路,上手就写,临时变量,各种循环等。它的主要思想就是关注计算机的执行步骤,一步步告诉计算机先做什么再做什么,就好像是一个指令序列。

2. 声明式编程

声明式编程以数据结构的形式来表达程序执行逻辑。它的主要思想是告诉计算机该做什么,但是不指定它具体怎么做。它不需要去存储一大堆临时变量来存储数据,也不包含循环控制代码,如for,while等

SQL语句就是典型的声明式编程。

3. 面向对象编程

面向对象编程属于命令式编程的一种抽象。面向对象编程的三个基本概念:封装,继承,多态;

面向对象编程的五个原则:

  1. 单一职责【一个类应该有且只有一个去改变它的理由,这意味着一个类应该只有一项工作】
  2. 开放封闭【对象或实体应该对扩展开发,对修改关闭】
  3. 里氏替换【对父类的调用同样适用于子类】
  4. 依赖倒置【高层次的模块不应该依赖低层次的模块】
  5. 接口隔离【客户端不应该被迫依赖使用不到的接口】

JS中面向对象是把逻辑与数据封装到函数与原型链中,通过函数原型链实现继承,而代码的运行逻辑与数据依然是封装在函数内,但是做了属性和方法的区分。

4. 函数式编程

函数式编程是与声明式编程有关联的,但是不局限于声明式编程。它的重点是函数第一位,把逻辑完全视为函数的计算。可以将函数作为入参传入,也可以将函数作为返回值返回。函数式编程完全依赖于表达式。

5. 泛型编程

泛型编程理念是要求其中算法以尽可能抽象的方式编写,而不依赖于将在其上执行这些算法的数据形式。

泛型编程最初诞生于C++中,目的是为了实现C++的STL(标准模板库),其语言机制就是模板。模板的精神就是参数化类型。在前端开发领域,很少接触,不做过多了解。

命令式与声明式区别

示例:假设要把一个文件类型的数组转化成一个对象数组存储

arr = ['txt', 'zip', 'excel']
   
newArr = [{type: 'txt'}, {type: 'zip'}, {type: 'excel'}]

命令式:

function imperativeFun() {
    let arr = ['txt','zip', 'excel'];
    let newArr = [];
    for(let i = 0, len = arr.length; i < len; i++) {
        let obj = {
            type: arr[i]
        }
        newArr.push(obj);
    };

    return newArr;
}

声明式:

function declarativeFun() {
    let arr = ['txt','zip', 'excel'];

    return  arr.map(function(item) {
        return {
            type: item
        }
    });
}


// map函数将遍历数组的整个过程抽离出来。让我们更关注我们想要的是什么,而不是怎么做。
// 可以看出,在大部分情况下,命令式编程都比较直观简单,维护的人容易理解。    
// 但是如果花时间去学习声明式中可以抽离归整的部分,会给我们的编程带来很大的便捷,比如节省代码量,抽象工具函数等。

面向对象与函数式编程区别

  1. 函数式编程是把数据与逻辑封装到函数中,而不是类或者对象。
  2. 面向对象的任何一个原型方法都可以拿到this,可以轻易获取闭包的数据。但是函数式编程的每个函数都是纯函数,不会依赖外部状态。
  3. 面向对象编程(OOP)通过封装变化让代码更容易理解。函数式编程(FP)通过最小化变化让代码更容易理解。
性质函数式面向对象
组合单元函数对象(类)
编程风格声明式命令式
数据和行为独立且松耦合的纯函数与方法紧密耦合的类
状态管理将对象视为不可边值通过实例方法改变对象
程序流控制函数与递归循环与条件
线程安全可并发编程难以实现
封装性一切都是不可变的,无须封装需要保护数据的完整性
示例,给定一个数字组成的数组,计算他们的平方和

面向对象实现:


//ES5: 
function Calculate(nums) {
    this.nums = nums;
}

Calculate.prototype.square = function() {
    var squares = [];
    for(var i=0; i<this.nums.length; i++) {
        squares.push(this.nums[i]*this.nums[i]);
    }
    
    return square;
}

Calculate.prototype.sum = function(arr) {
    var res = 0;
    for(var i=0; i<arr.length; i++) {
        res += arr[i];
    }
    return res;
}

var test = new Calculate([1, 2, 3]);
test.sum(test.square());     //14


//ES6:
class Calculate {
    constructor(nums) {
        this.nums = nums;
    }

    square() {
        let squares = [];
        for(let i=0; i<this.nums.length; i++) {
            squares.push(this.nums[i]*this.nums[i]);
        }

        return squares;
    }

    sum(arr) {
        let res = 0;
        for(let i=0; i<arr.length; i++) {
            res += arr[i];
        }
        return res;
    }
}
let test = new Calculate([1, 2, 3]);
let m = test.sum(test.square());

函数式实现:

const calculate = (nums) => nums.map(x => x*x).reduce((res, data) => res+data, 0);
calculate([1,2,3])

总结

Javascript本身就是多范式语言,所以在合适的地方使用合适的编程方式。它们彼此互不排斥,可以共存。