App下載

代碼優(yōu)化技巧 : 邏輯判斷

猿友 2020-08-10 18:00:05 瀏覽數(shù) (3101)
反饋

想成為一名合格的程序員,那么代碼優(yōu)化是相當(dāng)重要的一部分,一份高質(zhì)量的代碼,可以讓人一目了然,既方便自己,也方便他人。

if else、switch case 是日常開發(fā)中最常見的條件判斷語句,這種看似簡單的語句,當(dāng)遇到復(fù)雜的業(yè)務(wù)場景時,如果處理不善,就會出現(xiàn)大量的邏輯嵌套,可讀性差并且難以擴展。

編寫高質(zhì)量可維護(hù)的代碼,我們先從最小處入手,一起來看看在前端開發(fā)過程中,可以從哪些方面來優(yōu)化邏輯判斷?

下面我們會分別從 JavaScript 語法和 React JSX 語法兩個方面來分享一些優(yōu)化的技巧。

JavaScript 語法篇

嵌套層級優(yōu)化

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

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

如果提前 return 掉無效條件,將 if else的多重嵌套層次減少到一層,更容易理解和維護(hù)。

function supply(fruit, quantity) {
    const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
    if(!fruit) throw new Error('沒有水果啦'); // 條件 1: 當(dāng) fruit 無效時,提前處理錯誤
    if(!redFruits.includes(fruit)) return; // 條件 2: 當(dāng)不是紅色水果時,提前 return

    
    console.log('紅色水果');

    
    // 條件 3: 水果數(shù)量大于 10 個
    if (quantity > 10) {
        console.log('數(shù)量大于 10 個');
    }
}

多條件分支的優(yōu)化處理

當(dāng)需要枚舉值處理不同的業(yè)務(wù)分支邏輯時, 第一反應(yīng)是寫下 if else ?我們來看一下:

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

在上面的實現(xiàn)中:

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

使用 switch case 優(yōu)化上面的代碼后:

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

switch case 優(yōu)化之后的代碼看上去格式整齊,思路很清晰,但還是很冗長。繼續(xù)優(yōu)化:

  • 借助 Object{ key: value } 結(jié)構(gòu),我們可以在 Object 中枚舉所有的情況,然后將 key 作為索引,直接通過 Object.key 或者 Object[key] 來獲取內(nèi)容

const fruitColor = {                                                                        
    red: ['apple', 'strawberry'],
    yellow: ['banana', 'pineapple'],
    purple: ['grape', 'plum'],
}
function pick(color) {
    return fruitColor[color] || [];
}

  • 使用 Map 數(shù)據(jù)結(jié)構(gòu),真正的 (key, value) 鍵值對結(jié)構(gòu) ;

const fruitColor = new Map()
.set('red', ['apple', 'strawberry'])
.set('yellow', ['banana', 'pineapple'])
.set('purple', ['grape', 'plum']);


function pick(color) {
  return fruitColor.get(color) || [];
}

優(yōu)化之后,代碼更簡潔、更容易擴展。

為了更好的可讀性,還可以通過更加語義化的方式定義對象,然后使用 Array.filter 達(dá)到同樣的效果。

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);
}

(推薦教程:JavaScript教程

使用數(shù)組新特性簡化邏輯判斷

巧妙的利用 ES6 中提供的數(shù)組新特性,也可以讓我們更輕松的處理邏輯判斷。

多條件判斷

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

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

但是當(dāng) type 未來到 10 種甚至更多時, 我們只能繼續(xù)添加 || 來維護(hù)代碼么 ?

試試 Array.includes ~

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

判斷數(shù)組中是否所有項都滿足某條件

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
}

上面的實現(xiàn)中,主要是為了處理數(shù)組中的所有項都符合條件。

使用 Array.every 可以很容的實現(xiàn)這個邏輯:

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
}

判斷數(shù)組中是否有某一項滿足條件

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

如果想知道是否有紅色水果,可以直接使用 Array.some 方法:

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


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

還有許多其他數(shù)組新特性,比如 Array.find、Array.slice、Array.findIndex、Array.reduceArray.splice 等,在實際場景中可以根據(jù)需要選擇使用。

函數(shù)默認(rèn)值

使用默認(rèn)參數(shù)

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

我們經(jīng)常需要處理函數(shù)內(nèi)部的一些參數(shù)默認(rèn)值,上面的代碼大家都不陌生,使用函數(shù)的默認(rèn)參數(shù),可以很好的幫助處理這種場景。

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

我們可以通過 Babel 的轉(zhuǎn)譯來看一下默認(rèn)參數(shù)是如何實現(xiàn)的。

默認(rèn)參數(shù)

