編寫高質量可維護的代碼:優化邏輯判斷

這是第 64 篇不摻水的原創,想獲取更多原創好文,請搜索公衆號關注咱們吧~ 本文首發於政採雲前端博客:編寫高質量可維護的代碼:邏輯判斷javascript

前言

if else、switch case 是平常開發中最多見的條件判斷語句,這種看似簡單的語句,當遇到複雜的業務場景時,若是處理不善,就會出現大量的邏輯嵌套,可讀性差而且難以擴展。前端

編寫高質量可維護的代碼,咱們先從最小處入手,一塊兒來看看在前端開發過程當中,能夠從哪些方面來優化邏輯判斷?java

下面咱們會分別從 JavaScript 語法和 React JSX 語法兩個方面來分享一些優化的技巧。算法

JavaScript 語法篇

嵌套層級優化

function supply(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
  // 條件 1: 水果存在
  if (fruit) {
    // 條件 2: 屬於紅色水果
    if (redFruits.includes(fruit)) {
      console.log('紅色水果');
      // 條件 3: 水果數量大於 10 個
      if (quantity > 10) {
        console.log('數量大於 10 個');
      }
    }
  } else {
    throw new Error('沒有水果啦!');
  }
}
複製代碼

分析上面的條件判斷,存在三層 if 條件嵌套。npm

若是提早 return 掉無效條件,將 if else的多重嵌套層次減小到一層,更容易理解和維護。數組

function supply(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
  if (!fruit) throw new Error('沒有水果啦'); // 條件 1: 當 fruit 無效時,提早處理錯誤
  if (!redFruits.includes(fruit)) return; // 條件 2: 當不是紅色水果時,提早 return
  
  console.log('紅色水果');
  
  // 條件 3: 水果數量大於 10 個
  if (quantity > 10) {
    console.log('數量大於 10 個');
  }
}
複製代碼

多條件分支的優化處理

當須要枚舉值處理不一樣的業務分支邏輯時, 第一反應是寫下 if else ?咱們來看一下:babel

function pick(color) {
  // 根據顏色選擇水果
  if(color === 'red') {
      return ['apple', 'strawberry']; 
  } else if (color === 'yellow') {
      return ['banana', 'pineapple'];
  } else if (color === 'purple') {
      return ['grape', 'plum'];
  } else {
      return [];
  }
}
複製代碼

在上面的實現中:markdown

  • if else 分支太多
  • if else 更適合於條件區間判斷,而 switch case 更適合於具體枚舉值的分支判斷

使用 switch case 優化上面的代碼後:數據結構

function pick(color) {
  // 根據顏色選擇水果
  switch (color) {
    case 'red':
      return ['apple', 'strawberry'];
    case 'yellow':
      return ['banana', 'pineapple'];
    case 'purple':
      return ['grape', 'plum'];
    default:
      return [];
  }
}
複製代碼

switch case 優化以後的代碼看上去格式整齊,思路很清晰,但仍是很冗長。繼續優化:app

  • 藉助 Object 的 { key: value } 結構,咱們能夠在 Object 中枚舉全部的狀況,而後將 key 做爲索引,直接經過 Object.key 或者 Object[key] 來獲取內容
const fruitColor = {
  red: ['apple', 'strawberry'],
  yellow: ['banana', 'pineapple'],
  purple: ['grape', 'plum'],
};
function pick(color) {
  return fruitColor[color] || [];
}
複製代碼
  • 使用 Map 數據結構,真正的 (key, value) 鍵值對結構 ;
const fruitColor = new Map()
.set('red', ['apple', 'strawberry'])
.set('yellow', ['banana', 'pineapple'])
.set('purple', ['grape', 'plum']);

function pick(color) {
  return fruitColor.get(color) || [];
}
複製代碼

優化以後,代碼更簡潔、更容易擴展。

爲了更好的可讀性,還能夠經過更加語義化的方式定義對象,而後使用 Array.filter 達到一樣的效果。

const fruits = [
  { name: 'apple', color: 'red' }, 
  { name: 'strawberry', color: 'red' }, 
  { name: 'banana', color: 'yellow' }, 
  { name: 'pineapple', color: 'yellow' }, 
  { name: 'grape', color: 'purple' }, 
  { name: 'plum', color: 'purple' }
];

function pick(color) {
  return fruits.filter(f => f.color === color);
}
複製代碼

使用數組新特性簡化邏輯判斷

巧妙的利用 ES6 中提供的數組新特性,也可讓咱們更輕鬆的處理邏輯判斷。

多條件判斷

編碼時遇到多個判斷條件時,本能的寫下下面的代碼(其實也是最能表達業務邏輯的面向過程編碼)。

function judge(fruit) {
  if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'cherry' || fruit === 'cranberries' ) {
    console.log('red');
  }
}
複製代碼

