自2015年以来,随着ECMAScript 6(简称ES6)的发布,每年都会发布新版本的ECMAScript规范。 每次迭代都会为语言添加新功能、新句子模式和新质量改进。 为了实现这一点,大多数浏览器和 Node.js 中的 JavaScript 引擎必须迎头赶上。 甚至程序员的编程习惯、代码组织方式也需要与时俱进,以提高整体代码质量,方便后期维护。
为了让您更轻松地编写出更加简洁易读的程序代码,本文将在总结总结 ECMAScript 最新特性的基础上,为您提供七个提升 JavaScript 和 Node.js 代码质量的优秀实践。
1. 块作用域声明
自该语言诞生以来,JavaScript 开发人员仍然使用 var 来声明变量。 但是,如以下代码片段所示,使用 var 关键字创建的变量的作用域存在问题。
var x = 10
if (true) {
var x = 15 // inner declaration overrides declaration in parent scope
console.log(x) // prints 15
}
console.log(x) // prints 15
由于定义的变量var不是块作用域的,如果在小作用域内重新定义,它们都会影响外层作用域的值。
然而,如果我们用两个新关键字let和const替换var,就可以避免这个缺陷(见下面的代码片段)。
let y = 10
if (true) {
let y = 15 // inner declaration is scoped within the if block
console.log(y) // prints 15
}
console.log(y) // prints 10
当然,const 和 let 在语义上是不同的。 用 const 声明的变量不能在其作用域内重新分配(如下面的代码片段所示)。 然而,这并不意味着它们是不可变的,只是它们的引用不能被修改。
const x = []
x.push("Hello", "World!")
x // ["Hello", "World!"]
x = [] // TypeError: Attempted to assign to readonly property.
2. 箭头函数
作为 JavaScript 新引入的一个重要特性,箭头函数有很多优点。 首先,它们使 JavaScript 函数看起来更简洁,更易于开发人员编写。
let x = [1, 2, 3, 4]
x.map(val => val * 2) // [2, 4, 6, 8]
x.filter(val => val % 2 == 0) // [2, 4]
x.reduce((acc, val) => acc + val, 0) // 10
如上例所示,“=>”后面的函数用简洁的句型代替了传统的函数。
箭头函数的另一个优点是,为了避免使用 this 关键字带来的不便,箭头函数不定义作用域,而是会存在于其父作用域中。 也就是说,箭头函数对此没有任何绑定。 在箭头函数中,this 的值与父作用域中的值相同。 因此,箭头函数不能用作任何类型的技巧或构造函数。 它们既不适用于 apply、bind 或 call,也没有 super 的绑定。
此外,箭头函数还受到其他限制,例如缺乏传统函数可访问的参数对象,以及函数体中缺乏yield。
可以说javascript 字符串替换,箭头函数并不是与标准函数的 1:1 替代,而是为 JavaScript 添加了额外的功能集。
3. 可选链接
让我们想象一个像 person 对象这样的深度嵌套的数据结构。 业务应用程序需要访问对象的名字和姓氏。 由此,我们可以编译以下 JavaScript 代码:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
但是,如果 person 对象不包含嵌套的 name 对象javascript 字符串替换,则可能会出现以下错误。
person = {
age: 42
}
person.name.first // TypeError: Cannot read property 'first' of undefined
person.name.last // TypeError: Cannot read property 'last' of undefined
对此,开发者往往需要使用如下代码来解决。 显然,这些代码不仅晦涩难写,而且可读性也较差。
person && person.name && person.name.first // undefined
作为 JavaScript 的一项新功能,可选链接语法允许您访问深度嵌套的对象属性,而无需担心该属性是否实际存在。 也就是说,如果可选链在挖矿过程中遇到空值或者未定义的值,会通过短路计算并返回未定义的值,而不会报错。
person?.name?.first // undefined
如上面的代码所示,生成的代码简洁明了。
4. 空合并
在引入空合并运算符之前,JavaScript 开发人员需要使用 OR 运算符 --|| 如果输入为空,则回退到默认值。 这会导致:即使存在合法但错误的值(falsy value),它们也会回落到默认值。
function print(val) {
return val || 'Missing'
}
print(undefined) // 'Missing'
print(null) // 'Missing'
print(0) // 'Missing'
print('') // 'Missing'
print(false) // 'Missing'
print(NaN) // 'Missing'
今天,JavaScript 引入了空合并运算符——??。 它保证只有当上面的表达式为 null 时才会触发回退。 值得注意的是,这里的null值是指null或者undefined。
function print(val) {
return val ?? 'Missing'
}
print(undefined) // 'Missing'
print(null) // 'Missing'
print(0) // 0
print('') // ''
print(false) // false
print(NaN) // NaN
这样,您可以确保您的程序只能接受虚假值作为合法输入,而不会最终被回滚。
5. 逻辑赋值
假设在给变量赋值之前需要判断是否为空,下面的代码展示了基本逻辑:
if (x === null || x == undefined) {
x = y
}
如果您熟悉上述泄漏估计的工作原理,您可以使用 null-ish 合并运算符,将上面三行代码替换为下面更简约的版本。
x ?? (x = y) // x = y if x is nullish, else no effect
从上面的代码可以看出,如果x为null-ish,我们可以使用null-ish合并运算符的泄漏函数来执行第二部分(x = y)。 这段代码看起来非常少,但并不容易阅读或理解。 我们可以使用以下代码根据逻辑空分配来删除此解决方法。
x ??= y // x = y if x is nullish, else no effect
类似地,JavaScript 还引入了逻辑 AND 形参 --&&= 和逻辑 OR 形参 --||= 的运算符。 这些运算符仅在满足某些条件时才会执行,否则无效。
x ||= y // x = y if x is falsy, else no effect
x &&= y // x = y if x is truthy, else no effect
专业提示:如果您使用 Ruby 进行过编程,那么您一眼就能认出 ||= 和 &&= 运算符。 毕竟 Ruby 没有假值的概念。
6. 命名捕获组
不知道你知道正则表达式中“捕获组”的概念吗? 如下面的代码段所示,它是一个匹配正则表达式括号中部分的字符串。
let re = /(d{4})-(d{2})-(d{2})/
let result = re.exec('Pi day this year falls on 2021-03-14!')
result[0] // '2020-03-14', the complete match
result[1] // '2020', the first capture group
result[2] // '03', the second capture group
result[3] // '14', the third capture group
正则表达式也始终支持命名捕获组。 这是一种通过引用组的名称而不是索引来捕获组的形式。 目前,在 ES9 中,该功能已由 JavaScript 实现。 如下面的代码片段所示,结果对象包含一个嵌套的组对象,其中每个捕获组的值也可以映射到其名称。
let re = /(?d{4})-(?d{2})-(?d{2})/
let result = re.exec('Pi day this year falls on 2021-03-14!')
result.groups.year // '2020', the group named 'year'
result.groups.month // '03', the group named 'month'
result.groups.day // '14', the group named 'day'
而且,新的API和JavaScript的结构分配函数可以完美结合(见下面的代码片段)。
let re = /(?d{4})-(?d{2})-(?d{2})/
let result = re.exec('Pi day this year falls on 2021-03-14!')
let { year, month, day } = result.groups
year // '2020'
month // '03'
day // '14'
7. 异步和等待
我们都知道异步性是 JavaScript 的一个强大特性。 许多可能长时间运行或更耗时的函数可以在不停止运行的情况下返回 Promise。
const url = 'https://the-one-api.dev/v2/book'
let prom = fetch(url)
prom // Promise {}
// wait a bit
prom // Promise {: Response}, if no errors
// or
prom // Promise {: Error message}, if any error
在上面的代码片段中,对 fetch 的调用返回一个状态为“pending”的 Promise。 当 API 返回响应时,它将转换为“已完成”状态。 在 Promises 中,您可以执行以下操作,通过 API 调用将响应解析为 JSON。
const url = 'https://the-one-api.dev/v2/book'
let prom = fetch(url)
prom // Promise {: Response}
.then(res => res.json())
.then(json => console.log(json)) // prints response, if no errors
.catch(err => console.log(err)) // prints error message, if any error
2017年,JavaScript引入了两个新的关键字async和await,让Promise的处理和使用变得更加简单和流畅。 当然,它们并不是 Promise 的替代品,而是 Promise 概念的改进。
此外,await 努力使其更像同步 JavaScript,而不是让所有代码出现在一系列“then”函数中。 你可以使用 try...catch 和await 来代替使用Promise 的catch 函数来直接处理错误。 下面是具有等效效果的等待代码。
const url = 'https://the-one-api.dev/v2/book'
let res = await fetch(url) // Promise {: Response} -await-> Response
try {
let json = await res.json()
console.log(json) // prints response, if no errors
} catch(err) {
console.log(err) // prints error message, if any error
}
当然,async 关键字也有“硬币的另一面”,它将任何要发送的数据封装到 Promise 中。 下面是一段专门用于通过异步函数添加多个数字的程序代码。 实际上,您的代码可能比这更复杂。
async function sum(...nums) {
return nums.reduce((agg, val) => agg + val, 0)
}
sum(1, 2, 3) // Promise {: 6}
.then(res => console.log(res) // prints 6
let res = await sum(1, 2, 3) // Promise {: 6} -await-> 6
console.log(res) // prints 6
概括
正如您所看到的,JavaScript 每年都会为该语言添加新功能。 希望我们在其中介绍的关于代码质量的七个良好实践可以对您的日常编程有所帮助。