從上面的轉(zhuǎn)譯結(jié)果可以發(fā)現(xiàn),只有參數(shù)為 undefined 時才會使用默認(rèn)參數(shù)。

測試的執(zhí)行結(jié)果如下:

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

所以使用默認(rèn)參數(shù)的情況下,我們需要注意的是默認(rèn)參數(shù) amount=1 并不等同于 amount || 1。

使用解構(gòu)與默認(rèn)參數(shù)

當(dāng)函數(shù)參數(shù)是對象時,我們可以使用解構(gòu)結(jié)合默認(rèn)參數(shù)來簡化邏輯。

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)
}

復(fù)雜數(shù)據(jù)解構(gòu)

當(dāng)處理比較簡的對象時,解構(gòu)與默認(rèn)參數(shù)的配合是非常好的,但在一些復(fù)雜的場景中,我們面臨的可能是更復(fù)雜的結(jié)構(gòu)。

const oneComplexObj = {
    firstLevel:{
        secondLevel:[{
            name:"",
            price:""
        }]
    }
}

這個時候如果再通過解構(gòu)去獲取對象里的值。

const {
    firstLevel:{
        secondLevel:[{name,price]=[]
    }={}
} = oneComplexObj;        

可讀性就會比較差,而且需要考慮多層解構(gòu)的默認(rèn)值以及數(shù)據(jù)異常情況。

這種情況下,如果項目中使用 lodash 庫,可以使用其中的 lodash/get 方法。

import lodashGet from 'lodash/get';


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

策略模式優(yōu)化分支邏輯處理

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

使用場景:策略模式屬于對象行為模式,當(dāng)遇到具有相同行為接口、行為內(nèi)部不同邏輯實現(xiàn)的實例對象時,可以采用策略模式;或者是一組對象可以根據(jù)需要動態(tài)的選擇幾種行為中的某一種時,也可以采用策略模式;這里以第二種情況作為示例:

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});

使用思路:定義策略對象封裝不同行為、提供策略選擇接口,在不同的規(guī)則時調(diào)用相應(yīng)的行為。

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});

(推薦微課:JavaScript微課

框架篇之 React JSX 邏輯判斷優(yōu)化

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

JSX 中支持 JavaScript 表達(dá)式,日常很常見的循環(huán)輸出子組件、三元表達(dá)式判斷、再復(fù)雜一些直接抽象出一個函數(shù)。

JSX 中寫這么多 JavaScript 表達(dá)式,整體代碼看起來會有點兒雜亂。試著優(yōu)化一下!

JSX-Control-Statements

JSX-Control-Statements 是一個 Babel 插件,它擴展了 JSX 的能力,支持以標(biāo)簽的形式處理條件判斷、循環(huán)。

If 標(biāo)簽

<If> 標(biāo)簽內(nèi)容只有在 conditiontrue 時才會渲染,等價于最簡單的三元表達(dá)式。

Before:

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

After:

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

注意:<Else /> 已被廢棄,復(fù)雜的條件判斷可以使用 <Choose> 標(biāo)簽。

Choose 標(biāo)簽

<Choose> 標(biāo)簽下包括至少一個 <When> 標(biāo)簽、可選的 <Otherwise> 標(biāo)簽。

<When> 標(biāo)簽內(nèi)容只有在 conditiontrue 時才會渲染,相當(dāng)于一個 if 條件判斷分支。

<Otherwise> 標(biāo)簽則相當(dāng)于最后的 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 標(biāo)簽

<For> 標(biāo)簽需要聲明 of、each 屬性。

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

each 代表迭代器訪問時的當(dāng)前指向元素。

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> 標(biāo)簽不能作為根元素。

With 標(biāo)簽

<With> 標(biāo)簽提供變量傳參的功能。

Before:

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


// JSX 中表達(dá)式調(diào)用
{
    this.renderFoo(47)
}

After:

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

使用這幾種標(biāo)簽優(yōu)化代碼,可以減少 JSX 中存在的顯式 JavaScript 表達(dá)式,使我們的代碼看上去更簡潔,但是這些標(biāo)簽封裝的能力,在編譯時需要轉(zhuǎn)換為等價的 JavaScript 表達(dá)式。

總結(jié)

以上就是一些常見的邏輯判斷優(yōu)化技巧總結(jié)。當(dāng)然,編寫高質(zhì)量可維護(hù)的代碼,除了邏輯判斷優(yōu)化,還需要有清晰的注釋、含義明確的變量命名、合理的代碼結(jié)構(gòu)拆分、邏輯分層解耦、以及更高層次的貼合業(yè)務(wù)的邏輯抽象等等,相信各位在這方面也有自己的一些心得。

0 人點贊