如何用 es6+ 寫出優雅的 js 程式碼

gershonv發表於2018-12-07

相容 IE ?不存在的好嗎。

其實使用新語法配合 babel 的轉碼,已經可以解決這一些問題了。既然如此,那就多使用新語法去探索一下怎麼更好的去寫程式碼吧。

下面分享個人開發中常用的 js 寫法技巧,希望對各位有所幫助。

使用 let / const

var 命令會發生”變數提升“現象,即變數可以在宣告之前使用,值為 undefined。這種現象多多少少是有些奇怪的。

個人認為,對宣告的變數確定後面不會發生更改時,即使效能上沒有太大提升差異在,但使用 const, 程式碼的可讀性也會增強很多。

  • const 實際上保證的,並不是變數的值不得改動,而是變數指向的那個記憶體地址所儲存的資料不得改動。
  • let 變數指向的記憶體地址,儲存的只是一個指向實際資料的指標

補充 const 定義的變數不是資料不可變,而是儲存的引用地址不能發生改變。例子如下:

const person = { age: 22 }
person.age = 1

console.log(person.age ) // 1
複製程式碼

詳情看 let 和 const 命令

解構賦值

ES6 允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring)。

好處就是:解決了訪問多層巢狀的物件或陣列的命名,減少程式碼量

宣告多個變數:

// 宣告變數
let age = 22
let name = `guodada`
let sex = 1

// better
let [age, name, sex] = [22, `guodada`, 1]
console.log(age, name, sex) // 22, guodada, 1
複製程式碼

使用在物件中:

const obj = {
  name: {
    firstName: `guo`,
    lastName: `dada`
  }
}

// 提取變數
const firstName = obj.name.firstName
const lastName = obj.name.lastName

// better
const { firstName, lastName } = obj.name 
複製程式碼

使用在函式中:

// 在引數中結構賦值,獲取引數, 當引數多的使用時候十分方便
function Destructuring({ name, age }) {
  return { name, age } // 相當於 { name: name, age: age } , 可以簡寫
}

const params = { name: `guodada`, age: 22 }
Destructuring(params)
複製程式碼

更多用法見 變數的解構賦值

ES6 允許在物件之中,直接寫變數。這時,屬性名為變數名, 屬性值為變數的值。

function f(x, y) {
  return {x: x, y: y};
}

// better
function f(x, y) {
  return {x, y};
}
f(1, 2) // Object {x: 1, y: 2}
複製程式碼

擴充套件符的運用

es6 擴充套件符有很多用法,他可以使你的程式碼更加簡潔,易懂。這裡就舉例常用的用法

在物件中的用法:

let obj = {
  name: `guodada`,
  age: 22,
  sex: 1
}

// 複製物件。擴充套件符為淺複製!!!
const copy = { ...obj }

// 修改物件屬性值(生成新物件) 相當於 Object.assgin({}, obj, { age: 18 })
const newObj = { ...obj, age: 18 }

// 結合結構賦值
let { sex, ...z } = obj
z // { name: `guodada`, age: 22 }
複製程式碼

在陣列中的用法:

const arr = [1, 2, 3]
const arr2 = [4, 5, 6, 4]

// 複製陣列。擴充套件符為淺複製!!!
const newArr = [...arr] // ...[1, 2, 3] => 相當於展開陣列:1, 2, 3

// 合併陣列
const conbineArr = [...arr, ...arr2]

// 結合求最大值函式
Math.max(...arr)

// 結合 Set 實現陣列去重。注意:json 等物件陣列不可用
[...new Set(arr2)] // [4, 5, 6]
複製程式碼

擴充套件符的其他用法請自行查資料。

陣列用法

const arr = [1, 2, 3, 4]

Array.isArray(arr) // 判斷是否為陣列

arr.includes(2) // true 判斷陣列中是否包含某項

arr.findIndex(d => d === 3) // 2 找出第一個符合條件的陣列成員並返回陣列下標, 找不到返回 -1

arr.find(d => d === 3) // 3 找出第一個符合條件的陣列成員並返回, 找不到返回 undefined

// es5 其他還有 filter map forEach 等,這裡不做舉例。
arr.every(d => d > 2) // false 每一項都滿足條件則返回 true

arr.some(d => d > 2) // true 只要有一項滿足條件則返回 true
複製程式碼

