【譯】2019年JavaScript開發者應該都在用的9個棒的技巧

sundjly發表於2019-03-25

原文連結: 9 Tricks for Kickass JavaScript Developers in 2019
github的地址 歡迎star!
當然作者是在今年1月份寫的!我感覺大部分功能都已經使用很久啦,大家看看就好啦,就當總結回顧

前言

又一年過去了,JavaScript也一直在變化進步著。這兒列舉了一些小技巧幫你在2019年寫出更簡潔,高效的可擴充的程式碼。下面共列舉了9個講究使用的小技巧來幫助你成為更好的開發者。

1. async / await

如果你還陷入到回撥地獄中,那麼你應該回到2014年去開發你的程式碼。除非絕對必要(像第三方庫需要或者效能原因),否則不要使用回撥。Promise是非常好的解決回撥地獄,但是當你的程式碼變得越來越大時,它也會變得不太好用。我現在的解決方案就是async / await,它極大提高了程式碼可讀性以及簡潔性。在所有使用Promise的地方你都可以替換成await,在你需要返回Promise物件,簡單await它並返回,為了使它不報錯,你需要在定義函式的開頭新增async。事實上,async / await就是Promise的語法糖。下面就是一個簡單的例子:

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

getData()
// 對於在外部要使用getData中資料的話,是要 await getData()取到data的,async總是返回一個Promise物件
複製程式碼

await 操作符用於等待一個Promise 物件。它只能在非同步函式 async function 中使用。 async / await是屬於ES2017的內容,所以可能需要babel編譯你的程式碼。不過現在的主流瀏覽器都已經支援了。

另外推薦justjavac大神翻譯的await、return 和 return await 的陷阱以及這篇翻譯javascript-async-await-the-good-part-pitfalls-and-how-to-use可以更加詳細的理解相關知識。

2. 非同步控制流

經常地,我們會遇到這樣的需求,請求獲取多個資料集並對每個資料集進行各自處理或者需要等所有非同步回撥完成後返回一個值。遇到這些情況,我是這麼處理的:

for…of

假設我們的頁面有多個Pokemon(口袋妖怪),需要獲取到它們的詳細的資訊。我們不想等所有呼叫結束,特別是不知道它有多少次呼叫,我們僅想在它有呼叫返回時就更新我們的資料集。可以用for…of來遍歷陣列,在程式碼塊裡執行async,這樣的話,只有每次await執行成功,程式碼才會繼續往下執行。 這裡要著重說明,這樣做可能會導致效能瓶頸(當請求很多的時候),但像這樣做才能到達預期的效果。請看下面的例子:

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

這個程式碼是能正常執行,你可以輕鬆地複製它到 code sandbox執行。

Promise.all

如果你想同時獲取所有口袋妖怪的詳情呢?你需要等待所有的請求的完成返回,這時簡單使用Promise.all

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...of 和 Promise.all都是ES6以後提出來的,請確保你的環境能執行。

3. 解構(Destructuring ) & 預設值

我們接著上面的那個例子,提取一部分程式碼:

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
複製程式碼

這個例子起初看起來可能有點混亂,但是慢慢觀察。當我們沒有給函式傳遞引數的時候,就會使用預設值。一旦我們開始傳遞引數,僅會使用那些沒有傳遞的引數的預設值。這樣,減少了你對異常狀態的處理。

4. 真值 & 假值

當使用預設值,就可以不用對現有值進行一些額外的檢查。但是瞭解你的變數是真值還是假值是非常棒的。它能提高你的程式碼擴充套件性,更具有說服力的以及簡潔。我常看到下面一些寫法:

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!= NaN ,!myNumber是把範圍都擴大了,如果需求是隻要判斷是否NaN,還是應該要用isNaN的方法-->
複製程式碼

為了用這些簡潔的判斷,你要充分理解js中真值,假值具體有哪些?這裡概述一下:

假值:

  1. 字串,但長度為0
  2. 數字0
  3. false
  4. undefined
  5. null
  6. NaN

真值

  1. 空陣列
  2. 空物件
  3. 其他有值的資料 注意:在判斷真/假值,還應該注意到你使用的是等於'==',還是全等'===',這經常會導致bug。對我而言,經常是數字0。

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

邏輯運算

