过去 5 年最酷的 JavaScript 特性

2025-06-10

过去 5 年最酷的 JavaScript 特性

Erik Qualman 认为,语言总是在不断发展。虽然他指的是自然语言,但编程语言也同样如此。JavaScript 自 1995 年诞生以来发生了巨大的变化,并新增了许多功能。本文探讨了过去 5 年 JavaScript 中新增的一些非常有用(但可能不太为人所知)的功能。这绝不是一个详尽的列表,诸如与类相关的功能等重大修订将在另一篇文章中讨论。

关于 ECMAScript 的简要说明

ECMA(欧洲计算机制造商协会)是一个负责为编程语言、硬件和通信提供规范和标准的组织。ECMAScript 是该组织的一部分,专注于脚本语言。换句话说,它为脚本语言(例如 JavaScript)的行为提供了“蓝图”。JavaScript 实现了这些规范,并且随着 ECMAScript 的发展,JavaScript 也在不断发展。为了实现一个新功能,需要由 TC39 委员会主持一个包含四个步骤的流程。

由于本文讨论的功能较新,部分功能可能并非所有浏览器都支持。如需检查浏览器兼容性,请查看此链接

现在,我们已经了解了所有这些,让我们深入了解这些功能。

String.padStart() 和 String.padEnd()

这些字符串方法是将字符串附加到其他字符串的快速简便的方法。顾名思义,它们String.padStart()会在给定字符串的开头添加一个新字符串,并String.padEnd()在给定字符串的结尾附加一个字符串。这些方法不会改变原始字符串。

String.padStart(所需字符串长度,stringToAdd)

  • desiredStringLength:您希望新字符串的长度(以数字表示)。
  • stringToAdd:这是您想要添加到原始字符串开头的字符串。

我们来看一个例子:


let originalString = 'Script';

let paddedString = originalString.padStart(10, 'Java');

console.log(paddedString);

// OUTPUT -->
// 'JavaScript'

Enter fullscreen mode Exit fullscreen mode

如果desiredStringLength参数比原始字符串 + stringToAdd的长度短,会发生什么情况?在这种情况下,stringToAdd在添加到原始字符串之前会被截断:


let originalString = 'Script';

let paddedString = originalString.padStart(7, 'Java');

console.log(paddedString);

// OUTPUT -->
// 'JScript'
// truncates the stringToAdd from 'Java' to 'J'

Enter fullscreen mode Exit fullscreen mode

如果desiredStringLength参数的长度大于原始字符串 + stringToAdd的长度,该怎么办?这会导致一些奇怪的结果! stringToAdd 参数将重复执行,直到它等于 desiredStringLength 参数:


let originalString = 'Script';

let paddedString = originalString.padStart( 15, 'Java');

console.log(paddedString);

// OUTPUT -->
// 'JavaJavaJScript'

Enter fullscreen mode Exit fullscreen mode

如果没有提供 stringToAdd 参数,空格将被添加到原始字符串的前面,直到字符串长度等于 desiredStringLength:


let originalString = 'Script';

let paddedString = originalString.padStart(15);

console.log(paddedString);

// OUTPUT -->
// "         Script"

Enter fullscreen mode Exit fullscreen mode

最后,如果没有提供 desiredStringLength 参数怎么办?将返回原始字符串的副本,且该副本保持不变:


let originalString = 'Script';

let paddedString = originalString.padStart('Java');

console.log(paddedString);

// OUTPUT --> 
// 'Script'

Enter fullscreen mode Exit fullscreen mode

String.padEnd(desiredStringLength,stringToAppend)

此字符串方法的工作方式与相同String.padStart(),但将字符串附加到给定字符串的末尾。


let originalString = 'Web';

let paddedString = originalString.padEnd(6, 'Dev');

console.log(paddedString);

// OUTPUT -->
// 'WebDev

Enter fullscreen mode Exit fullscreen mode

对于参数的使用,适用相同的规则:

  • desiredStringLength < 原始字符串 + stringToAppend?附加到原始字符串末尾的 stringToAppend 将被截断。
  • desiredStringLength > 原始字符串 + stringToAppend?会重复追加到原始字符串末尾的 stringToAppend,直到达到 desiredStringLength。
  • 未传递 stringToAppend 参数?空格将被附加到原始字符串,直到达到 desiredStringLength 为止。
  • 未传递 desiredStringLength 参数?将返回原始字符串的副本(未更改)。

