編寫高質量可維護的代碼:編程範式

👆   這是第 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索