你的 JS 程式碼本可以更加優雅

淘淘笙悅發表於2018-12-02

有時感覺挺有趣的是在群裡聊天時的自嘲,「xx 專案在經過我一年的不斷努力下,終於變得不可維護」。個人認為,維護是一件比開發更富挑戰性的事情,前人的程式碼是否規範優雅會很直接地影響我們的工作效率和心情。

所以,我們更要時刻地去注意我們程式碼的質量,也許你的程式碼已經足夠規範,但在某種程度上來講卻不夠優雅。本文列出了一些讓 JS 程式碼更加優雅的技巧及建議,希望能夠對你有所幫助。

我的世界不只有 if else

在邏輯判斷的場景中,經常我們的第一反應都是使用 if else / switch 來解決,因為這是最符合我們命令式邏輯思維的語法(難道不是因為書裡只教了這兩個嗎)。

但當在我們的邏輯判斷場景中有很多種情況需要判斷時,使用 if else / switch 固然能夠解決我們的問題,但卻讓人感覺程式碼比較冗餘而不夠優雅。

舉個栗子,通過 type 型別來判斷相應的學生型別。

// if else
let getStudentType = (type) =>{
  if(type == 1){
    console.log('學神')
  } else if (type == 2){
    console.log('學霸')
  } else if (type == 3) {
    console.log('學渣')
  } else {
    console.log('學灰')
  }
}
// switch
let getStudentType = (type) => {
  switch (type) {
    case 1:
      console.log('學神')
      break
    case 2:
      console.log('學霸')
      break
    case 3:
      console.log('學渣')
      break
    default:
      console.log('學灰')
      break
  }
}
複製程式碼

如上,通過 if else / switch 語法雖然能夠直觀地表現出程式碼的邏輯,但卻似乎讓人感覺有些重複贅餘。其實,對於邏輯判斷的場景,我們還可以有更多其它的方式來解決;其實,你的 JS 程式碼本可以更加優雅。

三目運算子

如果通過邏輯判斷只是單純為了進行賦值的操作,那麼我們通常可以使用三目運算子來解決。

let getStudentType = (type) => {
  let studentType = type == 1 ?'學神'
    : type == 2 ? '學霸'
      : type == 3 ? '學渣' : '學灰';
  console.log(studentType)
}
複製程式碼

是否看起來更加舒適了呢。

Object / Map 物件

基本上所有的邏輯判斷操作都可以通過 Object / Map 物件的方式來解決。

// Object 物件
let getStudentType = (type) => {
  let obj = {
    1:'學神',
    2:'學霸',
    3:'學渣',
    0:'學灰'
  }
  let studentType = obj[type] || obj['0'];
  console.log(studentType);
}
// Map 物件
let getStudentType = (type) => {
  let map = new Map([
    [1, '學神'],
    [2, '學霸'],
    [3, '學渣'],
    [0, '學灰']
  ])
  let studentType = map.get(type) || map.get('0');
  console.log(studentType);
}
複製程式碼

在邏輯判斷的場景中通過 Object / Map 物件我們依舊能夠實現優雅的程式碼,當然,上面所舉的栗子僅僅只是邏輯判斷中進行賦值操作的場景,在對於需要做更多操作的邏輯判斷的場景中,Object / Map 物件更能體現出它們的優勢。

讓我們擴充套件上面的栗子,在通過 type 型別判斷相應學生型別之後,每個學生還會發動自身相關型別的技能。

通過 if else 實現的方式如下

// if else
let studentAction = (type) =>{
  if(type == 1){
    console.log('學神')
    launchXueshenSkill();
  } else if (type == 2){
    console.log('學霸')
    launchXuebaSkill();
  } else if (type == 3) {
    console.log('學渣')
    launchXuezhaSkill();
  } else {
    console.log('學灰')
    launchXuehuiSkill();
  }
}
複製程式碼

而通過 Object / Map 物件,可以更優雅地實現

// Object 物件
let getStudentType = (type) => {
  let obj = {
    1: () => { console.log('學神'); launchXueshenSkill(); },
    2: () => { console.log('學霸'); launchXuebaSkill(); },
    3: () => { console.log('學渣'); launchXuezhaSkill(); },
    0: () => { console.log('學灰'); launchXuehuiSkill(); },
  }
  let studentSkill = obj[type] || obj['0'];
  studentSkill();
}
// Map 物件
let getStudentType = (type) => {
  let map = new Map([
    [1, () => { console.log('學神'); launchXueshenSkill(); }],
    [2, () => { console.log('學霸'); launchXuebaSkill(); }],
    [3, () => { console.log('學渣'); launchXuezhaSkill(); }],
    [0, () => { console.log('學灰'); launchXuehuiSkill(); }]
  ])
  let studentSkill = map.get(type) || map.get('0');
  studentSkill()
}
複製程式碼

Object 和 Map 物件都能解決所有的邏輯判斷的問題,那麼它們兩者有什麼區別呢?

Map 物件是 ES6 中的語法,它與 Object 物件最大的區別就是 Object 物件的鍵只能是字串,而 Map 物件的鍵可以是任意值。

所以相對而言,Map 物件較 Object 物件更加靈活,在更加複雜的邏輯判斷中,當我們的鍵使用字串不再滿足需求時,使用 Map 物件才能實現我們的目的。

true && xxx

true && xxx 主要是適用於 if else 中一些簡單場景的情況。判斷一個值是否為 true,如果為 true 時則執行 xxx 的相關操作。

let isGoodStudent = true;
// if else
if (isGoodStudent){
  console.log('我是一個好學生')
}
// true && xxx 
isGoodStudent && (console.log('我是一個好學生'));
複製程式碼

