本文整理了9条JavaScript代码精简建议,涵盖async/await、异步控制流、解构赋值、真值假值判断、逻辑与三元运算符、可选链、类属性绑定及打包工具Parcel的使用。通过实际示例展示如何编写更简洁、高效且易读的现代JavaScript代码,帮助开发者提升编码质量。
引言
时光荏苒,JavaScript生态已历经多次迭代。即便已进入2019年,掌握编写整洁、高效且具备良好扩展性的代码技巧依然至关重要。
以下是九条旨在提升开发者编码能力的实用建议。
async / await 的使用
1. 使用 async / await
若你仍受困于回调地狱,请果断摒弃那些2014年风格的陈旧写法。除非存在特殊需求(如特定库强制要求回调,或出于极致的性能考量),否则应避免使用回调。虽然Promise表现优异,但随着代码规模膨胀,其链式调用往往显得臃肿。
我的推荐方案是 async / await,它能显著提升代码的可读性并简化逻辑。事实上,JavaScript中任何返回Promise的操作均可使用await,只要所用库支持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标准中引入,因此实际项目中通常需要对代码进行编译(transpile)处理。
异步控制流:for...of 与 Promise.all
2. 异步控制流
开发中常需获取多个数据集并进行处理,或在所有异步任务完成后执行特定逻辑。
for...of 循环
假设网页展示了一系列宝可梦,我们需要获取每只的详细信息。由于数据量未知,无法简单等待所有调用结束。若希望在获取部分数据后立即更新界面,可使用for...of遍历数组并在内部使用async代码块。但需注意,这种方式会阻塞后续执行直到所有调用完成,可能引发性能瓶颈,但这仍是一种可行的解决方案。
示例如下:
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)
上述示例均可直接运行,你可以将其复制到喜欢的代码沙盒中进行测试。
Promise.all
若需并行获取所有宝可梦数据,可await所有Promise,只需使用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. 解构与默认值
让我们回顾之前的示例:
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. 真值与假值
在使用默认值时,常需检查值是否存在。但利用真值和假值判断可进一步简化代码,节省字符并使逻辑更流畅。常见写法如下:
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. 逻辑运算符与三元运算符
这些运算符有助于缩减代码行数,保持代码整洁。虽然自动化工具能辅助代码规范,但过度依赖或频繁修改反而可能导致混乱。
逻辑运算符
逻辑运算符可组合两个表达式,返回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
结合前文关于真值假值的知识,逻辑运算符遵循以下规则:
- && :返回第一个值为假的表达式的值。若所有值均为真,则返回最后一个值为真的值。
- || :返回第一个值为真的表达式的值。若所有值均为假,则返回最后一个值为假的值。
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
三元运算符
三元运算符类似于逻辑表达式,由三部分组成:
- 比较部分,返回真值或假值;
- 第一个值,当比较结果为真时返回;
- 第二个值,当比较结果为假时返回。
示例如下:
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
可选链(Optional Chaining)
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
使用此方法检查嵌套属性既流畅又能使代码更整洁。
目前,optional chaining 尚未成为官方标准,但属于stage-1的实验性功能。需在babelrc中配置 @babel/plugin-proposal-optional-chaining 以启用它。
类属性与函数绑定
7. 类属性与绑定
在JavaScript中,函数绑定是一项常见任务。随着ES6标准引入箭头函数,我们现在可以自动以定义形式绑定函数——这一特性备受推崇,当前JavaScript开发者广泛使用。早期类定义时无法直接使用箭头函数,因为类需要特殊定义方式,通常需在构造函数中进行绑定(在React.js中推荐此做法)。
我原本厌恶先定义类方法再单独绑定的流程,但现在可通过箭头函数实现自动绑定。当前JavaScript允许直接在类中使用箭头函数。
示例如下,其中 _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 })
}
}
目前,类属性并非官方标准的一部分,但属于stage-3的实验性功能。必须在babelrc中添加 @babel/plugin-proposal-class-properties 才能使用它。
打包工具:Parcel
8. 使用 Parcel
作为前端开发者,打包和编译代码是必经之路。
长期以来,webpack是实践中的标准工具。我最初使用webpack v1时体验极差,需不断尝试各种配置选项,耗费无数小时使其运行。一旦配置完成便不敢再触碰,以免破坏现有功能。几个月后遇到Parcel,终于感到轻松。它几乎无需配置即可使用,同时保留自定义选项。它还支持插件(类似webpack和babel),且速度极快。
若你尚未了解Parcel,强烈建议尝试。
多写原生代码
9. 多写原生代码
这一点颇具启发性,也是我曾多次讨论的话题。
即使是CSS,许多人也倾向于使用现成库如Bootstrap。对于JavaScript,仍有大量开发者依赖jQuery及各类小型库进行表单验证、跑马灯等效果。虽然使用库无可厚非,但我强烈建议尽可能自行编写代码,而非依赖安装各种npm包。当然,大型库(甚至框架)如moment.js或react-datepicker需整个团队构建,自行实现不现实。
但其他大部分功能均可自行实现。这样做带来三个好处:
你清楚代码的具体内容;
在某个阶段你将真正理解编程原理,知晓内部工作机制;
可有效防止代码膨胀。
初期直接使用npm包确实便捷。自行实现某些功能耗时较多,但若安装的包无法正常工作且需更换方案,阅读其API将花费更多时间。而自行实现时,你可100%为项目量身定制。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。