提高程序代码质量的七大JavaScript优秀实践

译文
开发 前端
为了便于您编写出更简洁、更易读的程序代码,本文将在总结和归纳ECMAScript最新功能的基础上,为您提供提高JavaScript和Node.js代码质量的七种优秀实践。

[[397666]]

【51CTO.com快译】自2015年以来,随着ECMAScript 6(简称ES6)的发布,每年都有新版本的ECMAScript规范面市。而每次迭代都为该语言添加新的功能、新的语法、以及新的质量改进。为此,大多数浏览器和Node.js中的JavaScript引擎都需要迎头赶上。就连程序员的编程习惯、以及代码组织方法,也需要与时俱进,以提高整体的代码质量,并方便后期的维护。

为了便于您编写出更简洁、更易读的程序代码,本文将在总结和归纳ECMAScript最新功能的基础上,为您提供提高JavaScript和Node.js代码质量的七种优秀实践。

1.块作用域声明(Block Scoped Declarations)

自从该语言问世以来,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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

由于已定义的变量var并非块作用域(block-scoped),因此如果在小作用域内被重新定义,它们就会影响到外部作用域的值。

然而,如果我们用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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

当然,const与let在语义上有所不同,那些用const声明的变量,无法在其作用域内被重新分配(如下代码段所示)。不过,这并不意味着它们是不可改变的,而只是代表着它们的引用不能被更改。

const x = [] 
  
