从TC39文档看JavaScript未来,呕心整理,干货满满,你想要的全拿走!

111 阅读9分钟

关于TC39提案,第一次看,只能说略懂一二。

那么就由我这个小白和大家一起浅读TC39,看看近几年(2016-2023)又出了哪些新的API。

字符串类


includes

大家最熟悉的应当是includes了,这个方法既可以用于数组,也可以用于字符串,例如:

'tangytin'.includes('ytin', 3)   ===> true
['tangytin', 'abc', 'guoge'].includes('tangytin')  ===> true
[NaN, +0].includes(-0)   ===> true
[NaN, +0].includes(NaN)   ===> true

由上述可知,includes方法可以判断NaN和NaN相等,+0和-0相等,实际上includes方法是采用的SameValueZero算法,该算法的特点如下:

  • 相同类型原始值, 按 == 比较
  • 引用类型, 比较引用指针
  • 判断NaN和NaN相等
  • 判断+0 和 -0 相等

备注:我们来回顾一下Object.is()和 ==

  • Object.is(NaN, NaN) ===> true; Object.is(+0, -0) ===> false
  • NaN == NaN ===> false; +0 == -0 ===> true

padStart / padEnd

旨在提供更方便的字符串填充功能。 padStart允许在字符串的开头填充空格或其他字符, padEnd允许在字符串的末尾处填充空格或其他字符。 直到字符串达到指定的长度(采用最大长度+第一部分填充)

- padStart(maxLen, firstFill) / padEnd(maxLen, firstFill)
     	最大长度:指填充后的字符串的MathLen(str) === maxLen
     	第一部分填充:指取填充字符的首部开始填充,而不是尾部开始计数填充
  
- 以下示例
 	'tang'.padStart(10,'123456789')  ===>  123456tang
  'tang'.padEnd(10, '123456789')  ===>  tang123456

trimStart / trimEnd

功能与trim类似,而trimStart / trimEnd可以只指定去掉字符串的首部空格/尾部空格。

命名与padStart / padEnd统一,trimStart / trimEnd是由 trimLeft / trimRight更改而来。

示例如下:

- 以下示例:
'   sasd   '.trim() ====> 'sasd'
'   sasd   '.trimStart() ====> '   sasd'
'   sasd   '.trimEnd() ====> 'sasd   '

well-formed-stringify

旨在扩展JavaScript中的'JSON.stringify'方法, 使其在处理Unicode字符串时更加符合规范。确保生成的JSON字符串在包含特俗字符,控制字符和代理对时仍然有效。

str.replaceAll(a, b)

看到这个单词replaceAll,想必大家应该能猜到这个函数是用来做什么的了,它就和replace一样,只不过是replace带上/g的全局替代。

string.prototype.matchAll(reg)

matchAll匹配reg匹配到的字符串,返回一个迭代器。通过迭代器可以获取到匹配到的字符串索引

const text = "Hello, my name is John. I live in New York. John is my first name.";

const regex = /John/g; // 正则表达式,匹配 "John"

const matches = text.matchAll(regex);

for (const match of matches) {
  console.log("Match:", match[0]); // 打印匹配到的字符串
  console.log("Index:", match.index); // 打印匹配的起始位置
}

数组类


includes

和上文(字符串类-includes)一致

Array.prototype.findLast / Array.prototype.findLastIndex

顾名思义,找最后的元素 / 找最后的元素索引。

所以这两个方法会从最后一个元素迭代道第一个元素进行搜索。你是否想到了find / findIndex, 是的,findLast / findLastIndex和它们一样,不同之处只在于迭代元素的方向不一致。


Change Array By Copy 通过复制更改数组

toReversed、toSorted、toSpliced

通过复制更改数组,说明当前的数组会被复制,也就是重新拷贝一份,当然在执行逻辑的时候,则会保持目标数组不变,只是返回执行更改后的副本,主要涉及到的方法有以下四个

