大家好,这里是大家的林语冰。
在本文中,我们会深度学习若干令人鸡冻且备受期待的 JS 新功能,这些新功能预计会在 2024 年推出。
下列提案可能会进入 2024 的 ECMAScript 版本:
- Temporal(日期 API)
- 管道操作符
- 记录和元组
- 正则表达式的
/v
标志 - 装饰器
免责声明
本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 5 Exciting New JavaScript Features in 2024。
ECMAScript 更新
ES6 是一个超大规模的版本,是在其前身 ES5 沉淀了 6 年之后才发布的。当时,浏览器供应商和 JS 开发者对需要采用和学习的一大坨新功能感到一脸懵逼。从那时起,为了防止新功能一次性"填鸭式更新",每年都有一个发布周期。我们预计今年的 ES2024 会在 6 月份左右发布。
这个年度发布周期涉及提议的任何新功能,然后由技术委员会讨论、评估和投票,然后再将其添加到 JS 中。
ES 规范的一个重要之事是必须向后兼容。这意味着,任何新功能都不能通过改变 ES 之前版本的工作方式来互联网搞破坏。因此,它们无法改变现有方法的工作方式,只能添加新方法。
Temporal
在《2022 年 JS 现状调查》中,"您认为 JS 目前的缺陷是什么?"该问题最常见的答案的第三名是更优雅的日期管理。
这启发了 Temporal
提案,它提供了一个标准的全局对象来替换 Date
对象,并修复了这些年来许多在 JS 中处理日期时让开发者痛不欲生的问题。
在 JS 中处理日期简直是一种灾难;我们必须处理令人头大的不一致细节,比如变态的月份索引为 0
,但月份中的日期却从 1
开始。
日期的痛点导致了 Moment、Day.JS 和 date-fns 等流行库的诞生,而 Temporal
API 旨在从原生层级搞定所有问题。
Temporal
会支持多个时区和开箱即用的非公历,使从字符串解析日期变得轻而易举。此外,所有 Temporal
对象都是不可变的,这有助于避免任何意外的日期更改错误。
Temporal.Now.Instant()
Temporal.Now.Instant()
会返回一个最接近纳秒的 DateTime
对象。我们可以使用 from
方法指定特定日期,如下所示:
js
const catDay = Temporal.Instant.from('2024-08-08T10:24:00+01:00')
这会创建一个 DateTime
对象,表示今年的"国际猫咪日"将于 2024/08/08 日 10:24 开始。
PlainMonthDay()
PlainMonthDay()
能且仅能返回月份和日期,没有年份信息,这对于每年在同一天重复出现的日期很有用,比如"光棍节":
js
const cutHandDay = Temporal.PlainMonthDay.from({ month: 11, day: 11 })
PlainYearMonth()
举一反一,PlainYearMonth
能且仅能返回年份和月份,对于表示一年中的整个月份效果拔群,比如《四月是你的谎言》:
js
const march = Temporal.PlainYearMonth.from({ month: 4, year: 2024 })
其他功能
Temporal
日期对象还会拥有 compare
方法,允许使用各种排序算法对日期进行排序。
Temporal
目前是第 3
阶段提案,浏览器供应商正在实施该提案,因此似乎它的时机已经到来。与 Intl.DateTimeFormat
API 搭配使用时,我们能够执行某些十分优雅的日期操作。
管道运算符
管道运算符是函数式语言中的一个标准功能,它允许我们将值从一个函数到另一个函数之间建立"管道",前一个函数的输出用作下一个函数的输入。
举个栗子,假设我们想要连续将三个函数应用于一个字符串:
- 把字符串"niu bi"连接到原始字符串的结尾。
- 将三个感叹号连接到字符串的末尾。
- 将所有文本设为大写。
这三个函数如下所示:
js
const exclaim = string => string + '!!!'
const cool = string => string + ' niu bi'
const uppercase = string => string.toUpperCase()
这三个函数可以通过将它们全部嵌套起来应用,如下所示:
js
const string = 'lu ben wei'
uppercase(exclaim(cool(string))) // 撸本萎牛逼!!!
但是,这样深度嵌套多个函数调用很快会形成"代码屎山",特别是因为作为参数传递的值 string
最终会深深嵌入到表达式中,变得难以识别。
函数嵌套的另一个问题在于,函数的应用顺序是从后到前的,即最里面的函数最先被应用。特别是对于大型且复杂的函数,这变得很猪头且不直观。
另一种方法是使用如下所示的函数调用链:
js
const string = 'lu ben wei'
string.cool().exclaim().uppercase()
这可以搞定嵌套函数的很多问题。传递的参数位于开头。
不幸的是,上述写法其实毫无卵用,因为 cool
等函数都不是 String
类的原生方法。尽管它们可以通过String
类的"猴子补丁"来添加,但这通常是一种不受待见的"技术负债"。
这意味着,虽然链式调用看似比函数嵌套优雅,但它实际上只能与内置函数一起使用,就像数组方法或 Promise
的链式调用。
管道运算符结合了链式调用的易用性,而且能够将其与任何函数无缝衔接。根据当前的提案,上述例子可以这样重构:
js
string |> cool(%) |> exclaim(%) |> uppercase(%)
%
标记是一个占位符,用于表示前一个函数的输出值,尽管 %
字符可能会在正式版本中被其他符号替换。这允许在管道中使用接受多个参数的函数。
管道运算符结合了链式调用的便捷性,但可以我们编写的任意自定义函数"梦幻联动"。唯一的条件在于,我们必须确保一个函数的输出类型与管道中下一个函数的输入类型匹配。
管道传输最适合柯里化函数,这些函数只接受从任何先前函数的返回值通过管道传输的单个参数。它使函数式编程变得轻而易举,因为小型的构建块函数可以链接形成更复杂的复合函数。它还使得偏函数更容易实现。
尽管管道运算符人气爆棚,但它一直在该提案的第 2 阶段摇摆不定。这是由于对如何表达符号的分歧,以及对内存性能及其如何与 await
一起使用的担忧造成的。不过,委员会似乎正在慢慢妥协,因此上帝保佑管道运算符能够在今年崭露头角。
记录和元组
Record
和 Tuple
提案旨在将不可变的数据结构引入 JS。
元组是类似数组的值的有序列表,但它们是深度不可变的。这意味着,元组中的每个值必须是原始值,或另一个记录,或者元组,而不是 JS 中可变的数组或对象。
元组的创建方式与数组字面量类似,但有一个前置哈希符号 #
:
js
const cats = #['龙猫', '柴郡猫']
元组一旦创建,就不能再添加其他值,也不能删除任何值。这些值也无法更改。
记录是类似对象的键值对集合,但它们也是深度不可变的。它们的创建方式与对象类似 ------ 但与元组相同,它们前置哈希开头:
js
const bilibili = #{
like: false,
star: true
}
记录仍将使用点运算符来访问属性和方法:
js
bilibili.like // false
数组使用的方括号表示法也可以用于元组:
js
cats[1] // 柴郡猫
但由于它们是不可变的,因此我们无法更新任何属性:
js
bilibili.star = false // 报错
cats[1] = '盯裆猫' // 报错
元组和记录的不变性意味着,我们可以使用 ===
运算符轻松比较它们:
js
cats === #['龙猫', '柴郡猫'] // true
粉丝请注意,在考虑记录的相等性时,属性的顺序并不重要:
js
bilibili ===
#{
star: true,
like: false
} // true,虽然属性顺序改变,但仍然相等
不过,顺序对于元组而言至关重要,因为它们是有序的数据列表:
js
cats === #['柴郡猫', '龙猫'] // false
正则表达式的 /v
标志
从版本 3
开始,正则表达式就被纳入 JS 中,并且自那时以来已经有了许多改进,比如 ES2015 中使用 u
标志的 Unicode 支持。v
标志提案旨在完成 u
标志的所有功能,但它增加了某些额外的福利。
简而言之,实现 v
标志需要将 /v
添加到正则表达式的末尾。
举个栗子,可以使用下列代码来测试某个字符是否是表情符号:
js
const isEmoji = /^\p{RGI_Emoji}$/v
isEmoji.test('💚') // true
isEmoji.test('🐨') // true
这使用 RGI_Emoji
模式来识别表情符号。
v
标志还允许我们在正则表达式中使用集合表示法。我们还可以使用 &&
找到两个模式的交集。
v
标志还搞定了 u
标志不区分大小写的某些问题,使其成为在几乎所有情况下使用的更好选择。
正则表达式的 v
标志在 2023 年达到了第 4
阶段,并已经在所有主流浏览器中实现,因此成为 ES2024 规范的一部分指日可待。
装饰器
装饰器提案旨在使用装饰器来原生扩展 JS 类。
装饰器在 Python 等许多面向对象语言中已经司空见惯,并且已经包含在 TS 中。装饰器是标准的元编程抽象,允许我们向函数或类添加额外的功能,而无需更改其结构。举个栗子,我们可能想向方法添加一些额外的验证,这可以通过创建一个验证装饰器检查输入到表单中的数据,从而完成此操作。
虽然 JS 允许我们使用函数来实现这种设计模式,但大多数面向对象的程序员更喜欢一种更简单、更原生的方式来实现这一点。
装饰器提案添加了某些语法糖,使我们可以轻松地在类中实现装饰器,而不必考虑将 this
绑定到类。装饰器提供了一种更精简的方式来扩展类元素,比如类字段、类方法或类访问器,甚至可以应用于整个类。
装饰器以 @
符号前缀进行标识,并且始终放置在它们要"装饰"的代码之前。
举个栗子,类装饰器会紧接在类定义之前。在下述示例中,validation
校验装饰器应用于整个 FormComponent
表单组件类:
js
@validation
class FormComponent {}
function validation(target) {
// 自定义校验的功能
}
装饰器函数定义接受两个参数:值和上下文。value
参数指的是被修饰的值,比如类方法,上下文包含有关该值的元数据,比如它是否是函数、它的名称以及它是静态的还是私有的。我们还可以将初始化函数添加到上下文中,该函数将在实例化类时运行。
装饰器提案目前处于第 3
阶段,并已在 Babel 中实现,因此我们可以沉浸式体验了。
本期话题是 ------ 2024 你最期待的 JS 新功能是哪一个?
欢迎在本文下方自由言论,文明共享。谢谢大家的点赞,掰掰~
《前端猫猫教》每日 9 点半更新,坚持阅读,自律打卡,每天一次,进步一点。