在本文中,我们将探讨一些预计将于 2024 年实现的最令人兴奋和最受期待的 JavaScript 功能。
Temporal
在 JavaScript 中处理日期几乎总是一项令人畏惧的任务;必须处理各种细微但令人恼火的不一致问题,例如月份从零开始但月份中的天数却从 1 开始。
由于日期处理困难,一些流行的库(如 Moment、Day.JS 和 date-fns)应运而生,试图解决这些问题。然而,APITemporal
旨在原生解决所有问题。
Temporal
将立即支持多个时区和非公历,并提供易于使用的 API,使从字符串解析日期变得更加容易。此外,所有Temporal
对象都是不可变的,这将有助于避免任何意外的日期更改错误。
让我们看一下 API 提供的一些最有用的方法的示例Temporal
。
Temporal.Now.Instant()
Temporal.Now.Instant()
将返回一个精确到纳秒的 DateTime 对象。您可以使用from
如下方法指定特定日期:
const olympics = Temporal.Instant.from('2024-07-26T20:24:00+01:00');
这将创建一个 DateTime 对象,代表今年晚些时候 2024 年 7 月 26 日 20:24(UTC)巴黎奥运会的开始。
PlainDate()
这使您可以仅创建日期,而不创建时间:
new Temporal.PlainDate(2024, 7, 26); Temporal.PlainDate.from('2024-07-26'); // 两者都返回一个表示 2024 年 7 月 26 日的 PlainDate 对象
PlainTime()
作为的补充PlainDate()
,我们可以使用它来创建没有日期的时间,使用PlainTime()
:
ew Temporal.PlainTime(20, 24, 0); Temporal.PlainTime.from('20:24:00'); // 两者都返回 20:24 的 PlainTime 对象
PlainMonthDay()
PlainMonthDay()
与 类似PlainDate
,但它只返回月份和日期,没有年份信息(对于每年同一天重复的日期很有用,例如圣诞节和情人节):
const valentinesDay = Temporal.PlainMonthDay.from({ month: 2, day: 14 });
PlainYearMonth()
类似地,还有PlainYearMonth
只返回年份和月份的功能(用于表示一年中的整个月份):
const march = Temporal.PlainYearMonth.from({ month: 3, year: 2024 });
计算
使用时间对象可以进行多种计算。您可以在日期对象上添加或减去各种时间单位:
const today = Temporal.Now.plainDateISO(); const lastWeek = today.subtract({ days: 7}); const nextWeek = today.add({ days: 7 });
until()
和方法since()
可让您了解距离某个日期或自该日期发生以来还有多少天。例如,以下代码将告诉您距离巴黎奥运会还有多少天:
olympics.until().days valentinesDay.since().hours
这些方法返回一个Temporal.Duration
对象,该对象可用于测量具有多种不同单位和舍入选项的时间量。
更多
您可以从 Date 对象中提取年、月、日,并从 Time 对象中提取小时、分钟、秒、毫秒、微秒和纳秒(当前 DateTime 对象中不提供微秒和纳秒)。例如:
olympics.hour; << 20
还有其他属性,例如(dayOfWeek
返回1
星期一和7
星期日)、daysInMonth
(返回28
、29
或取决于月份)和(返回或取决于闰年)。30
31
daysinYear
365
366
Temporal
日期对象还将具有一种compare
方法,可用于使用各种排序算法对日期进行排序。
管道运算符
管道运算符是函数式语言中的标准功能,允许您将值从一个函数“传输”到另一个函数,并将前一个函数的输出用作下一个函数的输入(类似于 Fetch API 将其返回的任何数据从一个 Promise 传递到下一个 Promise)。
例如,假设我们想连续将三个函数应用于一个字符串:
- 将字符串“Listen up!”连接到原始字符串的开头。
- 将三个感叹号连接到字符串的末尾。
- 将所有文本变为大写。
这三个函数可以写如下:
const exclaim = string => string + "!!!" const listen = string => "Listen up! " + string const uppercase = string => string.toUpperCase()
这三个函数可以通过嵌套的方式应用,如下所示:
const text = "Hello World" uppercase(exclaim(listen(text))) << "LISTEN UP! HELLO WORLD!!!"
但是像这样深度嵌套的多个函数调用很快就会变得混乱,特别是因为作为参数传递的值(text
)最终深深嵌入表达式内部,从而很难识别。
函数嵌套的另一个问题是函数的应用顺序是从后往前,即最内层的函数最先应用。因此,在这种情况下,listen
将应用于 的原始值text
,然后是exclaim
,最后应用最外层的函数uppercase
。特别是对于大型和复杂的函数,这变得难以理解且不直观。
另一种方法是使用函数链,如下所示:
const text = "Hello World" text.listen().exclaim().uppercase()
这解决了很多嵌套函数的问题。传递的参数在开头,每个函数按其应用的顺序出现,因此listen()
首先应用,然后exclaim()
应用uppercase()
。
不幸的是,这个例子行不通,因为 listen、exclaim 和 uppercase 函数不是类的方法 String。它们可以通过 monkey 修补 String 类来添加,但 String 这种技术通常不受欢迎。
这意味着,尽管链接看起来比函数嵌套好很多,但它实际上只能与内置函数一起使用(就像经常使用数组方法一样)。
管道结合了链式调用的易用性,并可将其与任何函数一起使用。根据目前的提案,上述示例应如下所示:
text |> listen(%) |> exclaim(%) |> uppercase(%)
标记%
是一个占位符,用于表示前一个函数输出的值,尽管%在正式版本中,该字符很可能会被其他字符替换。这允许在管道中使用接受多个参数的函数。
管道结合了链式调用的便捷性,但可以与您编写的任何自定义函数一起使用。唯一的条件是您需要确保一个函数的输出类型与链中下一个函数的输入类型相匹配。
管道最适合只接受从任何前一个函数的返回值中管道传输的单个参数的柯里化函数。它使函数式编程变得更容易,因为可以将小型构建块函数链接在一起以形成更复杂的复合函数。它还使部分应用更容易实现。
尽管管道操作员很受欢迎,但它一直难以在该流程的第 2 阶段之后继续前进。这是因为人们对符号的表达方式存在分歧,并且对内存性能及其与 的配合方式存在担忧 await。不过,委员会似乎正在慢慢达成某种协议,因此希望管道操作员能够快速完成各个阶段并在今年亮相。
值得庆幸的是,从 Babel 7.15 版本开始,管道操作符已经实现。
就个人而言,我们希望管道运算符能够在今年实现并推出,因为它将真正有助于提高 JavaScript 作为一种严肃的函数式编程语言的资质。
Record 和 Tuple
“Record 和 Tuple 提案”旨在将不可变数据结构引入 JavaScript。
元组(Tuple)类似于数组——一个有序的值列表——但它们是深度不可变的。这意味着元组中的每个值都必须是基本值、另一个记录(Record)或元组(因为数组或对象在 JavaScript 中是可变的,所以它们不能作为元组的元素)。
元组的创建方式与数组字面量类似,但在前面加上一个井号(#)符号:
const heroes = #["Batman", "Superman", "Wonder Woman"]
一旦创建,就无法再添加或删除任何值,并且这些值也不能被更改。
记录(Record)类似于对象——一个键值对的集合——但它们也是深度不可变的。记录的创建方式与对象类似——但与元组相同,它们也是以井号(#)开头:
const traitors = #{ diane: false, paul: true, zac: false, harry: true }
记录仍将使用点符号来访问属性和方法:
traitors.paul << true
数组使用的方括号表示法也可以用于元组:
heroes[1] << "Superman"
但由于它们是不可变的,因此您无法更新任何属性:
traitors.paul = false << Error heroes[1] = "Supergirl" << Error
元组和记录的不变性意味着您可以使用===
运算符轻松地比较它们:
heroes === #["Batman", "Superman", "Wonder Woman"]; << true
需要注意的一点是,在考虑记录的相等性时,属性的顺序并不重要:
traitors === #{ ross: false, zac: false, paul: true, harry: true }; // 即使顺序已经改变,这仍然是正确的。 << true
不过,对于元组来说,顺序确实很重要,因为它们是数据的有序列表:
heroes === #["Wonder Woman", "Batman", "Superman"]; << false
RegExp /v 标志
自版本 3 以来,正则表达式就已融入 JavaScript,并且自那时以来已进行了许多改进(例如,u
在 ES2015 中使用标志支持 Unicode)。v
标志提案旨在实现 u 标志的所有功能,但它还增加了一些额外的好处,我们将在下面的示例中看到。
简单来说,实现该v
标志涉及/v
在正则表达式的末尾添加一个。
例如,以下代码可用于测试某个字符是否为表情符号:
const isEmoji = /^p{RGI_Emoji}$/v; isEmoji.test(""); << true isEmoji.test(""); << true
这使用该RGI_Emoji
模式来识别表情符号。
该 v 标志还允许您在正则表达式中使用集合符号。例如,您可以使用运算符将一个模式从另一个模式中减去--
。以下代码可用于从表情符号集合中删除任何爱心:
const isNotHeartEmoji = /^[p{RGI_Emoji_Tag_Sequence}--q{♥️}]$/v; isNotHeartEmoji.test(""); << false isNotHeartEmoji.test(""); << true
您可以使用 找到两个模式的交集&&
。例如,以下代码将找到希腊符号和字母的交集:
const GreekLetters = /[p{Script_Extensions=Greek}&&p{Letter}]/v; GreekLetters.test('π'); << true GreekLetters.test(''); << false
该v
标志还解决了一些u
与大小写不敏感有关的问题,使其成为在几乎所有情况下使用的更好的选择。
正则表达式的标志v
在 2023 年达到第 4 阶段,并已在所有主流浏览器中实现,因此完全有希望成为 ES2024 规范的一部分。
装饰器
Decorator 提案旨在使用装饰器来原生扩展 JavaScript 类。
装饰器在许多面向对象语言(如 Python)中已经很常见,并且已包含在 TypeScript 中。它们是一种标准的元编程抽象,允许您向函数或类添加额外的功能,而无需更改其结构。例如,您可能希望向方法添加一些额外的验证,您可以通过创建一个验证装饰器来检查输入到表单中的数据来实现。
虽然 JavaScript 允许您使用函数来实现这种设计模式,但大多数面向对象的程序员更喜欢采用一种更简单、更原生的方式来实现这一点,只是为了让生活变得更轻松。
该提案增加了一些语法糖,让您可以轻松地在类内实现装饰器,而无需考虑绑定this
到类。它提供了一种更简洁的方式来扩展类元素,例如类字段、类方法或类访问器,甚至可以应用于整个类。
装饰器以符号的前缀来标识@
,并且始终放置在它们“装饰”的代码之前。
例如,类装饰器将紧接着类定义。在下面的例子中,validation
装饰器应用于整个类FormComponent
:
@validation class FormComponent { // code here } // The decorator function also needs defining function validation(target) { // validation code here }
类方法装饰器位于其所装饰的方法之前。在下面的示例中,validation
装饰器应用于submit
方法:
class FormComponent { // class code here @validation submit(data) { // method code here } } // The decorator function also needs defining function validation(target) { // validation code here }
装饰器函数定义接受两个参数:一个值和上下文。值参数指的是被装饰的值(例如类方法),而上下文包含有关该值的元数据,比如它是否是函数、它的名称、以及它是否是静态的或私有的。你还可以在上下文中添加一个初始化函数,该函数将在类实例化时运行。
装饰器提案目前处于第 3 阶段,并且已经在 Babel 中实现,因此你现在就可以尝试它。
结语
以上就是今年新增的 5 个令人兴奋的 JavaScript 新功能,所有这些特性都将成为 JavaScript 的伟大补充,让我们期待它们能在今年成功加入!