- Array.prototype.toReversed()
- Array.prototype.toSorted(compareFn)
- Array.prototype.toSpliced(start, deleteCount, ...items)
- Array.prototype.with(index, value)	// 在数组中替换指定索引的位置。

上面的几种方法,前三个都有其对应会改变元素组的方法,分别为以下:
- Array.prototype.reversed()
- Array.prototype.sorted(compareFn)
- Array.prototype.spliced(start, deleteCount, ...items)

实际上,我们也可以通过这三种方法自己来实现其所对应的不改变元素组的方法
Array.prototype.toReversed = function() {
  return this.slice().reverse();
};
Array.prototype.toSorted = function(compareFn) {
  return this.slice().sort(compareFn);
};
Array.prototype.toSpliced = function(start, deleteCount, ...items) {
  const copy = this.slice();
  copy.splice(start, deleteCount, ...items);
  return copy;
};

Array.prototype.at

该方法是ECMAScript2021中引入的方法。它提供了一种通过正数或负数索引从数组中访问元素的方式。极大的方便了我们从数组中获取特定索引位置的元素。

- 以下示例:
	```javascript
	[1, 2, 3].at(-1) === 3
	```

Array.prototype.flat / Array.prototype.flatMap

数组扁平化,flat方法将嵌套的子数组展开,创建一个新的数组, flatMap方法首先使用映射函数对数组中的每个元素进行映射,然后将结果压平成一个新数组。

- Array.prototype.flat
  ```javascript
  [1, [2, [3], 4],[5]].flat(1) === [1, 2, [3], 4, 5]   //展开一层
  [1, [2, [3], 4],[5]].flat(1) === [1, 2, 3, 4, 5]   //展开两层
  ```
- Array.prototype.flatMap
	```javascript
 [1, 2, 3, 4].flatMap(num  => [num, num * 2])  === [1, 2, 2, 4, 3, 6, 4, 8]
  ```

对象类


Object.keys() / Object.values() / Object.entries()

ES2017中引入的特性。这些方法可以更方便的遍历和处理对象的属性。

- 我们假定一个对象为obj = { name: 'tyt', age: 18, hobbies: [1,2,3] }
- Object.keys(obj) ===> ['name','age','hobbies']
- Object.values(obj) ===> [ 'tyt', 18, 'hobbies' ]
- Object.entries(obj) ===> [ ['name', 'tyt'], ['age', 18], ['hobbies', [1, 2, 3]] ]

Object.fromEntries()

- 可以将数组对象转换成对象, 和Object.entries()是反向操作
- Object.fromEntries([['name', 'tyt'], ['age', 18], ['hobbies', [1, 2, 3]]]) ===> { name: 'tyt', age: 18, hobbies: [1,2,3] }

Rest / Spread Properties

Rest / Spread 是在2018年引入的,Rest是用来处理对象/数组的剩余操作,Spread是用来处理对象/数组的展开操作。这两个属性想必在日常开发中会经常使用道,比如在不知道参数个数的情况下,我们用形参...args的方式来接收,这个...+标识符args,就是Rest属性,用来收集剩余的属性并把它们放在args中。 而当我们已知,例如obj = { name: 'tyt', age: 18, hobbies: [1,2,3] },使用...+对象,来展开obj,这就是Spread展开属性

// Rest:
const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a);     // 1
console.log(b);     // 2
console.log(rest);  // { c: 3, d: 4 }

// Spread:
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
console.log(obj2);  // { a: 1, b: 2, c: 3 }

Asynchronous Iterators for JavaScript(Javascript的异步迭代器)

"Asynchronous Iterators "旨在扩展Javascript中的迭代器Iterators功能,以支持异步操作。 我们传统的迭代器仅仅支持同步遍历,每次迭代都需要等待当前元素处理完成之后才能继续下一个元素。“Asynchronous Iterators ”的引入,允许我们更自然的去处理异步操作

