[譯] 優秀 JavaScript 開發人員應掌握的 9 個技巧

JintNiu發表於2019-01-22

[譯] 優秀 JavaScript 開發人員應掌握的 9 個技巧
Photo by Andrew Worley on Unsplash

原文連結:9 Tricks for Kickass JavaScript Developers in 2019
原文作者:Lukas Gisder-Dubé
譯者:JintNiu
推薦理由:JavaScript 已經成為了當今使用最為廣泛、最受歡迎的語言之一,掌握一些使用技巧不僅可以提高開發效率,更有利於思維轉換。


過去的一年, JavaScript 在持續變化著,其使用範圍也越來越廣。接下來,我將針對 JavaScript 的使用,列出 9 條 建議,以幫助你寫出更加整潔高效的程式碼,成為更好的開發者。

1. async/await

JavaScript 極速發展的今天,回撥地獄所產生的問題已不復存在。實際開發過程中我們應當儘量避免使用回撥函式,除非為了遵守程式碼庫規則或是維護效能。而解決回撥地獄的一個常用方法為 Promise,但在程式碼量較多時使用會適得其反。於是提出了 async / await,使程式碼結構更加清晰明瞭,便於閱讀和維護。一般而言,可以 await 任何 Promise 以防止正使用的庫的返回值為 Promise ,也就是說 async/awaitPromise 的語法糖,而且使用方法也十分簡單:在函式前加 async。下面是一個簡單的例子:

async function getData() {
    const result = await axios.get('https://dube.io/service/ping')
    const data = result.data
    console.log('data', data)
    return data
}
getData()
複製程式碼

await 只能使用在 async 函式中,不能用於全域性作用域。

async/await 是 ES2017 中引入的,使用時請進行轉換。

2. 非同步控制流

當我們進行非同步呼叫並獲得返回值時,通常期望直接獲取多個資料集,並且分別操作每個資料集。因此有了以下方式:

for...of

假設頁面上要展示 Pokemon 資料,可以通過 axios 獲取它們的詳細資訊,我們所期望的是在得到返回值時立即更新頁面中的所有資料,而不是等所有呼叫完成後才進行更新。

我們可以使用 for...of 解決上述問題。 首先迴圈遍歷陣列,並在每個迴圈內執行非同步程式碼,當所有呼叫都成功時跳出迴圈。需要注意的是,這種方法雖然會對效能產生一些影響,但也不乏是一個很好的方法。

以下是一個例子:

import axios from 'axios'

let myData = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]

async function fetchData(dataSet) {
    for (entry of dataSet) {
        const result = await axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
        const newData = result.data
        updateData(newData)
        console.log(myData)
    }
}
function updateData(newData) {
    myData = myData.map(el => {
        if (el.id === newData.id) return newData
        return el
    })
}
fetchData(myData)
複製程式碼

可以將這些例子複製貼上到編輯器中除錯執行。

譯者注:除了迴圈本身帶來的效能問題之外,在使用 async/await 處理非同步請求時也會對效能造成影響:如果使用過多 await 語句,而且候這些語句並不需要依賴於之前的語句,則會產生 async/await 地獄,影響效能。

Promise.all

如果想要並行獲取所有的 Pokemon,我們可以使用 Promise.all 方法來 await 所有 Promise


import axios from 'axios'
  
let myData = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
async function fetchData(dataSet) {
    const pokemonPromises = dataSet.map(entry => {
        return axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
    })
    const results = await Promise.all(pokemonPromises)
    results.forEach(result => {
        updateData(result.data)
    })
    console.log(myData)
}
function updateData(newData) {
    myData = myData.map(el => {
        if (el.id === newData.id) return newData
        return el
    })
}
fetchData(myData) 

複製程式碼

for...ofPromise.all 都是 ES6+ 引入的,使用時請進行轉換。

3. 解構賦值 & 預設值

回到上個例子:

const result = axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
const data = result.data
複製程式碼

現在有一種更簡單的方法來實現它:通過解構賦值的方式從物件或陣列中獲取一個或多個值:

const { data } = await axios.get(...)
複製程式碼

也可對變數重新命名:

const { data: newData } = await axios.get(...)
複製程式碼

另一種方法是在解構賦值時指定預設值,這樣做可以確保程式碼不會出現 undefined,也避免手動檢查變數的麻煩。

const { id = 5 } = {}
console.log(id) // 5
複製程式碼