可是當 type 將來到 10 種甚至更多時, 咱們只能繼續添加 || 來維護代碼麼 ?

試試 Array.includes ~

// 將判斷條件抽取成一個數組
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
function judge(fruit) {
  if (redFruits.includes(fruit)) {
      console.log('red');
   }
}

複製代碼

判斷數組中是否全部項都知足某條件

const fruits = [
  { name: 'apple', color: 'red' },
  { name: 'banana', color: 'yellow' },
  { name: 'grape', color: 'purple' }
];

function match() {
  let isAllRed = true;

  // 判斷條件:全部的水果都必須是紅色
  for (let f of fruits) {
    if (!isAllRed) break;
    isAllRed = (f.color === 'red');
  }

  console.log(isAllRed); // false
}

複製代碼

上面的實現中,主要是爲了處理數組中的全部項都符合條件。

使用 Array.every 能夠很容的實現這個邏輯:

const fruits = [
  { name: 'apple', color: 'red' },
  { name: 'banana', color: 'yellow' },
  { name: 'grape', color: 'purple' }
];

function match() {
  // 條件:全部水果都必須是紅色
  const isAllRed = fruits.every(f => f.color === 'red');

  console.log(isAllRed); // false
}

複製代碼

判斷數組中是否有某一項知足條件

Array.some ,它主要處理的場景是判斷數組中是否有一項知足條件。

若是想知道是否有紅色水果,能夠直接使用 Array.some 方法:

const fruits = [
  { name: 'apple', color: 'red' },
  { name: 'banana', color: 'yellow' },
  { name: 'grape', color: 'purple' }
];

// 條件:是否有紅色水果 
const isAnyRed = fruits.some(f => f.color == 'red');

複製代碼

還有許多其餘數組新特性,好比 Array.find、Array.slice、Array.findIndex、Array.reduce、Array.splice 等,在實際場景中能夠根據須要選擇使用。

函數默認值

使用默認參數

const buyFruit = (fruit,amount) => {
 if(!fruit){
	return
  }
  amount = amount || 1;
  console.log(amount)
}

複製代碼

咱們常常須要處理函數內部的一些參數默認值,上面的代碼你們都不陌生,使用函數的默認參數,能夠很好的幫助處理這種場景。

const buyFruit = (fruit,amount = 1) => {
 if(!fruit){
		return
  }
  console.log(amount,'amount')
}

複製代碼

咱們能夠經過 Babel 的轉譯來看一下默認參數是如何實現的。

從上面的轉譯結果能夠發現,只有參數爲 undefined 時纔會使用默認參數。

測試的執行結果以下:

buyFruit('apple','');  // amount
buyFruit('apple',null);  //null amount
buyFruit('apple');  //1 amount

複製代碼

因此使用默認參數的狀況下,咱們須要注意的是默認參數 amount=1 並不等同於 amount || 1

使用解構與默認參數

當函數參數是對象時,咱們可使用解構結合默認參數來簡化邏輯。

Before:

const buyFruit = (fruit,amount) => {
  fruit = fruit || {};
  if(!fruit.name || !fruit.price){
      return;
  }
  ...
  amount = amount || 1;
  console.log(amount)
}

複製代碼

After:

const buyFruit = ({ name,price }={},amount) => {
  if(!name || !prices){
  	return;
  }
  console.log(amount)
}

複製代碼

複雜數據解構

當處理比較簡的對象時,解構與默認參數的配合是很是好的,但在一些複雜的場景中,咱們面臨的多是更復雜的結構。

const oneComplexObj = {
  firstLevel: {
    secondLevel: [{
      name: '',
      price: '',
    }],
  },
};
複製代碼

這個時候若是再經過解構去獲取對象裏的值。

const {
  firstLevel: {
    secondLevel: [{ name, price }] = [],
  } = {},
} = oneComplexObj;
複製代碼

可讀性就會比較差,並且須要考慮多層解構的默認值以及數據異常狀況。

這種狀況下,若是項目中使用 lodash 庫,可使用其中的 lodash/get 方法。

import lodashGet from 'lodash/get';

const { name,price } = lodashGet(oneComplexObj,'firstLevel.secondLevel[0]',{});

複製代碼

策略模式優化分支邏輯處理

策略模式:定義一系列的算法,把它們一個個封裝起來, 而且使它們可相互替換。

使用場景:策略模式屬於對象行爲模式,當遇到具備相同行爲接口、行爲內部不一樣邏輯實現的實例對象時,能夠採用策略模式;或者是一組對象能夠根據須要動態的選擇幾種行爲中的某一種時,也能夠採用策略模式;這裏以第二種狀況做爲示例:

Before:

const TYPE = {
  JUICE: 'juice',
  SALAD: 'salad',
  JAM: 'jam',
};
function enjoy({ type = TYPE.JUICE, fruits }) {
  if (!fruits || !fruits.length) {
    console.log('請先採購水果!');
    return;
  }
  if (type === TYPE.JUICE) {
    console.log('榨果汁中...');
    return '果汁';
  }
  if (type === TYPE.SALAD) {
    console.log('作沙拉中...');
    return '拉沙';
  }
  if (type === TYPE.JAM) {
    console.log('作果醬中...');
    return '果醬';
  }
}

enjoy({ type: 'juice', fruits });
複製代碼

使用思路:定義策略對象封裝不一樣行爲、提供策略選擇接口,在不一樣的規則時調用相應的行爲。

After:

const TYPE = {
  JUICE: 'juice',
  SALAD: 'salad',
  JAM: 'jam',
};

const strategies = {
  [TYPE.JUICE](fruits) {
    console.log('榨果汁中...');
    return '果汁';
  },
  [TYPE.SALAD](fruits) {
    console.log('作沙拉中...');
    return '沙拉';
  },
  [TYPE.JAM](fruits) {
    console.log('作果醬中...');
    return '果醬';
  },
};

function enjoy({ type = TYPE.JUICE, fruits }) {
  if (!type) {
    console.log('請直接享用!');
    return;
  }
  if (!fruits || !fruits.length) {
    console.log('請先採購水果!');
    return;
  }
  return strategies[type](fruits);
}

enjoy({ type: 'juice', fruits });
複製代碼

框架篇之 React JSX 邏輯判斷優化

JSX 是一個看起來很像 XML 的 JavaScript 語法擴展。通常在 React 中使用 JSX 來描述界面信息,ReactDOM.render() 將 JSX 界面信息渲染到頁面上。

在 JSX 中支持 JavaScript 表達式,平常很常見的循環輸出子組件、三元表達式判斷、再複雜一些直接抽象出一個函數。

在 JSX 中寫這麼多 JavaScript 表達式,總體代碼看起來會有點兒雜亂。試着優化一下!

JSX-Control-Statements

JSX-Control-Statements 是一個 Babel 插件,它擴展了 JSX 的能力,支持以標籤的形式處理條件判斷、循環。

If 標籤

標籤內容只有在 condition 爲 true 時纔會渲染,等價於最簡單的三元表達式。

Before:

{ condition() ? 'Hello World!' : null }   

複製代碼

After:

<If condition={ condition() }>Hello World!</If>   

複製代碼

注意: 已被廢棄,複雜的條件判斷可使用 標籤。

Choose 標籤

標籤下包括至少一個 標籤、可選的 標籤。

標籤內容只有在 condition 爲 true 時纔會渲染,至關於一個 if 條件判斷分支。

標籤則至關於最後的 else 分支。

Before:

{ test1 ? <span>IfBlock1</span> : test2 ? <span>IfBlock2</span> : <span>ElseBlock</span> }

複製代碼

After:

<Choose>
  <When condition={ test1 }> <span>IfBlock1</span> </When>
  <When condition={ test2 }> <span>IfBlock2</span> </When>
  <Otherwise> <span>ElseBlock</span> </Otherwise>
</Choose>

複製代碼

For 標籤

標籤須要聲明 of、each 屬性。

of 接收的是可使用迭代器訪問的對象。

each 表明迭代器訪問時的當前指向元素。

Before:

{
  (this.props.items || []).map(item => {
      return <span key={ item.id }>{ item.title }</span>
  })
}

複製代碼

After:

<For each="item" of={ this.props.items }>
   <span key={ item.id }>{ item.title }</span>
</For>

複製代碼

注意: 標籤不能做爲根元素。

With 標籤

標籤提供變量傳參的功能。

Before:

renderFoo = (foo) => {
    return <span>{ foo }</span>;
}

// JSX 中表達式調用
{
    this.renderFoo(47)
}

複製代碼

After:

<With foo={ 47 }>
  <span>{ foo }</span>
</With>

複製代碼

使用這幾種標籤優化代碼,能夠減小 JSX 中存在的顯式 JavaScript 表達式,使咱們的代碼看上去更簡潔,可是這些標籤封裝的能力,在編譯時須要轉換爲等價的 JavaScript 表達式。

注意:具體 babel-plugin-jsx-control-statements 插件的使用見第三篇參考文章;Vue 框架已經經過指令的形式支持 v-if、v-else-if、v-else、v-show、slot 等。

總結

以上咱們總結了一些常見的邏輯判斷優化技巧。固然,編寫高質量可維護的代碼,除了邏輯判斷優化,還須要有清晰的註釋、含義明確的變量命名、合理的代碼結構拆分、邏輯分層解耦、以及更高層次的貼合業務的邏輯抽象等等,相信各位在這方面也有本身的一些心得,歡迎一塊兒留言討論~

參考文獻

推薦閱讀

分分鐘教會你搭建企業級的 npm 私有倉庫

一份值得收藏的 Git 異常處理清單

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「 5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

相關文章
相關標籤/搜索