String.replaceAll(模式,替换)

你可能之前遇到过String.replace()类似的情况,它接受一个模式参数和一个替换参数,并替换字符串中匹配模式的第一个实例。模式参数可以是字符串文字或正则表达式。

String.replaceAll()更进一步,顾名思义,它允许我们用替换字符串替换指定模式的所有实例,而不仅仅是第一个实例。


// Using String.replace() 
const aString = 'My name is Pippa. Pippa is my name.';

const replaceString = aString.replace('Pippa', 'Philippa');

console.log(replaceString);

// OUTPUT -->
// "My name is Philippa. Pippa is my name"
// only the first instance of 'Pippa' is replaced with 'Philippa'

// Using String.replaceAll() with regex
const  regex = /Pippa/ig;

const anotherString = 'My name is Pippa. Pippa is my name.';

const replaceAllString = anotherString.replaceAll(regex, 'Philippa');

console.log(replaceAllString);

// OUTPUT -->
// "My name is Philippa. Philippa is my name."
// both instances of 'Pippa' and replaced by 'Philippa'

Enter fullscreen mode Exit fullscreen mode

Object.entries()、Object.keys()、Object.values() 和 Object.fromEntries()

这组方法对于转换某些数据结构非常有用。让我们从……开始。

对象.entries(原始对象)

此 object 方法接受一个对象作为参数,并返回一个新的二维数组,其中每个嵌套数组都包含原始对象的键和值作为元素。让我来解释一下我的意思:


const fruitObject = {
  'banana': 'yellow',
  'strawberry': 'red',
  'tangerine': 'orange' 
};

const fruitArray = Object.entries(fruitObject);

console.log(fruitArray);

// OUTPUT -->
// [["banana", "yellow"], ["strawberry", "red"], ["tangerine", "orange"]]

Enter fullscreen mode Exit fullscreen mode

在转换数据时,这是一个非常有用的方法。另一个用例是访问对象中的特定键值对:


const fruitObject = {
  'banana': 'yellow',
  'strawberry': 'red',
  'tangerine': 'orange' 
};

const firstFruit = Object.entries(fruitObject)[0];

console.log(firstFruit);

// OUTPUT -->
// ['banana', 'yellow']

Enter fullscreen mode Exit fullscreen mode

如果你还没听说过,JavaScript 中很多东西都是对象。所以,我们甚至可以将数组和字符串作为参数传递给方法,Object.entries()方法会将它们强制转换为对象。让我们看看当我们将字符串作为参数传递时会发生什么:


const string = 'Hello'

const stringAsArgument = Object.entries(string);

console.log(stringAsArgument);

// OUTPUT --> 
// [["0", "H"], ["1", "e"], ["2", "l"], ["3", "l"], ["4", "o"]]

Enter fullscreen mode Exit fullscreen mode

字符串中的每个字符都会插入到一个单独的数组中,并将其索引设置为数组的第一个元素。当你将数组作为参数传递时,也会发生这种行为:


const array = [1,2,3]

const formattedArray = Object.entries(array);

console.log(formattedArray);

// OUTPUT --> 
// [["0", 1], ["1", 2], ["2", 3]]

Enter fullscreen mode Exit fullscreen mode

请注意,对于这两种情况,第一个元素(索引)都是一个字符串。

对象.keys(一个对象)

该对象方法接受一个对象作为参数,并返回一个包含该对象的键作为元素的数组。


const programmingLangs = {
  'JavaScript': 'Brendan Eich', 
  'C': 'Dennis Ritchie',
  'Python': 'Guido van Rossum'
};

const langs = Object.keys(programmingLangs);

console.log(langs);

// OUTPUT -->
// ["JavaScript", "C", "Python"]

Enter fullscreen mode Exit fullscreen mode

如果我们尝试传递一个字符串作为参数会怎么样?让我们看一下:


const string = 'Hallo';

const stringArray = Object.keys(string);

console.log(stringArray);

// OUTPUT -->
// ["0", "1", "2", "3", "4"]

Enter fullscreen mode Exit fullscreen mode

