9個JavaScript小技巧:寫出更簡潔,高效代碼
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()
await 操作符用于等待一個Promise 對象。它只能在異步函數 async function 中使用。 async / await是屬于ES2017的內容,所以可能需要babel編譯你的代碼。不過現在的主流瀏覽器都已經支持了。
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(...)
注意當解構的時候,通常要賦給它一個默認值。這樣確保你不會得到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(...)
- }
為了用這些簡潔的判斷,你要充分理解js中真值,假值具體有哪些?這里概述一下:
假值:
1.字符串,但長度為0
2.數字0
3.false
4.undefined
5.null
6.NaN
真值:
1.空數組
2.空對象
3.其他有值的數據.注意:在判斷真/假值,還應該注意到你使用的是等于'==',還是全等'===',這經常會導致bug。對我而言,經常是數字0。
邏輯運算與三元運算符
邏輯運算
邏輯運算是基于多個表達式真假的判斷,注意到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}) // 0console.log(false && 'a') // falseconsole.log('2' && 5) // 5console.log([] || false) // []console.log(NaN || null) // nullconsole.log(true || 'a') // true
三元運算符
三元運算符和邏輯運算是相似的,但是它有3個部分:
condition ? expr1 : expr2
condition為進行條件判斷的部分,將會得到真值或者假值
expr1為條件判斷為真時返回的值
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 })
- }
- }
使用箭頭函數聲明類中方法,它現在處于 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,而且當需求發生改變時,你想要改動它是困難的。所以,你應該寫更多自己的組件。這將會帶來三個重要的優點:
- 你很清楚你的代碼發生了什么
- 同時,在你自己動手實現的過程中,你也能真正開始明白何為編程,以及庫中代碼是怎么運行的
- 你能阻止你的代碼變得臃腫
在開始,使用npm包是方便的。你能花更多時間去實現自己的業務邏輯。但當那個包沒有按照預期運行時,你又換了一個包,這時不得不花更多時間去讀它的API才能使用它。當是你自己實現的庫(組件)時,你能百分百用自己的用例來定制化開發它。
總結
文中提到的點其實都是基礎,但實用的。對于新手JavaScript開發者,其實我更建議應該開始學習typescript了。因為它實在太好了!