以下提案很有可能进入今年的 ECMAScript 版本:
ECMAScript 更新
JS的新版本总是会引起轰动。自 ES6 更新以来,每年都会有新版本,我们预计今年(ES2024)将在 6 月左右发布。
ES6 是一个大规模版本,是在其前身 ES5六年后发布的。 浏览器供应商和 JavaScript 开发人员对大量需要采用和学习的新功能感到不知所措。从那时起,为了防止新功能同时出现如此大的下降,每年都有一个发布周期。
这个年度发布周期涉及提出任何新功能,然后由委员会讨论、评估和投票,然后再将其添加到语言中。此过程还允许浏览器在提案正式添加到语言之前尝试实现这些提案,这可能有助于解决任何实现问题。
如前所述,JavaScript(或 ECMAScript)的新功能由技术委员会 39 (TC39)决定。TC39 由来自所有主要浏览器供应商的代表以及 JavaScript 专家组成。他们定期开会讨论该语言的新功能以及如何实现这些功能。新功能以提案形式提出(由任何人提出),然后委员会成员投票决定每个提案是否可以进入下一阶段。每个提案有 4 个阶段;一旦提案达到第 4 阶段,预计将被包含在下一版本的 ES 中。
ES 规范的一个重要部分是它必须向后兼容 。这意味着任何新功能都不能通过改变 ES 之前版本的工作方式来破坏互联网。 因此,他们无法改变现有方法的工作方式,只能添加新方法,因为任何使用可能预先存在的方法运行的网站都将面临崩溃的风险。
Temporal
在《2022 年 JS 现状》调查中,第三个 最常见的答案是"您认为 JavaScript 目前缺少什么?" 是更好的日期管理。
这导致了Temporal
提案的产生,它提供了一个标准的全局对象来替换该Date
对象,并修复了多年来在 JavaScript 中处理日期时给开发人员带来很大痛苦的一些问题。
在 JavaScript 中处理日期几乎总是一项可怕的任务;必须处理微小但令人恼火的不一致,例如疯狂的月份索引为零,但月份中的日期从 1 开始。
日期的困难导致流行的库如Moment、Day.JS和date-fns出现,试图解决这些问题。然而,该Temporal
API 旨在本地解决所有问题。
Temporal
将支持多个时区和开箱即用的非公历,并将提供一个简单易用的 API,使从字符串解析日期变得更加容易。此外,所有Temporal
对象都是不可变的,这将有助于避免任何意外的日期更改错误。
让我们看一下 API 提供的最有用方法的一些示例Temporal
。
Temporal.Now.Instant()
Temporal.Now.Instant()
将返回一个最接近纳秒的 DateTime 对象。您可以使用from
如下方法指定特定日期:
ini
const olympics = Temporal.Instant.from('2024-07-26T20:24:00+01:00');
这将创建一个 DateTime 对象,表示今年晚些时候巴黎奥运会将于 2024 年 7 月 26 日 20:24(UTC)开始。
PlainDate()
这允许您只创建一个日期,没有时间:
csharp
new Temporal.PlainDate(2024, 7, 26);
Temporal.PlainDate.from('2024-07-26');
// both return a PlainDate object that represents 26th July 2024
PlainTime()
作为 PlainDate()
的补充,我们可以使用它来创建一个没有日期的时间,使用.PlainTime()
:
csharp
new Temporal.PlainTime(20, 24, 0);
Temporal.PlainTime.from('20:24:00');
// both return a PlainTime object of 20:24
PlainMonthDay()
PlainMonthDay()
与 类似PlainDate
,但它只返回月份和日期,没有年份信息(对于每年在同一天重复出现的日期很有用,例如圣诞节和情人节):
ini
const valentinesDay = Temporal.PlainMonthDay.from({ month: 2, day: 14 });
PlainYearMonth()
同样,还有PlainYearMonth
只返回年份和月份(对于表示一年中的整个月份很有用):
ini
const march = Temporal.PlainYearMonth.from({ month: 3, year: 2024 });
计算
可以使用 Temporal 对象完成许多计算。您可以向日期对象添加和减去各种时间单位:
ini
const today = Temporal.Now.plainDateISO();
const lastWeek = today.subtract({ days: 7});
const nextWeek = today.add({ days: 7 });
和until
方法since
可让您了解距某个特定日期或自该日期发生以来的时间。例如,以下代码将告诉您距离巴黎奥运会还有多少天:
scss
olympics.until().days
valentinesDay.since().hours
这些方法返回一个Temporal.Duration
对象,该对象可用于测量具有多种不同单位和舍入选项的时间量。
附加功能
您可以从 Date 对象中提取年、月、日,并从 Time 对象中提取时、分、秒、毫秒、微秒和纳秒(微秒和纳秒在当前 DateTime 对象中不可用)。例如:
bash
olympics.hour;
<< 20
还有其他属性,例如dayOfWeek
(返回1
星期一和7
星期日)、daysInMonth
(返回28
, 29
,30
或31
取决于月份) 和daysinYear
(返回365
或366
取决于闰年)。
Temporal
日期对象还有一个compare
方法,可用于使用各种排序算法对日期进行排序。
Temporal 目前是第 3 阶段提案,浏览器供应商正在实施该提案,因此似乎它的时机已经到来(双关语)。您可以在此处查看完整文档。这里还有一本有用的用例手册。与Intl.DateTimeFormat API配合使用时,您将能够执行一些非常漂亮的日期操作。
Pipe Operator
在2022 年 JS 现状调查中,"您认为 JavaScript 目前缺少什么?"的第六个热门答案 是一名管道操作员。
您可以在此处查看Pipe Operator 提案。
管道运算符是函数式语言中的一项标准功能,它允许您将值从一个函数"管道"到另一个函数,前一个函数的输出用作下一个函数的输入(类似于 Fetch API 传递它从一个承诺返回到下一个承诺的任何数据)。
例如,假设我们想要连续将三个函数应用于一个字符串:
- 连接字符串"Listen up!" 到原始字符串的开头。
- 将三个感叹号连接到字符串的末尾。
- 将所有文本设为大写。
这三个函数可以写成如下:
c
const exclaim = string => string + "!!!"
const listen = string => "Listen up! " + string
const uppercase = string => string.toUpperCase()
这三个函数可以通过将它们全部嵌套在一起来应用,如下所示:
arduino
const text = "Hello World"
uppercase(exclaim(listen(text)))
<< "LISTEN UP! HELLO WORLD!!!"
但是像这样深度嵌套多个函数调用可能会很快变得混乱,特别是因为text
作为参数传递的值 ( ) 最终深深嵌入到表达式中,使其难以识别。
函数嵌套的另一个问题是函数的应用顺序是从后到前的,即最里面的函数首先被应用。因此,在这种情况下,listen
gets 应用于 的原始值text
,然后是,最后将应用exclaim
最外层函数。uppercase
特别是对于大型且复杂的函数,这变得很难且不直观。
另一种方法是使用像这样的函数链:
scss
const text = "Hello World"
text.listen().exclaim().uppercase()
这解决了嵌套函数的很多问题。传递的参数位于开头,每个函数按照其应用的顺序出现,因此listen()
首先应用,exclaim()
然后uppercase()
。
不幸的是,这个例子不起作用,因为 ,listen
和exclaim
函数uppercase
不是该类的方法String
。它们可以通过猴子修补类来添加String
,但这作为一种技术通常不受欢迎。
这意味着,虽然链接看起来比函数嵌套好得多,但它实际上只能与内置函数一起使用(就像数组方法经常使用的那样)。
管道结合了链接的易用性,并且能够将其与任何函数一起使用。根据当前的提案,上面的示例将如下所示:
scss
text |> listen(%) |> exclaim(%) |> uppercase(%)
令牌%
是一个占位符,用于表示前一个函数的输出值,尽管该%
字符很可能在正式版本中被其他字符替换。这允许在管道中使用接受多个参数的函数。
管道结合了链接的便利性,但可以与您编写的任何自定义函数一起使用。唯一的条件是您需要确保一个函数的输出类型与链中下一个函数的输入类型匹配。
管道传输最适合只接受从任何先前函数的返回值管道传输的单个参数的柯里化函数。它使函数式编程变得更加容易,因为小型的构建块函数可以链接在一起以形成更复杂的复合函数。它还使得部分应用更容易实现。
尽管很受欢迎,但管道运营商一直在努力推进该过程的第二阶段。这是由于对如何表达符号的分歧以及对内存性能及其如何与await
. 不过,委员会似乎正在慢慢达成某种协议,因此希望管道运营商能够迅速完成各个阶段并在今年露面。
值得庆幸的是,管道运算符已经在 Babel 7.15 版本中实现了。
就我个人而言,我们希望管道运算符能够在今年实现并推出,因为它将真正有助于提高 JavaScript 作为一种严肃的函数式编程语言的资格。
Record和元组
Record和 Tuple 提案旨在将不可变的数据结构引入 JavaScript。
元组类似于数组(值的有序列表),但它们是深度不可变的 。这意味着元组中的每个值必须是原始值或另一个Record或元组(不是数组或对象,因为它们在 JavaScript 中是可变的)。
元组的创建方式与数组文字类似,但#
前面有一个前导哈希符号 ( ):
rust
const heroes = #["Batman", "Superman", "Wonder Woman"]
一旦创建,就不能添加其他值,也不能删除任何值。这些值也无法更改。
Record类似于对象(键值对的集合),但它们也是深度不可变的。它们的创建方式与对象类似 - 但与元组相同,它们以前导哈希开头:
yaml
const traitors = #{
diane: false,
paul: true,
zac: false,
harry: true
}
记录仍将使用点符号来访问属性和方法:
arduino
traitors.paul
<< true
数组使用的方括号表示法也可以用于元组:
arduino
heroes[1]
<< "Superman"
但由于它们是不可变的,因此您无法更新任何属性:
bash
traitors.paul = false
<< Error
heroes[1] = "Supergirl"
<< Error
元组和记录的不变性意味着您将能够使用===
运算符轻松比较它们:
rust
heroes === #["Batman", "Superman", "Wonder Woman"];
<< true
需要注意的一件事是,在考虑记录的相等性时,属性的顺序并不重要:
yaml
traitors === #{
ross: false,
zac: false,
paul: true,
harry: true
};
// still true, even though the order of people has changed
<< true
不过,顺序对于元组来说确实很 重要,因为它们是有序的数据列表:
rust
heroes === #["Wonder Woman", "Batman", "Superman"];
<< false
此页面有一个带有现场游乐场的方便教程,因此您可以习惯记录和元组的工作方式。
正则表达式 /v 标志
从版本 3 开始,正则表达式就被纳入 JavaScript 中,并且自那时以来已经有了许多改进(例如u
ES2015 中使用标志的 Unicode 支持)。标志v
提案旨在完成u
标志所做的所有事情,但它增加了一些额外的好处,我们将在下面的示例中看到。
简单地说,实现该v
标志需要/v
在正则表达式的末尾添加 a 。
例如,可以使用以下代码来测试某个字符是否是表情符号:
javascript
const isEmoji = /^\p{RGI_Emoji}$/v;
isEmoji.test("💚");
<< true
isEmoji.test("🐨");
<< true
这使用RGI_Emoji
模式来识别表情符号。
该v
标志还允许您在正则表达式中使用设置表示法。例如,您可以使用--
运算符从一种模式中减去另一种模式。以下代码可用于从表情符号集中删除任何爱心:
javascript
const isNotHeartEmoji = /^[\p{RGI_Emoji_Tag_Sequence}--\q{💜💚♥️💙🖤💛🧡🤍🤎}]$/v;
isNotHeartEmoji.test("💚");
<< false
isNotHeartEmoji.test("🐨");
<< true
您可以使用 找到两个模式的交集&&
。例如,以下代码将查找希腊符号和字母的交集:
javascript
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
:
less
@validation
class FormComponent {
// code here
}
// The decorator function also needs defining
function validation(target) {
// validation code here
}
类方法装饰器紧接在它所装饰的方法之前。在下面的示例中,validation
装饰器应用于该submit
方法:
javascript
class FormComponent {
// class code here
@validation
submit(data) {
// method code here
}
}
// The decorator function also needs defining
function validation(target) {
// validation code here
}
装饰器函数定义接受两个参数:值和上下文。value 参数指的是被修饰的值(例如类方法),上下文包含有关该值的元数据,例如它是否是函数、它的名称以及它是静态的还是私有的。您还可以将初始化函数添加到上下文中,该函数将在实例化类时运行。
Decorator 提案目前处于第 3 阶段,并已在 Babel 中实现,因此您已经可以尝试一下。
结论
所以你怎么看?您希望今年的规范中添加哪些内容?所有这些功能都将为 JavaScript 带来巨大的补充,所以祈祷他们能在今年实现这一目标!