在这种情况下,字符串也被强制转换为一个对象。每个字母代表一个值,其索引代表一个键,因此我们得到一个包含字符串中每个字母索引的数组。

对象.值(一个对象)

正如你所料,该Object.values()方法的工作原理与我们刚刚讨论的方法类似,但它不是以数组的形式返回对象的键,而是以数组的形式返回对象的值。让我们使用之前看到的programmingLangs示例:


const programmingLangs = {
  'JavaScript': 'Brendan Eich', 
  'C': 'Dennis Ritchie',
  'Python': 'Guido van Rossum'
};

const creators = Object.values(programmingLangs);

console.log(creators);

// OUTPUT -->
// ["Brendan Eich", "Dennis Ritchie", "Guido van Rossum"]

Enter fullscreen mode Exit fullscreen mode

Object.entries()正如我们在前面的和的例子中看到的Object.keys(),我们可以传入其他数据类型,例如字符串。


const string = 'Bonjour'

const stringArray = Object.values(string);

console.log(stringArray) 

// OUTPUT -->
// ["B", "o", "n", "j", "o", "u", "r"]

Enter fullscreen mode Exit fullscreen mode

Object.fromEntries(anIterable)

另一个非常有用的数据转换方法。还记得Object.entries()我们之前看到的将对象转换为二维数组的方法吗?其实,它Object.fromEntries()本质上做的是相反的事情。它接受一个可迭代对象作为参数,例如数组或映射,并返回一个对象。我们来看一下:


const arrayTranslations = [
   ['french', 'bonjour'], 
   ['spanish', 'buenos dias'], 
   ['czech', 'dobry den']
];

const objectTranslations = Object.fromEntries(arrayTranslations);

console.log(objectTranslations);

// OUTPUT --> 
/* [object Object] {
  czech: "dobry den",
  french: "bonjour",
  spanish: "buenos dias"
} */

Enter fullscreen mode Exit fullscreen mode

因此,我们的可迭代对象(在本例中是存储为 的嵌套数组)translations被迭代,并且每个子数组都转换为一个对象,其中索引 0 处的元素作为键,索引 1 处的元素作为值。好用!

数组.flat(可选深度参数)

在处理多维数组时很有用,array 方法.flat()接受给定的数组并返回一个平面数组(默认为一维)或指定深度的数组(当提供optionalDepthArgument时)。

当没有提供optionalDepthArgument时,默认深度为1:


const numArray = [1, 2, [3,4]];

const flatArray = numArray.flat();

console.log(flatArray);

// OUTPUT -->
// [1, 2, 3, 4]

Enter fullscreen mode Exit fullscreen mode

以下是将optionalDepthArgument传递到方法中的示例:


const numArray = [1, 2, [[[3,4]]]];

const depthOf2Array = numArray.flat(2);

console.log(depthOf2Array);

// OUTPUT -->
// [1, 2, [3, 4]];

Enter fullscreen mode Exit fullscreen mode

在上面的代码片段中,我们指定了可选深度参数 (optionalDepthArgument) 为 2,因此我们的新数组depthOf2Array是具有 2 个“层级”的数组。如果您有一个嵌套很深的数组,并且想要返回一个一维数组,但不确定数组的深度,则可以Infinity像这样传递参数:


const nestedArray = [1, 2, [3, 4, [5, [6, 7]]]];

const oneDimensionalArray = nestedArray.flat(Infinity);

console.log(oneDimensionalArray); 

// OUTPUT -->
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

Enter fullscreen mode Exit fullscreen mode

对象扩展运算符...

您可能之前在 JavaScript 中见过展开语法 (…),但直到最近,我们才能够将其用于数组。这是一种克隆和合并数组的好方法。从 ES2018 开始,我们可以利用这种能力来处理对象。对象展开运算符的用例与数组展开运算符相同:克隆和合并。

让我们首先看一下如何使用这个运算符来克隆对象并添加额外的键值对:


const bookAndAuthor = {
  'Gabriel Garcia Marquez': '100 Years of Solitude'
}

const moreBooksAndAuthors = {
  ...bookAndAuthor, 
  'Paolo Coelho' : 'The Alchemist'
}; 

console.log(moreBooksAndAuthors)

// OUTPUT -->
/* [object Object] {
  Gabriel Garcia Marquez: "100 Years of Solitude",
  Paolo Coelho: "The Alchemist"
}
*/