三行的程式碼最終簡寫為一行,真是優雅呀!

false || variable

false || xxx 的使用場景是作為某些場景下三目運算子的簡潔寫法。判斷一個變數是否存在,若是不存在(為 false )則賦值另一個變數(variable)。

let studentType1 = '學神'
let studentType2 = '學霸'
// 三目運算子
let goodStudent = studentType1 ? studentType1 : studentType2;
// false || xxx
let goodStudent = studentType1 || studentType2;
複製程式碼

我的世界不只有 for

在邏輯迴圈的場景中,經常我們的第一反應都是使用 for / while 來解決,因為這也是最符合我們命令式邏輯思維的語法(難道不還是因為書裡只教了這兩個嗎)。

但與 if else / switch 一樣,for / while 也是比較直觀但同時欠缺優雅性的寫法。

let studentType = ['學神', '學霸', '學渣', '學灰'];
// for
for (let i = 0, len = studentType.length; i < len; i++) {
  console.log(studentType[i]);
}
// while
let i = 0;
while (i < studentType.length){
  console.log(studentType[i]);
  i++;
}
複製程式碼

同樣的,對於邏輯迴圈的場景,我們還可以有更多其它的方式來解決。

Array.prototype.forEach

forEach() 方法的使用場景與 for / while 基本是一致的(forEach 迴圈不能提前終止),只要是邏輯迴圈的場景,都可以使用 forEach() 來實現。

studentType.forEach((v,i) => {
  console.log(v);
})
複製程式碼

Array.prototype.map

map() 方法若是隻需要取得陣列的元素進行迴圈的一些操作,則其使用方式與 forEach() 是一致的。

studentType.map((v, i) => {
  console.log(v);
})
複製程式碼

map() 方法會返回一個新陣列,其結果是原始陣列中的每個元素都呼叫一個提供的函式後返回的結果。

舉個栗子,在 studentType 型別中的每個元素後面都新增 +10086 的字串然後返回一個新陣列。

llet superStudentType = studentType.map((v, i) => `${v}+10086`)
console.log(superStudentType); // [ '學神+10086', '學霸+10086', '學渣+10086', '學灰+10086' ]
複製程式碼

所以,map() 方法除了能代替 for / while 迴圈外,還提供了對原始陣列元素操作並返回新陣列的功能(這同樣也可以使用 for / while 迴圈來實現,只是需要書寫更多的程式碼來實現)。

同樣的,下述所列舉的關於陣列的方法,都是可以通過 for / while 迴圈來實現的,只是使用下述已封裝好的方法,會讓我們的程式碼邏輯更清晰並且程式碼更加簡潔優雅。

Array.prototype.filter

filter() 方法返回一個新陣列, 其包含通過所提供函式實現的測試的所有元素。

let studentTypeWithScore = [{
    type:'學神',
    score:100
  },{
    type: '學霸',
    score: 85
  },{
    type: '學渣',
    score: 65
  },{
    type: '學灰',
    score: 50
  }]
let goodStudentType = studentTypeWithScore.filter((v, i) => v.score > 80)
console.log(goodStudentType); // [ { type: '學神', score: 100 }, { type: '學霸', score: 85 } ]
複製程式碼

Array.prototype.find

find() 方法返回陣列中滿足提供的測試函式的第一個元素的值,否則返回  undefined。

所以,當我們只想獲得陣列中符合條件的第一個元素時,使用 find() 方法會比使用 filter() 方法更加高效簡潔。find() 方法獲得符合條件的第一個元素時就停止遍歷了,而 filter() 方法需要遍歷陣列全部元素獲得符合條件的所有元素並取出第一個元素。

let oneGoodStudentType = studentTypeWithScore.find((v, i) => v.score > 80)
console.log(oneGoodStudentType); // { type: '學神', score: 100 }
複製程式碼

Array.prototype.some

some() 方法用於檢測陣列中是否有元素滿足指定條件。

同樣的,當我們只想確定陣列中是否有符合條件的元素,使用 some() 方法會比使用 find() 方法更加高效簡潔。some() 方法是返回布林值而 find() 方法是返回符合條件的第一個元素值。

let hasGoodStudentType = studentTypeWithScore.some((v, i) => v.score > 80)
console.log(hasGoodStudentType); // true
複製程式碼

Array.prototype.every

every() 方法測試陣列的所有元素是否都通過了指定函式的測試。

let isAllGoodStudentType = studentTypeWithScore.every((v, i) => v.score > 80)
console.log(isAllGoodStudentType); // false
let isAllStudentType = studentTypeWithScore.every((v, i) => v.score > 40)
console.log(isAllStudentType); // true
複製程式碼

Array.prototype.reduce

reduce() 方法對累計器和陣列中的每個元素(從左到右)應用一個函式,將其簡化為單個值。

let sum = studentTypeWithScore.reduce((acc, curVal) => acc + curVal.score, 0); // 100 + 85 + 65 + 50
console.log(sum); // 300
複製程式碼

其它技巧及建議

  1. 當僅僅是為了判斷字串中是否存在某子串時,使用 String.prototype.includes 代替 String.prototype.indexOf;
  2. 當僅僅是為了判斷陣列中是否存在某元素時,使用 Array.prototype.includes 代替 Array.prototype.indexOf;
  3. 儘可能地減少程式碼塊的巢狀;
  4. 儘可能使用 ES6 及更新的語法;

有時,程式碼優雅是建立在犧牲程式碼可讀性及效能之上的,魚與熊掌不可兼得,具體的實現方式還是需要根據實際場景來做不同的取捨。

公眾號不定時分享個人在前端方面的學習經驗,歡迎關注。

你的 JS 程式碼本可以更加優雅

相關文章