【譯】2019年給牛掰的 JavaScript 開發者的9條技巧

又一年過去了,JavaScript 也一直在改變。不過有些技巧能夠幫助你寫出簡潔高效可伸縮的代碼,即使是(或者說特別是)2019 年。下面 9 條實用小技巧能助你成爲一個更好的開發者。javascript

1.async / await

若是你仍深陷回調地獄,那麼你應該還在寫 2014 年以前的老古董代碼吧。除非頗有必要,好比遵照代碼庫要求或者出於性能緣由,不然不要使用回調方式。Promise 還行,但若是你的代碼日漸龐大,Promise 就顯得有些尷尬了。我如今的首選方案是 async / await,它讓代碼的閱讀與改進都變得簡潔不少。事實上,你能夠 await JavaScript 中的每個 Promise,若是你用的庫函數返回一個 Promise,就能夠簡單地 await 之。其實,async / await 只是使用 Promise 的語法糖。想讓你的代碼正常工做起來,你只須要在 funcion 前增長 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 函數中使用。java

async / await 是在 ES2017 中引入的,因此記得轉譯你的代碼。react

2.async control flow(異步控制流)

實際開發中不可避免地常常會遇到這種狀況,咱們要獲取多個數據項而後分別對它們進行某些處理(for…of,或者須要在全部異步調用都獲得返回值後再完成某項任務(Promise.all)webpack

for…of

比方說咱們要獲取頁面中幾個 Pokemon 的具體信息,咱們並不想等待全部調用所有完成,尤爲是有時候並不知道具體有多少次調用,但咱們想只要一有返回數據就當即更新數據項。這時候咱們就能夠用 for...of 來遍歷數組,在循環體內執行 async 代碼,代碼的執行會被暫停,直到每次調用成功。必須注意的是若是你在代碼中如示例這樣作,可能會帶來性能瓶頸,但把這個技巧收藏你的工具箱裏仍是很是有用的。示例以下:ios

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)
複製代碼

注:這些示例均可有效運行,可隨意複製粘貼到你喜歡的代碼沙盒內執行(如 jsfiddle、jsbin、codepen)。git

Promise.all

若是想並行獲取全部 Pokemon 的信息又該如何實現呢?既然 await 能夠用在全部 Promise 上,很簡單,用 Promise.allgithub

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+ 引入的,因此(必要的話)記得轉譯你的代碼。web

3.Destructuring & default values(解構賦值與默認值)

讓咱們返回到上一示例中,咱們是這樣作的:npm

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 values(檢測真假值)

在肯定是否要取默認值的時候,咱們每每會先對給定的值進行檢查,其中的某些檢查如今來講已經沒有必要了,將成爲歷史。不管如何,知道如何處理 真值(truthy values)和 假值(falsy values)老是很是好的。它能幫助咱們改進代碼,省去一些表達式,讓代碼更清晰明白。我常常看到有人這樣作:

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(...)
}
複製代碼

想要真正用好這些,你須要理解 真值假值 的含義。這裏有個小總結:

假值

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

真值

  • 空數組
  • 空對象
  • 全部其餘的東西

注意在檢測真假值時,這裏進行的是非嚴格比較,也就是說用的是 == 而不是 ===。通常說來,兩者行爲相同,但在某些特定狀況下會出現 bug。對我來講,常發生在數字 0 上。

5.Logical and ternary operators(邏輯運算符與三元運算符)

一樣,這也是精簡代碼的好方法。一般都能幫咱們簡化代碼,但也會帶來一些混亂,尤爲是鏈式使用時。

Logical operators

邏輯運算符主要用於鏈接兩個表達式,計算返回 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
複製代碼

咱們把邏輯運算符與上一個知識點真假值結合起來理解。當使用邏輯運算符時,聽從以下規則:

  • &&:返回第一個假值,若是沒有,則返回最後一個真值
  • ||:返回第一個真值,若是沒有,則返回最後一個假值
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
複製代碼

Ternary operator

三元運算符與邏輯運算符相似,但有三個部分:

  1. 比較表達式,計算返回真值或者假值
  2. 第一個返回值,用於表達式計算爲真值時返回
  3. 第二個返回值,用於表達式計算爲假值時返回

示例以下:

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 evening
複製代碼

6.Optional chaining(可選鏈式調用)

你是否遇到過這種問題,想要訪問嵌套對象的屬性,然而並不知道該對象或其中一個子屬性是否存在?你極可能會寫出相似這樣的代碼:

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
複製代碼

我認爲,這是一種讓代碼更清晰的檢查嵌套屬性的有效方法。

注:目前可選鏈式調用 (optional chaining) 還不是官方規範的一部分,是處於 stage-1 的實驗性特性。你須要在你的 balelrc 中添加插件 @babel/plugin-proposal-optional-chaining 來使用。

7.Class properties & binding(類屬性與綁定)

函數綁定在 JavaScript 中十分常見。隨着 ES6 規範中箭頭函數的引入,咱們如今有辦法自動綁定函數到定義時的上下文了,這種方法很是好用,被 JavaScript 開發者普遍使用。Class(類)剛剛引入的時候,你並不能真正的使用箭頭函數,由於類方法須要一種特定的聲明方式。咱們要在其餘地方綁定函數,如在構造器中(React.js 的最佳實踐)。我一直以爲先定義類方法而後再綁定的流程很煩人,一段時候事後再看更感受莫名其妙。有了類屬性語法,咱們又能夠用箭頭函數得到自動綁定的好處。箭頭函數如今能夠在類內使用了。示例以下,重點看 _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 })
    }
}
複製代碼

注:目前,class properties 並非正式官方規範的一部分,是處於 stage-3 的一個實驗性特性。須要在你的 balelrc 中添加插件 @babel/plugin-proposal-class-properties 來使用。

8.Use parcel

作爲前端開發者,你確定遇到過打包和轉譯代碼的狀況。wepback 成爲事實標準已經有很長一段時間了。我最初使用 webpack 時它還處於第一個版本,那時候很痛苦。我花了無數個小時去處理各類不一樣的配置項,讓項目打包運行。一旦能跑起來,我就不再會去動它們,怕又給弄壞了。幾個月前偶然發現的 parcel,讓我鬆了口氣。它提供的全部功能開箱即用,同時還容許咱們在必要時作出更改。它像 webpack 或者 babel 同樣支持插件系統,而且速度極快。若是你還沒聽過 parcel,牆裂建議去看看!

9.Write more code yourself

這是個很好的話題。關於這個問題,我有過不少不一樣的討論。即便是 CSS,有不少人也會傾向於使用組件庫,好比 bootstrap。JavaScript 的話,也有很多人使用 jQuery 和一些輕量代碼庫處理驗證、滑動效果等。雖然用庫也能夠理解,但我仍是牆裂建議本身編寫更多的代碼,而不是盲目地安裝 npm 包。對於那些整個團隊維護構建的大型代碼庫(或者框架),如 moment.js、react-datepicker,咱們我的嘗試去編寫是沒有什麼意義的。但能夠多寫一些只是本身項目使用的代碼。這樣對本身有三大好處:

  1. 你能確切地知道代碼中都作了什麼
  2. 在某種程度上,幫助本身開始真正理解什麼是編程以及程序底層是如何運做的
  3. 防止代碼庫變得更加臃腫

一開始,用 npm 包會顯得更簡單,本身去實現某些功能反而更費時間。但萬一這個包並無像預期的那樣工做,而後你不得不換另外一個,花更多的時間去閱讀如何使用新的 API。若是是本身實現,你能夠按本身的使用狀況 100% 量身定製。

相關文章
相關標籤/搜索