Enter fullscreen mode Exit fullscreen mode

在这里,我们可以非常轻松地克隆原始对象并添加更多属性。

重要的是,使用对象扩展运算符复制没有嵌套数据的对象将在内存中创建一个新对象。这意味着我们可以克隆一个对象,更改或向新创建的对象添加属性,但原始对象不会被改变。但是,如果我们克隆一个包含嵌套数据的对象,则克隆将存储在内存中的新位置,但嵌套数据将通过引用传递。这意味着如果我们要更改一个对象中的任何嵌套数据,则第二个对象中的相同嵌套数据也会发生变化

现在,让我们看看当我们使用对象扩展运算符合并对象时会发生什么:


const book1 = {
  'Milan Kundera': 'The Unbearable Lightness of Being'
}

const book2 = {
  'Bohumil Hrabal': 'I Served the King of England'
}

const books = {...book1, ...book2 }

console.log(books);

// OUTPUT --> 
/*
  [object Object] {
  Bohumil Hrabal: "I Served the King of England",
  Milan Kundera: "The Unbearable Lightness of Being"
}
*/

Enter fullscreen mode Exit fullscreen mode

这里我们合并了两个对象来创建一个新对象。是不是很简单?

Promise.finally() 和 Promise.allSettled()

Promise.finally(回调函数)

JavaScript 中的 Promise 并非新鲜事物,自 ES6 以来就已存在,但该Promise.finally()方法是异步 JavaScript 工具箱中的最新成员。该方法接受一个回调函数,该函数在 Promise 状态确定(即已解决或已拒绝)后执行。它非常适合运行与任何清理任务相关的代码。


const promise = new Promise((resolve, reject) => {
  let num = Math.floor(Math.random());

  if (num >= 0.5) {
    resolve('promise resolved')
  } else {
    reject('promise rejected')
  }
})

promise
  .then(value => console.log(value))
  .catch(error => console.log(error))
  .finally(() => console.log('promise has been settled'));

// OUTPUT -->
// 'promise resolved' / 'promise rejected' (depending on value of num)
// 'promise has been settled'

Enter fullscreen mode Exit fullscreen mode

无论promise是被解决还是被拒绝,Promise.finally()方法中的回调函数总会被执行。

Promise.allSettled([承诺])

Promise.allSettled() 方法作为 ES2020 对 JavaScript 的新增功能,它接受一个 Promise 数组并返回一个新的 Promise,该 Promise 只有在数组中的所有 Promise 都已解决(已解决或已拒绝)后才会解析。解析后,返回值将是一个对象数组,每个对象都描述了数组中传递的 Promise 的结果。


const promise1 = new Promise((resolve, reject) => {
  resolve('I have been resolved')
}); 

const promise2 = new Promise((resolve, reject) => {
  reject('I have been rejected')
});

Promise.allSettled([promise1, promise2])
  .then(result => console.log(result))

// OUTPUT --> 
/*
[[object Object] {
  status: "fulfilled",
  value: "I have been resolved"
}, [object Object] {
  reason: "I have been rejected",
  status: "rejected"
}]
*/

Enter fullscreen mode Exit fullscreen mode

在此示例中,在第 9 行,我们声明Promise.allSettled()并向此方法传递了一个包含promise1和 的数组promise2。在第 10 行,我们链接了一个.then()方法,Promise.allSettled()指示 JavaScript 打印出 的已解析值Promise.allSettled()。输出显示已返回一个对象数组。每个对象代表作为参数传递给 的 Promise 的结果Promise.resolve()(在我们的例子中promise1是 和promise2)。这些对象具有 2 个属性:status,其值为 fulfilled 或 rejected;value,如果 Promise 被拒绝,则其值为 rejected;如果 Promise 已解析,则其值为 solved 的值。

BigInt

这种便捷的数据类型用于将过大的整数值存储在变量中,使其无法存储为 Number 数据类型。您可能知道,也可能不知道,JavaScript Number 数据类型对其可存储整数的大小是有限制的——安全范围是从 -9007199254740991 -(253-1) 到 9007199254740991 +(253-1),也就是 15 位数字。引入这种数据类型(其 typeof 值为“bigint”)使我们能够更轻松地处理超出此范围的整数。