- Symbol.asyncInterator:内置符号,用来定义一个异步迭代器方法,使对象成为异步可迭代。
- 定义一个异步迭代器对象:
  const asyncIterable = {
    async *[Symbol.asyncIterator]() {
      yield new Promise(resolve => setTimeout(() => resolve(1), 1000));
      yield new Promise(resolve => setTimeout(() => resolve(2), 2000));
      yield new Promise(resolve => setTimeout(() => resolve(3), 3000));
    }
  };

  <!-- 异步迭代器对象==》异步迭代器 -->
	const asyncIterator = asyncIterable[Symbol.asyncIterator]();
  <!-- 使用.next()方法开始迭代 -->
	const firstIteration = await asyncIterator.next();
	console.log('firstIteration', firstIteration)

globalThis

globalThis是浏览器中的一个全局对象,在ES11中定义的,这个属性可以说是方便了多端开发,多平台开发。正常情况下,我们在浏览器中全局是window, 而Nodejs中是global,这个对象globalThis的出现,使我们不再需要去兼容平台,globalThis在浏览器中自动转为window,而在Nodejs中自动转为global

Object.hasOwn() - 无障碍的Object.prototype.hasOwnProperty()

旨在Object.prototype.hasOwnProperty的无障碍和可读性。总之,以后遇到需要用Object.prototype.hasOwnProperty()的地方就可以都替换成Object.hasOwn().谁叫它是新属性呢?

正则表达式


正则表达式\s(dotAll)标志符

  • 当设置\s(启动dotAll模式),修饰符'.' 会匹配任何字符(包含换行符)
  • 当设置\m(启动mutiple模式)结合\g,修饰符'^'和'$' 会匹配每行的开头和结尾, 而不仅仅是整个输入的开头和结尾
- 以下示例:
  /aaa.bbb/.test('aaa\nbbb')				===>  		false
  /aaa.bbb/s.test('aaa\nbbb')				===>		true (添加上\s,‘.’匹配了\n)

- 除了使用添加\s以外,我们还可以用以下方法: /aaa[^]nnn/.test(aaa\nbbb)来匹配\n

RegExp named capture groups

  • (?...) : 给捕获组命名为name
  • \k : 访问命名组name
- 以下示例:
  ```javascript
  // 可视化如图 1.1 ,表示匹配任意长度开头和结尾相同的字符串,两个字符串中间还有一个字符
  const reg = /^(?<name>.*).\k<name>$/u
  reg.test('1234m1234')  ====>   true
  reg.test('12341234')   ====>   false
  reg.test('a*b')	       ====>   false
  ```

图 1.1

RegExp Lookbehind Assertions

  • 正后向断言: (?<=...) ===> 匹配(?<=...)后面的正则表达式,它是否是以...开头的
  • 否后向断言: (?<! ...) ===> 匹配(?<! ...)后面的正则表达式,它是否不是以...开头的
- 以下示例:
- /(?<!aaa)a/ ==> 匹配一个字符a,这个a的前面不是aaa
  /(?<!aaa)a/.match('aaaa')  ===>  会匹配到索引为0,1,2的a

- /(?<=aaa)a/ ==> 匹配一个字符a,这个a的前面是aaa
  /(?<!aaa)a/.match('aaaa')  ===>  会匹配到索引为3的a

RegExp \d

  • 通过exec执行含有\d的正则匹配时,可以给出匹配到的起始位置和结束位置。
- 例如: m = 'xaaaz'.exec(/a+(?<Zname>z)?/)
- 起始位置:m.indices[0][0]
- 结束位置:m.indices[0][1]
- 'xaaaz'.slice(m.indices[0][0], m.indices[0][1]) == 匹配到的第一个值'aaaz'

Promise


Promise.any

- Promise.any用法:
  处理多个promise。 当多个promise中有一个被resolved(成功) 时立即返回
  忽略掉所有reject(失败)的promise。 如果所有的promise都被reject,那么Promise.any会返回一个AggregateError对象,其中包含了所有的Promise的rejection原因