find/findIndex : 找出第一個符合條件的陣列成員之後不再匹配,一定程度下優化查詢。
includes: 返回 true/false, 相較於 indexOf, 實用多了

  • flat() : 扁平化陣列,常用於將陣列轉化為一維陣列

    const arr = [1, 2, [3, 4]]
    
    arr.flat() // [1, 2, 3, 4] 扁平化陣列, 預設展開一層。
    
    const arr2 = [1, 2, [3, 4, [5, 6]]]
    
    arr2.flat() // [1, 2, 3, 4, [5, 6]]
    arr2.flat(2) // [1, 2, 3, 4, 5, 6] flat(3) 也是展開兩層...
    複製程式碼
  • flatMap(): 在陣列執行 map 方法後執行 flat, 用的不多,其實可以寫 map 後寫 flat 更好懂點。

    [2, 3, 4].flatMap(x => [x, x * 2]) //  [ 2, 4, 3, 6, 4, 8 ]
    // 1. [2, 3, 4].map(d => [d, d * 2]) => [[2, 4], [3, 6], [4, 8]]
    // 2. [[2, 4], [3, 6], [4, 8]].flat()
    複製程式碼

補充常用的物件轉陣列的用法:

const obj = { name: `guodada` }
  
Object.keys(obj) // [`name`]
Object.values(obj) // [`guodada`]
Object.entries(obj) // [[`name`, `guodada`]]
複製程式碼

模板字串

用的挺多的,注意不相容 IE !

const name = `guodada`

const newStr = `welcome ${name}` // welcome guodada

// the same as
const newStr = `welcome ` + name
複製程式碼

使用 async / await

async/await 實際上就是 generator 的語法糖, 主要用來解決非同步問題,具體網上很多文章都有介紹,這裡就不做多的解釋吧。

async function test() {
  const data = await axios.get(`https://randomuser.me/api/`)
  console.log(data)
}
// 等同於
function test() {
  axios.get(`https://randomuser.me/api/`).then(res => console.log(res)) // axios 也是 promise 物件
}