這些方法也可以用於函式引數,例如:

function calculate({ operands = [1, 2], type = 'addition' } = {}) {
    return operands.reduce((acc, val) => {
        switch (type) {
            case 'addition':
                return acc + val
            case 'subtraction':
                return acc - val
            case 'multiplication':
                return acc * val
            case 'division':
                return acc / val
        }
    }, ['addition', 'subtraction'].includes(type) ? 0 : 1)
}
console.log(calculate()) // 3 
console.log(calculate({ type: 'division' })) // 0.5 
console.log(calculate({ operands: [2, 3, 4], type: 'multiplication' })) // 24 
複製程式碼

ES6 引入瞭解構賦值和預設值,使用時請進行轉換。

4. 真值和虛值

當我們使用預設值時,通常要對現有值進行一系列判斷,這種方法使程式碼變得異常繁瑣,而現在我們可以真值(Truthy)和虛值(Falsy)的方式來改進它,不僅可以節省程式碼量,還使人更加信服。

以下是之前的做法:

if (myBool === true) {
    console.log(...)
}
// OR
if (myString.length > 0) {
    console.log(...)
}
// OR
if (isNaN(myNumber)) {
    console.log(...)
}
複製程式碼

簡化後:

if (myBool) {
    console.log(...)
}
// OR
if (myString) {
    console.log(...)
}
// OR
if (!myNumber) {
    console.log(...)
}
複製程式碼

以下為 FalsyTruthy 的概念:

Falsy

  • 長度為0的字串
  • 數字 0
  • false
  • undefined
  • null
  • NaN

Truthy

  • 空陣列
  • 空物件
  • 其他

使用真值和虛值時沒有確切的比較方式,這類似於我們進行比較時常使用雙等號 == 而不是三等號 ===。一般而言,這兩者的判定方式相同,但在某些情況下也會遇到一些錯誤,對我來說主要為數字 0

[譯] 優秀 JavaScript 開發人員應掌握的 9 個技巧
Photo by Philippe Leone on Unsplash

5. 邏輯運算子和三元運算子

邏輯運算子和三元運算子主要用於精簡程式碼,有助於保持程式碼整潔度,但當他們形成運算鏈時會顯得雜亂。

邏輯運算子

邏輯運算子:和(&&)、或(||),一般用於比較兩個表示式,返回值為: truefalse 或著它的匹配值。如下例:

console.log(true && true) // true
console.log(false && true) // false
console.log(true && false) // false
console.log(false && false) // false
console.log(true || true) // true
console.log(true || false) // true
console.log(false || true) // true
console.log(false || false) // false
複製程式碼

我們可以將邏輯運算子與真值和虛值的相關知識結合起來。

如果有表示式 AB,針對兩種邏輯運算子,有以下規則:

  • A && B : 當 Afalse 時則直接返回 A 的值 ;否則返回 B 的值。
  • A || B : 當 Atrue 時則直接返回 A 的值 ;否則返回 B 的值。

譯者注:上述規則為邏輯運算中的短路現象。

console.log(0 && { a: 1 }) // 0
console.log(false && 'a') // false
console.log('2' && 5) // 5
console.log([] || false) // []
console.log(NaN || null) // null
console.log(true || 'a') // true
複製程式碼

三元運算子

三元運算子與邏輯運算子非常相似,但有由三個部分組成:

  1. 條件表示式:其結果為真值或是虛值
  2. 返回值 1:條件表示式為真值時,返回該值
  3. 返回值 2:條件表示式為虛值時,返回該值

例如:

const lang = 'German'
console.log(lang === 'German' ? 'Hallo' : 'Hello') // Hallo
console.log(lang ? 'Ja' : 'Yes') // Ja
console.log(lang === 'French' ? 'Bon soir' : 'Good evening') // Good eveing
複製程式碼

6. 自判斷連結

當訪問某個巢狀物件的屬性時,由於不能確定目標物件或者屬性性是否存在,而需要進行一系列判斷:

let data
if (myObj && myObj.firstProp && myObj.firstProp.secondProp && myObj.firstProp.secondProp.actualData)
    data = myObj.firstProp.secondProp.actualData
複製程式碼

顯而易見,程式碼變得非常臃腫難看。而自判斷連結(optional chaining)的提出,正好可以滿足對巢狀屬性的校驗需求,並使程式碼更加清晰整潔。如下例:

const data = myObj?.firstProp?.secondProp?.actualData
複製程式碼