- 示例如下:
```javascript
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Promise 1 rejected");
  }, 1000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 2 resolved");
  }, 1500);
});

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 3 resolved");
  }, 800);
});

const promises = [promise1, promise2, promise3];

Promise.any(promises)
  .then(result => {
    console.log('At least one promise fulfilled:', result);
  })
  .catch(error => {
    console.log('All promises rejected:', error);
  });

```

Promise.allSettled

- Promise.allSettled用法:
  不短路, 处理多个promise。Promise.allSettled(promiseList)。
	返回所有promise都settled(已完成/已拒绝)后的集合数组

- Promise.allSettled与Promise.all的区别:
  当输入值被拒绝时短路, .all返回一个promise
  1. 若所有.all参数的promise集合全部成功,返回promise成功集合数组。
  2. 若存在一个promise失败,返回最先失败的promise
 
- Promise.allSettled好处:
  可以对多有promise的结果左处理

- 示例如下: 
```javascript
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 1 resolved");
  }, 1000);
});  
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Promise 2 rejected");
  }, 1500);
});
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Promise 3 resolved");
  }, 800);
});

const promises = [promise1, promise2, promise3];

Promise.allSettled(promises)
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('Fulfilled:', result.value);
      } else if (result.status === 'rejected') {
        console.log('Rejected:', result.reason);
      }
    });
  });


// 输出结果
Fulfilled: Promise 1 resolved
Rejected: Promise 2 rejected
Fulfilled: Promise 3 resolved
```

Promise.prototype.finally

无论promise成功与否,都会进入到finally当中,且finally没有参数

Optional catch binding

ECMAScript 2022 (ES12)中引入的新特性。它可以更方便的在某些情况下处理异常,特别是在我们不关心异常参数的时候,这可以帮助我们减少代码中的冗余,并提高代码的可读性。

try {
  // Some code that might throw an exception
  throw new Error("An error occurred");
} catch {
  console.log("Caught an exception, but not interested in the error details");
}

其他


求幂运算符

符号:**
用法:let a = 3; a**3 = a*a*a = a^3;

WeakRefs

WeakMap允许symbol类型作为key

const weakMap = new weakMap()
const symbolKey = Symbol('mySymbol')
const objKey = {}
weakMap.set(symbolKey, 'value from Symbol')
weakMap.set(objKey, 'value from Obj')

(🤔:weakMap和map有什么区别)

  • 当前,第一就是weakMap允许对象和Symbol作为key
  • weakMap是弱引用, 当没有其他引用指针指向sumbolKey或objKey, 它们会被垃圾回收

& / << / >>

  • & : 按位运算符 11 & 15 => 1011 & 1111 => 1011
  • << : 左移 1 << 7 => 1* (2**7) => 2500
  • : 右移 10000 >> 2 => 10000 / (2 ** 2) => 2500

数字分隔符

  • 使用方式:_
  • 数字分隔符主要是为了解决人眼很难快速解析大的数字文字的问题
  • 范围:可用于常规数字,二/十/八/十六进制, BigInt文字
  • 注意:BigInt文字中_只能放在数字与数字之间

logical-assignment / ??

??的用法是该运算符前的表达式为null/undefined时候。才使用??后的表达式。

- 如下示例
const headerText = a.b ?? 'hello'		==> 当a.b为null/undefined时, headerText的值为hello

- 对比 || 
  a || b, 是当a是falsy(假值)的时候才使用b。
	所以如果我们本身a可以是假值之一(比如false),但我们又想判断当a不存在时(undefined),才选b
  这个时候使用a || b 就是一种错误的情况。这个时候就可以用 a ?? b (a === null || a === undefined 时, 取 b)
  

BigInt

import

- 动态导入--使用类似函数调用的语法
- // 动态导入模块
import('./module.js').then(module => {    
  // 使用导入的模块    
  module.someFunction();  
}).catch(error => {    
  console.error("Module import error:", error);
});

TC39链接


TC39的github链接: https://github.com/tc39/proposals/blob/main/finished-proposals.md