【JavaScript】提高JavaScript代码质量的7个最佳实践

注:转载于 微信公众号的『前端新世界』原创翻译:

被第3、4、5条 实践惊艳到了:推荐!!!

本文翻译自:
https://dev.to/i5han3/git-commit-message-convention-that-you-can-follow-1709

自2015年以来,随着ES6的发布,每年都会发布ECMAScript规范的新版本。每次迭代都为该语言添加了新的功能、新的语法和高质量的改进。接着大多数浏览器和Node.js中的JavaScript引擎也会很快追赶上来,所以我们的代码也应该与时俱进才可以——因为伴随着JavaScript的每一次新迭代,都会出现表达代码的新习惯和新方式,而且很多时候,这些更改可能会使得更易于我们维护代码。

下面有一些最新的ECMAScript功能,可以用来帮助我们编写更简洁,更明确和更易读的代码。

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定义的变量不是块范围的,因此在较小的作用域内重新定义它们会影响外部作用域的值。

现在,我们有了两个新的关键字来替换var,即letconst,它们就没有这方面的缺陷。

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

constlet的语义不同:用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

在以上所有示例中,因以独特的箭头=>而命名的箭头函数用简洁的语法替换了传统函数。

  1. 如果函数主体是单个表达式,则不需要写范围括号{}return关键字。
  2. 如果函数只有单个参数,则不需要写参数括号()
  3. 如果函数主体表达式是字典(dictionary),那必须将它封闭在括号()内。

箭头函数的另一个重要优点是它们不定义范围,而是存在于父范围内。这避免了使用this关键字可能会引起的许多陷阱。箭头函数对this没有绑定。在箭头函数内部,this的值与在父范围中的相同。因此,箭头函数不能用作方法或构造函数。箭头函数不适用于applybindcall,也没有对super的绑定。

它们还有其他的一些局限性,例如缺少传统函数可以访问的参数对象以及无法从函数主体yield

因此,箭头函数不能百分百替代标准函数,但是完全可以添加到JavaScript的功能集中。

3. 可选链

假设有一个深层嵌套的数据结构,例如此处的person对象。想好你要访问的那个人的名和姓,你可以像这样编写代码:

person = {
    
    
  name: {
    
    
    first: 'John',
    last: 'Doe',
  },
  age: 42
}
person.name.first // 'John'
person.name.last  // 'Doe'

现在想象一下,如果person对象不包含嵌套的name对象,会发生什么。

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的一项新功能,可以避免我们陷入这种尴尬的境地。可选链只要遇到nullundefined的值,就可以在未引发错误的情况下返回未undefined

person?.name?.first // undefined

代码简洁明了。

4. Nullish合并

在引入Nullish合并运作符之前,JavaScript开发人员使用OR运算符||(如果缺少输入)回退到默认值。这伴随着一个重要的警告:即使合法但falsy的值也会导致回退到默认值。

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-ish,它只会导致回退。这里的null-ish表示nullundefined的值。

function print(val) {
    
    
    return val ?? 'Missing'
}

print(undefined) // 'Missing'
print(null)      // 'Missing'

print(0)         // 0
print('')        // ''
print(false)     // false
print(NaN)       // NaN

这样一来,就可以确保如果程序接受falsy值作为合法输入,最后就不会回退。

转载 注:jsfalsy值: '', 0, -0, NaN, false, null, undefined.

5. 逻辑分配

假设,当且仅当变量的值为null-ish时,才为该变量分配一个值。逻辑上的写法如下:

if (x === null || x == undefined) {
    
    
    x = y
}

如果你知道JS短路的工作原理,则可能想要使用null-ish合并运算符将这三行代码改为更简洁的版本。

译者注:短路,即逻辑运算从左到右。逻辑或运算,当左边的条件成立时,后面的条件将不再参与运算。

x ?? (x = y) // x = y if x is nullish, else no effect

这里我们使用了null-ish合并运算符的短路功能来执行第二部分,如果x为nullish,则x = y。代码非常简洁,但理解起来仍然有点难度。逻辑null-ish分配消除了对这种变通方法的需要。

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没有falsy值的概念。

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 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\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 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\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. async和await

JavaScript的强大功能之一就是它的异步性。这意味着许多长时间运行或非常耗时的函数可以返回Promise而不阻塞执行。

const url = 'https://the-one-api.dev/v2/book'
let prom = fetch(url)
prom // Promise {<pending>}

// wait a bit
prom // Promise {<fullfilled>: Response}, if no errors
// or
prom // Promise {<rejected>: Error message}, if any error

调用fetch返回Promise,该Promise在创建时的状态为pending。很快,当API返回响应时,它将转换为fulfilled状态,并包装可以访问的Respond。在Promises世界中,你将执行以下操作来进行API调用并将响应解析为JSON。

const url = 'https://the-one-api.dev/v2/book'
let prom = fetch(url)
prom                               // Promise {<fullfilled>: 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宣布了两个新的关键字asyncawait,这使Promises的处理和使用变得更加轻松和流畅。它们不是Promises的替代品;它们只是强大的Promises概念之上的语法糖。

取代了将所有代码都发生在一系列then函数内,await让一切看起来像是同步的JavaScript。另外一个好处是,你可以将try ... catchawait结合使用,而不是像直接使用Promises那样处理catch函数中的错误。用await表示的相同代码如下所示。

const url = 'https://the-one-api.dev/v2/book'
let res = await fetch(url) // Promise {<fullfilled>: 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 {<fulfilled>: 6}
  .then(res => console.log(res) // prints 6

let res = await sum(1, 2, 3)    // Promise {<fulfilled>: 6} -await-> 6
console.log(res)                // prints 6

猜你喜欢

转载自blog.csdn.net/qq_38987146/article/details/119137736