x.push("Hello""World!"
x // ["Hello""World!"
  
x = [] // TypeError: Attempted to assign to readonly property. 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

2.箭头函数(Arrow Functions)

作为新近被引入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.
  • 2.
  • 3.
  • 4.
  • 5.

如上述示例所示,“=>”后面的函数以一种简洁的语法,替换了传统函数。

  • 如果函数主体是单个表达式,则已经隐含了作用域括号{}和return关键字,所以无需额外写入。
  • 如果函数只有一个参数,则也已经隐含了参数括号(),同样无需额外写入。
  • 如果函数体的表达式是一套字典(dictionary),则必须将其括入括号()中。

箭头函数的另一个优势在于:为了避免由于使用this关键字,而引起的诸多不便,箭头函数并不会定义作用域,而是会存在于其父作用域中。也就是说,箭头函数没有任何针对this的绑定。在箭头函数中,this的值与父作用域中的值是相同的。因此,箭头函数不能被用作各种方法或构造函数。它们既不适用于apply、bind或call,也没有针对super的绑定。

此外,箭头函数还会受到诸如:缺少可供传统功能访问的arguments对象,以及缺少函数体中的yield等其他限制。

可以说,箭头函数并非是与标准函数的1:1替代,而是向JavaScript中添加了额外的功能集。

3.可选链(Optional Chaining)

让我们来试想一个类似person对象的、具有深层嵌套的数据结构。业务应用需要访问到该对象的名字和姓氏。由此,我们可以编写出如下JavaScript代码:

public class HelloWorld { 
    public static void main(String[] args) { 
        System.out.println("Hello World"); 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

然而,如果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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

对此,开发人员往往需要通过如下代码,来予以解决。显然,这些代码不但冗长难写,而且可读性较差。

person && person.name && person.name.first // undefined 
  • 1.

而作为JavaScript的一项新功能,可选链的语法允许您访问嵌套得更深的对象属性,而不用担心属性是否真的存在。也就是说,如果可选链在挖掘过程遇到了null或undefined的值,那么就会通过短路(short-circuit)计算,返回undefined,而不会报错。

person?.name?.first // undefined 
  • 1.

如上述代码所示,其结果代码简洁且明了。

4.空值合并(Null-ish Coalescing)

在引入空值合并运算符之前,在输入为空的情况下,JavaScript开发人员需要使用OR运算符--||,以回退到默认值。这就会导致:即使出现合法的、但属于虚假值(falsy values),也会被回退到默认值的情况。

function print(val) { 
    return val || 'Missing' 

  
print(undefined) // 'Missing' 
print(null)      // 'Missing' 
  
print(0)         // 'Missing' 
print('')        // 'Missing' 
print(false)     // 'Missing' 
print(NaN)       // 'Missing' 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

如今,JavaScript推出了null合并运算符--??。它能够保证只有在前面的表达式为null-ish的情况下,才会触发回退。值得注意的是,此处的空值是指null或undefined。

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

  
print(undefined) // 'Missing' 
print(null)      // 'Missing' 
  
print(0)         // 0 
print('')        // '' 
print(false)     // false 
print(NaN)       // NaN 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

如此,您可以确保自己的程序能够接受虚假值作为合法输入,而不会最终被回退。

5.逻辑赋值(Logical Assignment)

假设您需要先判断是否为空,再为变量分配数值,那么如下代码便展示了这样的基本逻辑:

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

  • 1.
  • 2.
  • 3.

如果您熟悉上面提到的短路计算的工作原理,则可能会使用null-ish合并运算符(coalescing operator),将上述三行代码替换为如下更简洁的版本。

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

由上述代码可知,如果x为null-ish的话,我们可以使用null-ish合并运算符的短路功能,来执行第二部分(x = y)。这段代码虽然非常简洁,但是不太容易被阅读或理解。而我们完全可以使用如下代码,根据逻辑上的null-ish分配,来消除此类变通方法。

x ??= y // x = y if x is nullish, else no effect 
  • 1.

同样,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 
  • 1.
  • 2.

专家建议:如果您有过Ruby的编程经验,那么您会一眼识别出||=和&&=运算符。毕竟Ruby并没有虚假值的概念。

6.已命名捕获组(Named Capture Groups)

不知您是否知晓正则表达式中的“捕获组”的相关概念?如下面的代码段所示,它是与括号中的正则表达式部分匹配的字符串。

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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

一直以来,正则表达式都能够支持已命名捕获组。这是一种通过引用名称、而非索引,来捕获各个组的方式。目前,在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' 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

而且,新的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 { yearmonthday } = result.groups 
  
year  // '2020' 
month // '03' 
day   // '14' 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

在上述代码段中,针对fetch的调用返回了一个状态为“待处理(pending)”的Promise。而当API返回响应时,它将会转换为“已实现(fulfilled)”的状态。在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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

2017年,JavaScript推出了两个新的关键字async和await,来使得Promises的处理和使用变得更加容易和流畅。当然,它们并非Promises的替代,只是Promises概念之上的语法增强。

而且,并非让所有的代码里都出现在一系列的“then”函数,await旨在让其更像同步的JavaScript。您可以使用带有await的try...catch,来代替直接使用Promise的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 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

当然,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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

小结

如您所见,JavaScript每年都会在其语言中加入新的功能。希望我们在上面介绍到的七项提到代码质量的优秀实践,能够对您的日常编程提供帮助。

原文标题:7 JavaScript Best Practices to Improve Code Quality,作者:Dhruv Bhanushali

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

 

责任编辑:华轩 来源: 51CTO
相关推荐

2020-11-23 18:39:54

容器Kubernetes架构

2020-08-12 07:00:00

开发代码技术

2022-05-16 08:45:05

数据质量数据安全

2014-11-07 16:57:21

程序员

2022-05-26 08:00:00

Linux白板应用程序

2022-11-01 15:13:41

2021-05-24 09:00:00

ETL工具数据

2023-07-11 15:57:15

数据产品产品经理

2023-02-21 14:55:40

React开发技巧

2024-10-11 06:00:00

Python代码编程

2012-08-06 10:34:26

JavaScript框架

2019-01-31 09:02:56

网页抓取设计模式数据

2024-07-31 16:04:14

2012-08-06 10:51:40

JavaScript

2012-09-07 09:27:01

2024-05-07 14:46:56

IT领导力CIO

2021-09-30 14:06:08

安全团队网络攻击首席信息安全官

2014-10-13 10:36:15

程序员职场价值技巧

2024-05-08 15:44:07

CIO

2009-08-31 16:28:35

程序开发语言
点赞
收藏

51CTO技术栈公众号