譯者注:自判斷連結: 檢查一個物件上面是否存在某屬性。
出現原因:呼叫某 Object屬性鏈中的某個屬性時,如果該屬性不存在,會導致 Cannot read property xxx of undefined 錯誤。於是自判斷連結 ?. 出現。
使用方式:obj?.a?.b?.c。依次對程式碼中的屬性進行判斷,如果為 null 或者 undefined 時,結束呼叫,返回 undefined

目前,自判斷連結還未納入官方規範中,只處於第一階段的實驗特性。您需要在 babelrc 中新增 @ babel / plugin-proposal-optional-chaining 後方可使用它。

7. 類屬性 & 繫結

JavaScript 中經常會用到繫結(bind)。ES6 規範中箭頭函式的引入,使 JavaScript 開發人員有了一種將函式自動繫結到執行上下文中的常用方法,同時這種方法非常重要。

由於 JavaScript 中的類方法有特定的呼叫方式,因此當我們首次宣告一個類時不能使用箭頭函式,因此需要在其他位置進行函式繫結,比如在建構函式中(以 React.js 為例)。工作當中我總是先定義類方法再對其進行繫結,這種方法非常繁瑣且容易出錯。但如果使用 class 語法,我們可以通過箭頭函式自動繫結它。以下是繫結 _increaseCount 的例子:

 class Counter extends React.Component {
    constructor(props) {
        super(props)
        this.state = { count: 0 }
    }
    render() {
        return (
            <div>
                <h1>{this.state.count}</h1>
                <button onClick={this._increaseCount}>Increase Count</button>
            </div>
        )
    }
    _increaseCount = () => {
        this.setState({ count: this.state.count + 1 })
    }
} 
複製程式碼

目前,類屬性還未納入官方規範中,只處於第三階段的實驗特性。您需要在 babelrc 中新增 @ babel / plugin-proposal-class-properties 後方可使用。

8. 使用 ParcelJS

作為前端開發人員,保證會有打包專案或著轉換程式碼的需求,對此,webpack 已經在很久之前提出先關規範了。第一次使用 webpack v1.0 時,我花了很長時間進行配置,雖然最終執行成功,但整個過程非常痛苦,而且成功後的我變得畏手畏腳,生怕破壞之前的配置。直到幾個月前,ParcelJS 的發現使我心情大好,在提供開箱即用功能的同時,它還實現了按需配置,也可以支援類似於 webpack 或 babel 的外掛系統,最重要的是它的速度極快。

譯者注:ParcelJS 官網顯示,parcelJS 的打包速度比 webpack 快 2 倍以上。

9. 封裝自己的元件庫

這是一個非常有趣的話題,關於它我有很多的想法。對於 CSS,很多人更傾向於使用類似於 BootStrap 這樣的元件庫。而對於 JavaScript,仍然有人呼叫 jQuery 或者其他庫來實現驗證、滑塊等功能。首先不否認使用各種庫的好處,但還是強烈建議可以親手實現這些功能,而不是盲目地安裝 npm 包。當整個團隊正構建一個類似於 moment.jsreact-datepicker 的大型庫(甚至框架)時,你沒必要親手實現它,但可以封裝為屬於自己的元件庫,而且在實現元件庫的同時,您可以:

  1. 準確掌握程式碼的結構以及執行機制
  2. 真正理解程式設計及其工作原理
  3. 防止程式碼庫變得臃腫

直接使用 npm 包是當然非常容易,但如果想要實現某些 npm 包中不具備的功能時則會需要更多的時間:如果軟體沒有按預期正常工作,或者要將其轉換為另一個軟體包,您將會花費更多時間來了解其 API 的配置方式。因此,您可以為自己量身定做一套資料自己的元件庫。


關於作者:Lukas Gisder-Dubé 元件並領導了一家初創公司,期間建立了自己的技術團隊,並任職 CTO 一年半。 離開創業公司後,在 Ironhack 擔任首席講師。如今在柏林正建立一家創業諮詢公司。檢視 dube.io 以瞭解更多資訊。

[譯] 優秀 JavaScript 開發人員應掌握的 9 個技巧


翻譯參考

  1. 怎樣處理 async/await 浪費效能問題
  2. Async/Await 優於 Promise 的 6 個理由
  3. MDN - Falsy
  4. MDN - Truthy
  5. Optional Chaining for JavaScript

相關文章