关于使用 BigInt 的一些注意事项:

  • JavaScript 中用于 Number 数据类型的算术运算符也可以与 BigInt 一起使用,例如 +、*、/ 等。
  • BigInt不能与小数一起使用。
  • 您不能在 BigInt 数据类型和 Number 数据类型之间执行算术运算。

有两种方法可以将变量声明为 BigInt 数据类型,使用BigInt(number)或附加n到数字:


let hugeNumber = BigInt(9999999999999999);
let anotherHugeNumber = 7777777777777777n;

console.log(typeof hugeNumber);
console.log(typeof anotherHugeNumber);

// OUTPUT -->
// 'bigint'
// 'bigint'

Enter fullscreen mode Exit fullscreen mode

空值合并运算符和可选链式调用

可以说这是我在这个列表中最常用的功能。

空值合并(??

此逻辑运算符接受两个操作数。如果左侧操作数是null“或”,undefined则返回右侧操作数。反之,如果左侧操作数不是“null或” undefined,则返回左侧操作数。

它类似于逻辑或运算符 ( ||),不同之处在于,||运算符会根据左侧是否为假值来返回右侧(而不仅仅是null或)。JavaScript 中,作为假值的undefined行为会有一些重叠,包括,还有(空字符串),当然还有??||nullundefined0“”Nanfalse

让我们看看实际效果:


const usingOr = undefined || 'Return me because undefined is a falsy value';

const usingOrAgain = 'Return me because I am NOT falsy' || 'I will not be returned'


const usingNullishCoalescing = undefined ?? 'Return me!';

const usingNullishCoalescingAgain = 'I will return because I am NOT null/undefined ' ?? 'I will not be returned';


console.log(usingOr)
console.log(usingOrAgain)
console.log(usingNullishCoalescing)
console.log(usingNullishCoalescingAgain)

// OUTPUT -->
// "Return me because undefined is a falsy value"
// "Return me because I am NOT falsy"
// "Return me!"
// "I will return because I am NOT null/undefined " 

Enter fullscreen mode Exit fullscreen mode

就运算符优先级而言,空值合并运算符的优先级排在倒数第五,因此在组合多个运算符时请记住这一点。有关运算符优先级的更多详细信息,请参阅此页面

可选链式调用(?.

此运算符用于访问对象的属性或方法。它帮助我们避免在属性/方法不存在时抛出错误。undefined如果找不到相应的属性/方法,我们不会收到错误,而是会收到错误。

  • Object?.property对于对象属性的使用,我们通过而不是仅仅 来访问属性Object.property
  • Object.method?.()为了与对象方法一起使用,我们用而不是仅仅 来调用方法Object.method()

const person = {
  name: 'Pippa',
  favouriteColour: 'green',

  sayHello() {
    return `${this.name} says hello`;
  }
}

// ?. with object properties 
const color = person?.favouriteColour;
const age = person?.age;

console.log(color);
console.log(age);

// ?. with object methods
console.log(person.sayHello?.());
console.log(person.sayGoodby?.());

// OUTPUT --> 
// "green"
// undefined
// "Pippa says hello"
// undefined

Enter fullscreen mode Exit fullscreen mode

如您所见,person.ageperson.sayGoodbye()在 person 对象上都不存在,但我们没有收到错误,而是返回了 undefined。

数字分隔符( _)

让我们用一个简单的例子来结束这个列表。JavaScript 引入了数字分隔符,以提高处理较大数字时的可读性。它们允许你将数字“分解”成更容易理解的部分,就像使用逗号 (,) 或点 (.) 一样。我们可以使用_字符(下划线)来分隔较大的数字。


const harderToReadNumber = 100000000
const easierToReadNumber = 100_000_000

// how much nicer is that to read?!

Enter fullscreen mode Exit fullscreen mode

如果你读到这里,感谢阅读!希望你学到的东西能对你未来的 JavaScript 项目有所帮助。

鏂囩珷鏉ユ簮锛�https://dev.to/ppiippaa/some-cool-javascript-features-from-the-last-5-years-4alp
PREV
👩🏼‍💻 Docker 初学者指南 - 由初学者👨🏼‍💻 GenAI LIVE! 提供 | 2025 年 6 月 4 日
NEXT
CSS 艺术创作简介