第 64 篇原創好文~
本文首發於政採雲前端團隊博客: 編寫高質量可維護的代碼:邏輯判斷
)
if else、switch case 是平常開發中最多見的條件判斷語句,這種看似簡單的語句,當遇到複雜的業務場景時,若是處理不善,就會出現大量的邏輯嵌套,可讀性差而且難以擴展。javascript
編寫高質量可維護的代碼,咱們先從最小處入手,一塊兒來看看在前端開發過程當中,能夠從哪些方面來優化邏輯判斷? 前端
下面咱們會分別從 JavaScript 語法和 React JSX 語法兩個方面來分享一些優化的技巧。java
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 條件嵌套。算法
若是提早 return 掉無效條件,將 if else的多重嵌套層次減小到一層,更容易理解和維護。npm
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 ?咱們來看一下:數組
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 []; } }
在上面的實現中:babel
使用 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
const fruitColor = { red: ['apple', 'strawberry'], yellow: ['banana', 'pineapple'], purple: ['grape', 'plum'], } function pick(color) { return fruitColor[color] || []; }
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(type) { 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 '果醬'; } return; } enjoy({type:'juice',fruits});
使用思路:定義策略對象封裝不一樣行爲、提供策略選擇接口,在不一樣的規則時調用相應的行爲。
After:
const TYPE = { JUICE:'juice', SALAD:'salad', JAM:'jam' } const strategies = { [TYPE.JUICE]: function(fruits){ console.log('榨果汁中...'); return '果汁'; }, [TYPE.SALAD]:function(fruits){ console.log('作沙拉中...'); return '沙拉'; }, [TYPE.JAM]:function(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});
JSX 是一個看起來很像 XML 的 JavaScript 語法擴展。通常在 React 中使用 JSX 來描述界面信息,ReactDOM.render() 將 JSX 界面信息渲染到頁面上。
在 JSX 中支持 JavaScript 表達式,平常很常見的循環輸出子組件、三元表達式判斷、再複雜一些直接抽象出一個函數。
在 JSX 中寫這麼多 JavaScript 表達式,總體代碼看起來會有點兒雜亂。試着優化一下!
JSX-Control-Statements 是一個 Babel 插件,它擴展了 JSX 的能力,支持以標籤的形式處理條件判斷、循環。
<If> 標籤內容只有在 condition 爲 true 時纔會渲染,等價於最簡單的三元表達式。
Before:
{ condition() ? 'Hello World!' : null }
After:
<If condition={ condition() }>Hello World!</If>
注意:<Else /> 已被廢棄,複雜的條件判斷可使用 <Choose> 標籤。
<Choose> 標籤下包括至少一個 <When> 標籤、可選的 <Otherwise> 標籤。
<When> 標籤內容只有在 condition 爲 true 時纔會渲染,至關於一個 if 條件判斷分支。
<Otherwise> 標籤則至關於最後的 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>
注意:<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 等。
以上咱們總結了一些常見的邏輯判斷優化技巧。固然,編寫高質量可維護的代碼,除了邏輯判斷優化,還須要有清晰的註釋、含義明確的變量命名、合理的代碼結構拆分、邏輯分層解耦、以及更高層次的貼合業務的邏輯抽象等等,相信各位在這方面也有本身的一些心得,歡迎一塊兒留言討論~
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 50 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com