// 結合try/catch 
async function test() {
  try {
    const data = await axios.get(`https://randomuser.me/api/`)
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}
複製程式碼

ps 雖然好用,但是有時候適用場景不好,比如我們在拉取列表和使用者資訊需要同時進行時,await 後才執行下一條語句,這不是我們希望看到的。解決方法如下:

// 結合 Promise.all
const [result1, result2, result3] = await Promise.all([anAsyncCall(), thisIsAlsoAsync(), oneMore()])
複製程式碼

傳送門:async 函式

利用 class 封裝程式碼

主要是抽離程式碼邏輯,使得代複用性加強。同時,class 的形式會讓結構變得更加清晰,譬如:

class MyForm {
  /**
   * @func defaultLimit - 預設表單輸入限制條件, value 為空時返回 true
   * @param {Number} type - 代表表單型別的節點!
   * @param {String} value - 需要被驗證的值
   * @return Boolean
   * 
   * 根據 type 屬性對輸出進行驗證
   * 1 0≤x≤50 整數
   * 2 -1000≤x≤2000 整數
   * 3 1≤x 整數
   * 4 0≤x≤10
   */
  static defaultLimit(type, value) {
    const typeLimitMap = {
      1: /^(d|[1-4]d|50)$/g,
      2: /^-?(d{1,3}|1000)$|^(-|1d{3}|2000)$/,
      3: /^[1-9]d*$/,
      4: value => value <= 10 && value >= 0 // 0≤ x ≤ 10 可以為小數
    }
    if (!typeLimitMap[type] || !value) return true
    if (typeof typeLimitMap[type] === `function`) return typeLimitMap[type](value)
    else return typeLimitMap[type].test(value)
  }

  /**
   * @func translateLimit - 轉換操作符
   * @param {String} operator - 運算子
   * @param {*} value - 被匹配的值
   * @param {*} compareValue - 匹配的值
   * @return Boolean
   * `eq`: `=`
   * `ne`: `≠`
   * `gt`: `>`
   * `lt`: `<`
   * `ge`: `≥`
   * `le`: `≤`
   */
  static translateLimit(operator, value, compareValue) {
    const type = {
      eq: value === compareValue,
      ne: value !== compareValue,
      gt: value > compareValue,
      lt: value < compareValue,
      ge: value >= compareValue,
      le: value <= compareValue
    }
    if (!Object.keys(type).includes(operator) || !value || value === `-`) return true
    return type[operator]
  }

  // ...
}

export default MyForm
複製程式碼

使用:

import MyForm from `./MyForm`

MyForm.defaultLimit(1, 20)
複製程式碼
  • static :靜態屬性,類可以直接呼叫
  • constructor : 例項化類的時候呼叫,即 new MyForm(), 這裡沒用到

更多知識請閱 Class 的基本語法

優化 if/else 語句

當邏輯或||時,找到為 true 的分項就停止處理,並返回該分項的值,否則執行完,並返回最後分項的值。

當邏輯與&&時,找到為 false 的分項就停止處理,並返回該分項的值。

const a = 0 || null || 3 || 4
console.log(a) // 3

const b = 3 && 4 && null && 0
console.log(b) // null
複製程式碼

減少 if / else地獄般的呼叫

const [age, name, sex] = [22, `guodada`, 1]

if (age > 10) {
  if (name === `guodada`) {
    if (sex > 0) {
      console.log(`all right`)
    }
  }
}

// better 使用 &&
if (age > 10 && name === `guodada` && sex > 0) {
  console.log(`all right`)
}

// 或者(太長了不推薦)
age > 10 && name === `guodada` && sex > 0 && console.log(`all right`)
複製程式碼

提一下 react 的坑點, 在 render

render(){
  const arr = []
  return arr.length && null
}
// 渲染出 0 !
// Boolean / undefind / null / NaN 等才不會渲染。我們可以使用 !! 強制轉化為 boolean 解決這個問題
return !!arr.length && null

// 使用 && 控制元件的渲染
this.state.visible && <Modal />
複製程式碼

使用 Array.includes 來處理多重條件:

const ages = [18, 20, 12]

if (age === 18 || age === 12) {
  console.log(`match`)
}

// better
if ([18, 12].includes(age)) {
  console.log(`match`)
}
複製程式碼

如果是較少的判斷邏輯則可以使用三元運算子:

const age = 22
const isAdult = age >= 18 ? true : false // 這裡可以寫為 const isAdult = age > 18

const type = age >= 18 ? `adult` : `child`
複製程式碼

優化 switch/case 語句

switch/caseif/else 程式碼結構好點,但也和它一樣有時十分冗長。

這裡以自己實際專案中程式碼舉例:
有時我們可能需要對不同型別的欄位進行不一樣的正則驗證,防止使用者錯誤地輸入。譬如

const [type, value] = [1, `20`]
/**
 * 根據 type 屬性對輸出進行驗證
 * 1 0≤x≤50 整數
 * 2 -1000≤x≤2000 整數
 * 3 1≤x 整數
 */

function func1(type, value) {
  if (type === 1) {
    return /^(d|[1-4]d|50)$/.test(value)
  } else if (type === 2) {
    return /^-?(d{1,3}|1000)$|^(-|1d{3}|2000)$/.test(value)
  } else if (type === 3) {
    return /^[1-9]d*$/.test(value)
  } else {
    return true
  }
}

func1(type, value)

// 使用 switch/case
function fun2(type, value) {
  switch (type) {
    case 1:
      return /^(d|[1-4]d|50)$/.test(value)
    case 2:
      return /^-?(d{1,3}|1000)$|^(-|1d{3}|2000)$/.test(value)
    case 3:
      return /^[1-9]d*$/.test(value)
    default:
      return true
  }
}

func2(type, value)
複製程式碼

我們如何巧妙的解決這個程式碼冗長的問題呢,如下:

function func3(type, value) {
  const limitMap = {
    1: /^(d|[1-4]d|50)$/g,
    2: /^-?(d{1,3}|1000)$|^(-|1d{3}|2000)$/,
    3: /^[1-9]d*$/
  }
  return limitMap[type].test(value)
}
複製程式碼

利用物件去匹配屬性值,可以減少你的程式碼量,也使你的程式碼看起來更加簡潔。你也可以使用 Map 物件去匹配。

function func4(type, value) {
  const mapArr = [
    [1, /^(d|[1-4]d|50)$/g],
    [2, /^-?(d{1,3}|1000)$|^(-|1d{3}|2000)$/],
    [3, /^[1-9]d*$/]
  ]
  const limitMap = new Map(mapArr)
  return limitMap.get(type).test(value)
}
複製程式碼

Map 是一種鍵值對的資料結構物件,它的匹配更加嚴格。它會區分開你傳遞的是字串還是數字,譬如:

limitMap.get(1) // /^(d|[1-4]d|50)$/g
limitMap.get(`1`) // undefined
複製程式碼

更多詳見 Set 和 Map 資料結構

其他

  • 函式引數預設值
    function func(name, age = 22) {}
    // 等同於
    function func(name, age) {
      age = age || 22
    }
    複製程式碼
  • 使用 === 代替 ==。其實大家都懂這個的。。。
  • 箭頭函式,es6 最常用的語法。
  • return boolean
    const a = 1
    return a === 1 ? true : false
    // 多此一舉了,其實就等於
    return a === 1
    複製程式碼

敬請各位補充。交流才能進步,相視一笑,嘿嘿。

  • github – star 一下人生更美好
  • blog – 歡迎交流

相關文章