邏輯運算是基於多個表示式真假的判斷,注意到js是惰性求值的策略。邏輯運算一般返回一個布林值。&& 和 || 運算子會返回一個指定運算元的值。來看這裡:

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
複製程式碼

三元運算子

三元運算子和邏輯運算是相似的,但是它有3個部分: condition ? expr1 : expr2

  1. condition為進行條件判斷的部分,將會得到真值或者假值
  2. expr1為條件判斷為真時返回的值
  3. expr2為條件判斷為假時返回的值

例如:

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

過去在 Object 屬性鏈的呼叫中,很容易因為某個屬性不存在而導致之後出現Cannot read property xxx of undefined的錯誤。為了確認需要向這樣處理:

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
複製程式碼

我認為它是檢查巢狀屬性最佳方法,程式碼是如此的簡潔。

這個特性可以說是非常實用了,不過它現在處於 stage-1 階段。你可以在.babelrc檔案中引入 @babel/plugin-proposal-optional-chaining外掛來使用它。

7. Class properties & binding

在JavaScript中函式繫結也是經常的工作任務。現在,大家應該都是用箭頭函式自動繫結this到這個類上的(這裡可能有歧義,首先箭頭函式裡面是沒有this 和arguments的,這裡的this把它當成一個引數就行)。如果不用箭頭函式,我們就需要在建構函式繫結this,當類的方法很多的時候,這就顯得很冗餘。因此,建議和提倡在類裡面用箭頭函式。如:

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 })
    }
}
複製程式碼

關於必須bind傳參的情況可以看看我的另外一篇文章

使用箭頭函式宣告類中方法,它現在處於 stage-3 階段。你可以在.babelrc檔案中引入@babel/plugin-proposal-class-properties外掛來使用它。

8.使用parcel

作為一個前端,你也肯定會遇到打包和編譯程式碼情況。似乎webpack成為標準已經很長時間了。我從webpack 1版本就開始使用它了,當然那是痛苦的。為了弄懂它所有的配置項,我花了無數的時間才讓它正常打包和編譯。如果還有選擇的機會,我是不會學習webpack的。恰巧幾個月前用了parcel,從來沒有發現配置可以如此簡單!你不需要改動什麼便能得到你預期的效果,當然你也可以修改配置項。它也是和webpack或babel一樣是可配置的,同時也是快速的。如果你不知道parcel,我明確建議你去學習它!

當然,現在的主流標準還是webpack,webpack 4之後配置也簡潔了,可以在學習parcel之後瞭解webpack,不說了,又要開始學習webpack 5

9. 寫更多你自己的程式碼

談到這個話題,我有很多想要分享討論的東西。對於css,許多人更傾向於使用第三方元件庫像bootstrap。對於JavaScript,我也看到很多人喜歡用jQuery以及一些小型的驗證,滾動的庫等等。雖然使用庫是方便的,但是我強烈建議你能自己實現它,而不是盲目的安裝npm包。當它變成一個很大的庫或者框架的時候,整個團隊都要構建它,像moment.js或者react-datepicker,而且當需求發生改變時,你想要改動它是困難的。所以,你應該寫更多自己的元件。這將會帶來三個重要的優點:

  1. 你很清楚你的程式碼發生了什麼
  2. 同時,在你自己動手實現的過程中,你也能真正開始明白何為程式設計,以及庫中程式碼是怎麼執行的
  3. 你能阻止你的程式碼變得臃腫

在開始,使用npm包是方便的。你能花更多時間去實現自己的業務邏輯。但當那個包沒有按照預期執行時,你又換了一個包,這時不得不花更多時間去讀它的API才能使用它。當是你自己實現的庫(元件)時,你能百分百用自己的用例來定製化開發它。

我非常贊同這個的評論:很多人被'不要重複造輪子'的格言所困擾,因為他們把它理解成'不要重複實現輪子',對於一個熱愛程式設計的人,總是要有好奇心和不斷鑽研的,希望大家和我一樣謹記!

實現自己的輪子是多麼有成就感的一件事情! 後續我也會把自己的輪子放在github上。

總結

文中提到的點其實都是基礎,但實用的。對於新手JavaScript開發者,其實我更建議應該開始學習typescript了。因為它實在太好了!這裡給出了自己學習typescript一些歷程

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!

相關文章