原文連結: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/await
是 Promise
的語法糖,而且使用方法也十分簡單:在函式前加 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...of
和Promise.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(...)
}
複製程式碼
以下為 Falsy
和 Truthy
的概念:
Falsy
- 長度為0的字串
- 數字
0
false
undefined
null
NaN
Truthy
- 空陣列
- 空物件
- 其他
使用真值和虛值時沒有確切的比較方式,這類似於我們進行比較時常使用雙等號 ==
而不是三等號 ===
。一般而言,這兩者的判定方式相同,但在某些情況下也會遇到一些錯誤,對我來說主要為數字 0
。
5. 邏輯運算子和三元運算子
邏輯運算子和三元運算子主要用於精簡程式碼,有助於保持程式碼整潔度,但當他們形成運算鏈時會顯得雜亂。
邏輯運算子
邏輯運算子:和(&&
)、或(||
),一般用於比較兩個表示式,返回值為: true
、false
或著它的匹配值。如下例:
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
複製程式碼
我們可以將邏輯運算子與真值和虛值的相關知識結合起來。
如果有表示式 A
和 B
,針對兩種邏輯運算子,有以下規則:
A && B
: 當A
為false
時則直接返回A
的值 ;否則返回B
的值。A || B
: 當A
為true
時則直接返回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:條件表示式為虛值時,返回該值
例如:
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.js 或 react-datepicker 的大型庫(甚至框架)時,你沒必要親手實現它,但可以封裝為屬於自己的元件庫,而且在實現元件庫的同時,您可以:
- 準確掌握程式碼的結構以及執行機制
- 真正理解程式設計及其工作原理
- 防止程式碼庫變得臃腫
直接使用 npm 包是當然非常容易,但如果想要實現某些 npm 包中不具備的功能時則會需要更多的時間:如果軟體沒有按預期正常工作,或者要將其轉換為另一個軟體包,您將會花費更多時間來了解其 API 的配置方式。因此,您可以為自己量身定做一套資料自己的元件庫。
關於作者:Lukas Gisder-Dubé 元件並領導了一家初創公司,期間建立了自己的技術團隊,並任職 CTO 一年半。 離開創業公司後,在 Ironhack 擔任首席講師。如今在柏林正建立一家創業諮詢公司。檢視 dube.io 以瞭解更多資訊。