2024年Js五大新功能

米阳 2024-7-9 731 7/9

每次 JavaScript  更新都会引起轰动。

ES6 是一个重大更新,距离它的前身 ES5 已经有六年了。浏览器厂商和 JavaScript 开发者都被大量的新特性所淹没,需要学习和适应。为了避免一次性出现大量新特性,从那时起,JavaScript  就开始了一年一度的发布周期。

这个年度发布周期包括提出新特性,然后由委员会进行讨论、评估和投票,最后才会被添加到语言中。这个过程也允许浏览器在正式添加到语言之前尝试实现提案,这可能有助于解决任何实现问题。

所有当前提案的完整列表可以在这里看到:https://github.com/tc39/proposals/tree/main。

以下提案很有可能被纳入今年的 ECMAScript 版本:

  • Temporal
  • 管道操作符
  • 记录和元组
  • 正则表达式 /v 标志
  • 装饰器

Temporal:告别日期处理难题

在 JavaScript  中处理日期几乎总是一项令人头疼的任务;必须处理一些微小但令人恼火的矛盾,例如月份从 0 开始计数,而日期从 1 开始计数,真是让人抓狂。

日期处理的困难导致了 Moment、Day.JS 和 date-fns 等流行库的出现,它们试图解决这些问题。然而,Temporal API 旨在从根本上解决所有这些问题。

Temporal 将开箱即用地支持多个时区和非格里高利历,并将提供一个易于使用的 API,使从字符串解析日期变得更加容易。此外,所有 Temporal 对象都是不可变的,这将有助于避免任何意外的日期更改错误。

Temporal.Now.Instant()

Temporal.Now.Instant() 将返回一个精确到纳秒的 DateTime 对象。你可以使用 from 方法指定特定的日期,如下所示:

const olympics = Temporal.Instant.from('2024-07-08T20:24:00+01:00'); 

这将创建一个 DateTime 对象,表示今年晚些时候巴黎奥运会的开始时间,即 2024 年 7 月 26 日 20:24(UTC)。

PlainDate()

new Temporal.PlainDate(2024, 7, 8);
Temporal.PlainDate.from('2024-07-08');
// 两者都返回一个表示 2024 年 7 月 8 日的 PlainDate 对象 

PlainTime()

new 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 }); 

计算

可以使用 Temporal 对象进行许多计算。你可以对日期对象添加和减去各种时间单位:

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(根据月份返回 282930 或 31)和 daysinYear(根据闰年返回 365 或 366)。

Temporal 日期对象还将有一个 compare 方法,可以用来使用各种排序算法对日期进行排序。

管道操作符:让函数调用更流畅

管道操作符是函数式语言中的一个标准特性,它允许你将一个值从一个函数输送到另一个函数,前一个函数的返回作为下一个函数的入参(类似于 Fetch API 将从一个 Promise 返回的任何数据传递到下一个 Promise 的方式)。

例如,假设我们想连续对一个字符串应用三个函数:

  1. 将字符串“Listen up!”连接到原始字符串的开头。
  2. 将三个感叹号连接到字符串的末尾。
  3. 将所有文本转换为大写。

这三个函数可以写成如下:

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(),但是这样运行不了,因为listenexclaim 和 uppercase 函数不是 String 类的方法。

虽然链式调用看起来比函数嵌套好得多,但它只能与内置函数一起使用(就像经常使用数组方法一样)。

管道结合了链式调用的易用性,但能够与任何函数一起使用。根据目前的提案,上面的例子将写成如下:

text |> listen(%) |> exclaim(%) |> uppercase(%);

% 标记是一个占位符,用于表示前一个函数的输出值,如果在正式版本中,% 字符可能会被其他字符替换。这允许在管道中使用接受多个参数的函数。

管道最适合与只接受一个参数的函数一起使用,该参数从任何先前函数的返回值中进行传输。它使函数式编程变得更加容易,因为可以将小的构建块函数链接在一起,以创建更复杂的复合函数。它也使 部分应用更容易实现。

记录和元组:不可变数据结构

记录和元组提案 旨在将不可变数据结构引入js。

元组类似于数组——一个有序的值列表——但它们是深度不可变的。这意味着元组中的每个值必须是 原始值 或另一个记录或元组(不能是数组或对象,因为它们在 JavaScript  中是可变的)。

元组的创建方式与数组字面量类似,但在前面有一个前导哈希符号 (#):

const heroes = #["1", "2", "3"];

一旦创建了这个元组,就不能添加或删除其他值。值也不能更改。

记录类似于对象——一个键值对的集合——但它们也是深度不可变的。它们的创建方式与对象类似——但与元组一样,它们以一个前导哈希开头:

const traitors = #{
  diane: false,
  paul: true,
  zac: false,
  harry: true
};

记录仍然可以使用点符号来访问属性和方法,数组使用的方括号符号也可以用于元组,但是,由于它们是不可变的,因此你不能更新任何属性

traitors.paul; // 输出 true

heroes[1]; // 输出 "2"

traitors.paul = false; // 输出 错误

heroes[1] = "5";// 输出 错误

//元组和记录的不可变性意味着你可以使用 === 操作符轻松地比较它们 heroes === #["1", "2", "3"]; // 输出 true //元组顺序很重要,因为它们是一个有序的数据列表 heroes === #["2", "1", "3"]; // 输出 false //需要注意的是,在考虑记录的相等性时,属性的顺序不重要 traitors === #{   ross: false,   zac: false,   paul: true,   harry: true }; // 输出 true

正则表达式 /v 标志:增强正则表达式功能

实现 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 标志在不区分大小写方面的一些问题,使其在几乎所有情况下都是更好的选择。

装饰器:更优雅地扩展类

装饰器在许多面向对象语言(如 Python)中已经很常见,并且已经 包含在 TypeScript 中。它们是一种标准的元编程抽象,允许你在不改变函数或类结构的情况下为其添加额外的功能。例如,你可能想为一个方法添加一些额外的验证,你可以通过创建一个验证装饰器来检查输入到表单中的数据来实现这一点。

该提案添加了一些语法糖,让你可以轻松地在类中实现一个装饰器,而无需考虑将 this 绑定到类。它提供了一种更简洁的方法来扩展类元素,例如类字段、类方法或类访问器,甚至可以应用于整个类。

装饰器使用 @ 符号作为前缀,并且始终放在它们“装饰”的代码之前。

例如,类装饰器将紧接在类定义之前。在下面的例子中,validation 装饰器应用于整个 FormComponent 类:

@validation
class FormComponent {
  // 代码
}
// 装饰器函数也需要定义
function validation(target) {
  // 验证代码
}

类方法装饰器紧接在其装饰的方法之前。在下面的例子中,validation 装饰器应用于 submit 方法:

class FormComponent {
  // 类代码

  @validation
  submit(data) {
    // 方法代码
  }
}

// 装饰器函数也需要定义
function validation(target) {
  // 验证代码
}

装饰器函数定义接受两个参数:值和上下文。值参数指的是被装饰的值(例如类方法),上下文包含关于该值的元数据,例如它是否是函数、它的名称以及它是否是静态的或私有的。你还可以向上下文添加一个初始化器函数,该函数将在实例化类时运行。

 

 

 

- THE END -

米阳

10月24日11:15

最后修改:2024年10月24日
1

非特殊说明,本博所有文章均为博主原创。