ES2016 - ES2021(ES7 - ES12)特性一览(前端进阶1.9)

391 阅读25分钟

ES7新特性(2016)

Array.prototype.includes

Includes 方法用来检测数组中是否包含某个元素,返回布尔类型值。

includes 函数与 indexOf 函数很相似,下面两个表达式是等价的:

arr.includes(x)
arr.indexOf(x) >= 0

在ES7之前的做法是使用indexOf()验证数组中是否存在某个元素,这时需要根据返回值是否为-1来判断;在ES7之后使用includes()验证数组中是否存在某个元素,这样更加直观简单:

// includes   indexOf
const mingzhu = ['西游记','红楼梦','三国演义','水浒传'];

//判断
console.log(mingzhu.includes('西游记')); // true
console.log(mingzhu.includes('金瓶梅')); // false

指数操作符

ES7 中引入指数运算符「**」,用来实现幂运算,功能与 Math.pow 结果相同

// **
console.log(2 ** 10); // 1024
console.log(Math.pow(2, 10)); // 1024

ES8新特性(2017)

async 和 await

asyncawait 两种语法结合可以让异步代码像同步代码一样

async 函数

  • async 函数的返回值为 promise 对象,
  • promise 对象的结果由 async 函数执行的返回值决定 例子1
//async 函数
async function fn(){
    // 返回一个字符串
    return '测试';
}

const result = fn();

//调用 then 方法
result.then(value => {
    console.log(value);// 测试
}, reason => {
    console.warn(reason);
})

async 函数 return 的结果是一个字符串, 但返回的结果是一个成功 Promise 对象;

例子2

//async 函数
async function fn(){
    //抛出错误, 返回的结果是一个失败的 Promise
    throw new Error('出错啦!');
}

const result = fn();

//调用 then 方法
result.then(value => {
    console.log(value);
}, reason => {
    console.warn(reason);// Error: 出错啦!
})

例子3

//async 函数
async function fn(){
    //返回的结果如果是一个 Promise 对象
    return new Promise((resolve, reject)=>{
        resolve('成功的数据');
    });
}

const result = fn();

//调用 then 方法
result.then(value => {
    console.log(value);// 成功的数据
}, reason => {
    console.warn(reason);
})

例子4

//async 函数
async function fn(){
    //返回的结果如果是一个 Promise 对象
    return new Promise((resolve, reject)=>{
        reject("失败的错误");
    });
}

const result = fn();

//调用 then 方法
result.then(value => {
    console.log(value);
}, reason => {
    console.warn(reason);// 失败的错误
})

await 表达式

  • await 必须写在 async 函数中
  • await 右侧的表达式一般为 promise 对象
  • await 返回的是 promise 成功的值
  • awaitpromise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理
//创建 promise 对象
const p = new Promise((resolve, reject) => {
    // resolve("用户数据");
    reject("失败啦!");
})

// await 要放在 async 函数中.
async function main() {
    try {
        let result = await p;
        console.log(result);
    } catch (e) {
        console.log(e);// 失败啦
    }
}
//调用函数
main();

async与await封装AJAX请求

// 发送 AJAX 请求, 返回的结果是 Promise 对象
function sendAJAX(url) {
    return new Promise((resolve, reject) => {
        //1. 创建对象
        const x = new XMLHttpRequest();

        //2. 初始化
        x.open('GET', url);

        //3. 发送
        x.send();

        //4. 事件绑定
        x.onreadystatechange = function () {
            if (x.readyState === 4) {
                if (x.status >= 200 && x.status < 300) {
                    //成功啦
                    resolve(x.response);
                }else{
                    //如果失败
                    reject(x.status);
                }
            }
        }
    })
}

//promise then 方法测试
// sendAJAX("https://api.apiopen.top/getJoke").then(value=>{
//     console.log(value);
// }, reason=>{})

// async 与 await 测试  axios
async function main(){
    //发送 AJAX 请求
    let result = await sendAJAX("https://api.apiopen.top/getJoke");
    //再次测试
    let tianqi = await sendAJAX('https://www.tianqiapi.com/api/?version=v1&city=%E5%8C%97%E4%BA%AC&appid=23941491&appsecret=TXoD5e8P')

    console.log("result",result)
    console.log("tianqi",tianqi);
}

main();

Object.values 和 Object.entries

  1. Object.values()方法返回一个给定对象的所有可枚举属性值的数组
  2. Object.entries()方法返回一个给定对象自身可遍历属性 [key,value] 的数组
//声明对象
const school = {
    name:"学习",
    cities:['北京','上海','深圳'],
    xueke: ['前端','Java','大数据','运维']
};

//获取对象所有的键
console.log(Object.keys(school));// ["name", "cities", "xueke"]
//获取对象所有的值
console.log(Object.values(school));// ["学习", ['北京','上海','深圳'], ['前端','Java','大数据','运维']]
//entries
console.log(Object.entries(school));// [["name", "学习"],["cities", ['北京','上海','深圳']],["xueke", ['前端','Java','大数据','运维']]]
//创建 Map
const m = new Map(Object.entries(school));
console.log(m.get('cities'));// ["北京", "上海", "深圳"]

String padding

ES8String新增了两个实例函数String.prototype.padStartString.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。

String.padStart(targetLength,[padString])

  • targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
  • padString:(可选)填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为空格(" ")。
console.log('0.0'.padStart(4,'10')) 
console.log('0.0'.padStart(5,'10')) 
console.log('0.0'.padStart(7,'10')) 
console.log('0.0'.padStart(20))

String.padEnd(targetLength,padString])

  • targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
  • padString:(可选) 填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为空格(" ")。
console.log('0.0'.padEnd(4,'0')) //0.00  
console.log('0.0'.padEnd(10,'0'))//0.00000000

Object.getOwnPropertyDescriptors

该方法返回指定对象所有自身属性的描述对象,如果没有任何自身属性,则返回空对象。

//声明对象
const school = {
    name:"学习",
    cities:['北京','上海','深圳'],
    xueke: ['前端','Java','大数据','运维']
};
//对象属性的描述对象
console.log(Object.getOwnPropertyDescriptors(school));
//{
//  cities: {value: Array(3), writable: true, enumerable: true, configurable: true}
//  name: {value: "学习", writable: true, enumerable: true, configurable: true}
//  xueke: {value: Array(4), writable: true, enumerable: true, configurable: true}
//}

函数参数列表和调用中的尾逗号(Trailing commas)

尾逗号在函数定义中只是一个纯粹语法变化,在ES5中,将会非法语法,在函数参数后面应该是没有逗号的:

var f = function(a,b,c,d) {
  // ...
  console.log(d)// this
}
f(1,2,3,'this')

ES8中,这种尾逗号是没有问题的:

var f = function(a,b,c,d,) { 
  // ...
  console.log(d)// this
}
f(1,2,3,'this')

尾逗号主要有用在使用多行参数风格(典型的是那些很长的参数名),开发者终于可以忘记逗号放在第一位这种奇怪的写法。自从逗号bugs主要原因就是使用他们。而现在你可以到处使用逗号,甚至最后参数都可以。

SharedArrayBuffer对象

SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。

请注意,作为对Spectre的响应,所有主流浏览器均默认于2018年1月5日禁用SharedArrayBuffer。 Chrome在启用了网站隔离功能的平台上的v67中重新启用了该功能,以防止出现Spectre风格的漏洞。 语法: new SharedArrayBuffer(length)

参数length指所创建的数组缓冲区的大小,以字节(byte)为单位。

  • 需要new运算符构造
// 创建一个1024字节的缓冲
let buffer = new SharedArrayBuffer(1024);
console.log(buffer.byteLength); // 1024

Atomics对象

Atomics 对象提供了一组静态方法对 SharedArrayBufferArrayBuffer 对象进行原子操作。

这些原子操作属于 Atomics 模块。与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。

多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。

Atomics.add(typedArray, index, value)

将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。 参数:

  • typedArray是一个整型 的类型数组。例如Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,或者Uint32Array。indext, 数组中的位置,该位置数值会被加总并更新。value增加的数字。
  • 返回值: 返回给定位置的旧值
  • 如果typedArray不是符合的格式,就会报错
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
console.log(ta[0]); // 0

ta[0] = 5;
console.log(ta[0]) // 5

Atomics.add(ta, 0, 12); //将下标为0的值加12, 返回ta[0]
console.log(ta[0]); // 17
console.log(Atomics.load(ta, 0)); // 17 ✅  // 12 ❌

Atomics.load(typedArray, index)

返回数组中指定元素的值。

Atomics.and(typedArray, index, value)

将指定位置上的数组元素与给定的值相加,并返回与操作前该元素的值。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;

console.log(Atomics.and(ta, 0, 1)); // 5
console.log(Atomics.load(ta, 0)); // 1

Atomics.compareExchange(typedArray, index, expectedValue, replacementValue)

如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);

ta[0] = 5;

// 给定的值与指定元素值相等时
console.log(Atomics.compareExchange(ta, 0, 5, 12));// 5
console.log(Atomics.load(ta, 0))// 12

// 给定的值与指定元素值不相等时
console.log(Atomics.compareExchange(ta, 0, 6, 12));// 5
console.log(Atomics.load(ta, 0))// 5

Atomics.exchange(typedArray, index, value)

将数组中指定的元素更新为给定的值,并返回该元素更新前的值。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);

ta[0] = 5;
console.log(Atomics.exchange(ta, 0, 12));// 6
console.log(Atomics.load(ta, 0));// 12

Atomics.isLockFree(size)

可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回 true;否则就意味着对于该数组,Atomics 对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。 换种说法:表示Atomics对象是否可以处理某个size的内存锁定。如果返回false,应用程序就需要自己来实现锁定。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;

console.log(Atomics.isLockFree(1));// true
console.log(Atomics.isLockFree(2));// true
console.log(Atomics.isLockFree(3));// false
console.log(Atomics.isLockFree(4));// true

Atomics.notify(typedArray, index, count)

唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。 参数:

  • sharedArray:共享内存的视图数组。
  • index:视图数据的位置(从0开始)。
  • count:需要唤醒的 Worker 线程的数量,默认为Infinity。

Atomics.or(typedArray, index, value)

将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);

console.log(Atomics.or(ta, 0, 1));// 0 
console.log(Atomics.load(ta, 0));  // 1

Atomics.store(typedArray, index, value)

将数组中指定的元素设置为给定的值,并返回该值。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);

ta[0] = 5;
console.log(Atomics.store(ta, 0, 12));// 12

Atomics.sub(typedArray, index, value)

将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);

ta[0] = 5;
console.log(Atomics.sub(ta, 0, 2));// 5
console.log(Atomics.load(ta, 0));// 3

Atomics.wait(typedArray, index, value, timeout)

检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 "ok"、"not-equal" 或 "time-out"。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用 wait())。 参数:

  • sharedArray:共享内存的视图数组。
  • index:视图数据的位置(从0开始)。
  • value:该位置的预期值。一旦实际值等于预期值,就进入休眠。
  • timeout:整数,表示过了这个时间以后,就自动唤醒,单位毫秒。该参数可选,默认值是Infinity,即无限期的休眠,只有通过Atomics.notify()方法才能唤醒。 Atomics.xor(typedArray, index, value)

将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。

const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);

ta[0] = 5;
console.log(Atomics.xor(ta, 0, 1));// 5
console.log(Atomics.load(ta, 0));// 4

ES9新特性(2018)

异步迭代器(for await...of)

for...of循环用于遍历同步的 Iterator 接口。2⃣而新引入的for await...of循环,则是用于遍历异步的 Iterator 接口

手写一个异步迭代器

let createAsyncIterable = {
  [Symbol.asyncIterator]:() => {
    const items = [`a`, `s`, `y`, `n`, `c`];
    return {
      next: () => Promise.resolve({
        done: items.length === 0,
        value: items.shift()
      })
    }
  }
};

我们可以使用如下代码进行遍历:

for await (const item of createAsyncIterable) {
    console.log(item);
}

如果遇到了 Uncaught SyntaxError: Unexpected reserved word 错误,那是因为 for-await-of 只能在 async 函数或者 async 生成器里面使用。

修改一下:

(async function () {
  for await (const item of createAsyncIterable) {
    console.log(item); // a  s  y  n  c
  }
})();

根据上面的代码,我们可以知道,异步迭代器返回结果是一个Promise对象,如果next方法返回的 Promise 对象被rejectfor await...of就会报错,要用try...catch捕捉。

let createAsyncIterable = {
  [Symbol.asyncIterator]:() => {
    const items = [`a`, `s`, `y`, `n`, `c`];
    return {
      next:() => Promise.reject({
        err:"出错啦"
      })
    }
  }
};

(async function () {
  try{
    for await (const item of createAsyncIterable) {
      console.log(item); 
    }
  }catch(e){
    console.log(e);// {err: "出错啦"}
  }
})()

Promise.prototype.finally()

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

const Gen = (time) => {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      if (time < 500) {
        reject(time)
      } else {
        resolve(time)
      }
    }, time)
  })
}
Gen(Math.random() * 1000)
  .then(val => console.log('resolve', val))
  .catch(err => console.log('reject', err))
  .finally(() => { // 不管走resolve 还是reject 都要走这里
    console.log('finish')
  })

finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

使用finally的好处就是避免同样的语句需要在成功和失败两种情况中各写一次的情况。

Rest/Spread 属性

Rest 参数与 spread 扩展运算符在 ES6 中已经引入,不过 ES6 中只针对于数组, 在 ES9 中为对象提供了像数组一样的 rest 参数和扩展运算符;

//rest 参数
function connect({host, port, ...user}){
    console.log(host);// 127.0.0.1
    console.log(port); // 3306
    console.log(user);// {username: 'root',password: 'root',type: 'master'}
}

connect({
    host: '127.0.0.1',
    port: 3306,
    username: 'root',
    password: 'root',
    type: 'master'
});


//对象合并
const skillOne = {
    q: '天音波'
}
const skillTwo = {
    w: '金钟罩'
}
const mangseng = {...skillOne, ...skillTwo};
console.log(mangseng) // {q: "天音波", w: "金钟罩"}

正则表达式命名捕获分组

ES9 允许命名捕获分组使用符号『?<name>』,这样获取捕获结果可读性更强

//声明一个字符串
let str = '<a href="https://www.baidu.com/">百度一下</a>';

//提取 url 与 『标签文本』
const reg = /<a href="(.*)">(.*)<\/a>/;

//执行
const result = reg.exec(str);

console.log("result",result);
console.log("result[1]",result[1]);
console.log("result[2]",result[2]);

接下来对上面的代码进行分组

let str = '<a href="https://www.baidu.com/">百度一下</a>';
//分组命名
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;

const result = reg.exec(str);

console.log(result.groups.url);// https://www.baidu.com/

console.log(result.groups.text);// 百度一下

正则表达式反向断言

ES9 支持反向断言,通过对匹配结果前面的内容进行判断,对匹配进行筛选。

//声明字符串
let str = 'JS5211314你知道么555啦啦啦';
//正向断言
const reg = /\d+(?=啦)/;
const result = reg.exec(str);// ["555"]

//反向断言
const reg = /(?<=么)\d+/;
const result = reg.exec(str);// ["555"]

正则表达式 dotAll 模式

正则表达式中点.匹配除回车外的任何单字符,标记『s』改变这种行为,允许行 终止符出现

let str = `
<ul>
  <li>
    <a>肖生克的救赎</a>
    <p>上映日期: 1994-09-10</p>
  </li>
  <li>
    <a>阿甘正传</a>
    <p>上映日期: 1994-07-06</p>
  </li>
</ul>`;
//声明正则
// const reg = /<li>\s+<a>(.*?)<\/a>\s+<p>(.*?)<\/p>/;
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;
//执行匹配
// const result = reg.exec(str);
let result;
let data = [];
while(result = reg.exec(str)){
  data.push({title: result[1], time: result[2]});
}
//输出结果
console.log(data);

正则表达式 Unicode 转义

到目前为止,在正则表达式中本地访问 Unicode 字符属性是不被允许的。ES2018添加了 Unicode 属性转义——形式为\p{...}\P{...},在正则表达式中使用标记 u (unicode) 设置,在\p块儿内,可以以键值对的方式设置需要匹配的属性而非具体内容。例如:

/^\p{White_Space}+$/u.test(' ') // 空格
// true
/^\p{Script=Greek}+$/u.test('μετά') // 希腊字母
// true
/^\p{Script=Latin}+$/u.test('Grüße') // 匹配拉丁字母
// true
/^\p{Surrogate}+$/u.test('\u{D83D}') // 匹配单独的替代字符
// true

此特性可以避免使用特定 Unicode 区间来进行内容类型判断,提升可读性和可维护性。

非转义序列的模板字符串

ES2018 移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。

之前,\u开始一个 unicode 转义,\x开始一个十六进制转义,\后跟一个数字开始一个八进制转义。这使得创建特定的字符串变得不可能,例如Windows文件路径 C:\uuu\xxx\111

ES10新特性(2019)

更加友好的 JSON.stringify

根据标准,JSON 数据必须是 UTF-8 编码。但是,现在的JSON.stringify()方法有可能返回不符合 UTF-8 标准的字符串。

具体来说,UTF-8 标准规定,0xD800到0xDFFF之间的码点,不能单独使用,必须配对使用。比如,\uD834\uDF06是两个码点,但是必须放在一起配对使用,代表字符?。这是为了表示码点大于0xFFFF的字符的一种变通方法。单独使用\uD834和\uDFO6这两个码点是不合法的,或者颠倒顺序也不行,因为\uDF06\uD834并没有对应的字符。

JSON.stringify()的问题在于,它可能返回0xD800到0xDFFF之间的单个码点。

JSON.stringify('\u{D834}') // "\u{D834}"

为了确保返回的是合法的 UTF-8 字符,ES2019 改变了JSON.stringify()的行为。如果遇到0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。

JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
console.log(JSON.stringify('𝌆'))// "𝌆"
console.log(JSON.stringify('\uD834\uDF06'))// "𝌆"

Object.fromEntries

//Map
const m = new Map();
m.set('name','ATGUIGU');
const result = Object.fromEntries(m);
console.log(result); // {name: "ATGUIGU"}


//Object.entries ES8
const arr = Object.entries({
    name: "测试"
})
console.log(arr);// ["测试"]

trimStart 和 trimEnd

// trim
let str = '   iloveyou   ';

console.log(str);//   iloveyou
console.log(str.trimStart());//iloveyou   去除字符串头部的空格
console.log(str.trimEnd());//   iloveyou 去除字符串尾部的空格

Array.prototype.flat 与 flatMap

//flat 平
//将多维数组转化为低维数组
const arr1 = [1,2,3,4,[5,6]];
const arr2 = [1,2,3,4,[5,6,[7,8,9]]];
//参数为深度 是一个数字
console.log(arr1.flat(2)); // [1,2,3,4,5,6]
console.log(arr2.flat(2)); // [1,2,3,4,5,6,7,8,9]

//flatMap
const arr = [1,2,3,4];
const result = arr.flatMap(item => [item * 10]);
console.log(result); // [10, 20, 30, 40]

Symbol.prototype.description

//创建 Symbol
let s = Symbol('测试');

console.log(s.description);// 测试

BigInt:任意精度整数

BigInt 是第七种 原始类型BigInt 是一个任意精度的整数。这意味着变量现在可以 表示²⁵³ 数字,而不仅仅是9007199254740992

const b = 1n;  // 追加 n 以创建 BigInt

在过去,不支持大于 9007199254740992 的整数值。如果超过,该值将锁定为 MAX_SAFE_INTEGER + 1:

const limit = Number.MAX_SAFE_INTEGER;
⇨ 9007199254740991
limit + 1;
⇨ 9007199254740992
limit + 2;
⇨ 9007199254740992 <--- MAX_SAFE_INTEGER + 1 exceeded
const larger = 9007199254740991n;
⇨ 9007199254740991n
const integer = BigInt(9007199254740991); // initialize with number
⇨ 9007199254740991n
const same = BigInt("9007199254740991"); // initialize with "string"
⇨ 9007199254740991n

typeof

typeof 10;
⇨ 'number'
typeof 10n;
⇨ 'bigint'

等于运算符可用于两种类型之间比较

10n === BigInt(10);
⇨ true
10n == 10;
⇨ true

数学运算符只能在自己的类型中工作

200n / 10n20n
200n / 20Uncaught TypeError:
   Cannot mix BigInt and other types, use explicit conversions <

-运算符可以操作, + 不可用

-100n
⇨ -100n
+100nUncaught TypeError:
  Cannot convert a BigInt value to a number

ES11新特性(2020)

String.prototype.matchAll

matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// Array ["test2", "e", "st2", "2"]

语法:str.matchAll(regexp)

参数:regexp正则表达式对象。如果所传参数不是一个正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为一个 RegExp

RegExp必须是设置了全局模式g的形式,否则会抛出异常TypeError

返回值:一个迭代器(不可重用,结果耗尽需要再次调用方法,获取一个新的迭代器)。

matchAll 出现之前,通过在循环中调用regexp.exec来获取所有匹配项信息(regexp需使用/g标志):

const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
while ((matches = regexp.exec(str)) !== null) {
  console.log(`Found ${matches[0]}. Next starts at ${regexp.lastIndex}.`);
  // Found foo. Next starts at 9.
  // Found foo. Next starts at 19.
}
let str = `<ul>
    <li>
        <a>肖生克的救赎</a>
        <p>上映日期: 1994-09-10</p>
    </li>
    <li>
        <a>阿甘正传</a>
        <p>上映日期: 1994-07-06</p>
    </li>
</ul>`;

//声明正则
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/sg

//调用方法
const result = str.matchAll(reg);

// for(let v of result){
//     console.log(v);
// }

const arr = [...result];

console.log(arr);

类的私有属性

class Person{
    //公有属性
    name;
    //私有属性
    #age;
    #weight;
    //构造方法
    constructor(name, age, weight){
        this.name = name;
        this.#age = age;
        this.#weight = weight;
    }

    intro(){
        console.log(this.name);
        console.log(this.#age);
        console.log(this.#weight);
    }
}

//实例化
const girl = new Person('晓红', 18, '45kg');
console.log(girl.name);
console.log(girl.#age); 
console.log(girl.#weight);

我们创建一个Person类,并且已初始化,接下来输出该类对应的属性,输出后发现报错。 接下来我们把上面代码中的三个输出项注释,调用类里面的intro()方法,发现可以正常输出。

//实例化
const girl = new Person('晓红', 18, '45kg');
girl.intro();

这说明类定义的私有属性,不可在类的外面调用,只能在该类的内部进行使用。

Promise.allSettled

//声明两个promise对象
const p1 = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve('商品数据 - 1');
    },1000)
});

const p2 = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve('商品数据 - 2');
        // reject('出错啦!');
    },1000)
});

//调用 allsettled 方法
const result = Promise.allSettled([p1, p2]);

const res = Promise.all([p1, p2]);

console.log(result);

console.log(res);

再将上面两个promise对象中的一个返回不成功时(修改p2),我们再观察一下输出结果:

//声明两个promise对象
const p1 = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        resolve('商品数据 - 1');
    },1000)
});

const p2 = new Promise((resolve, reject)=>{
    setTimeout(()=>{
        //resolve('商品数据 - 2');
        reject('出错啦!');
    },1000)
});

//调用 allsettled 方法
const result = Promise.allSettled([p1, p2]);

const res = Promise.all([p1, p2]);

console.log(result);

console.log(res);

根据上面的例子,声明两个promise对象都是resolve时输出,以及其中有reject时的输出对比,用Promise.allSettled()Promise.all()接收这两个promise对象,对比这两个函数的输出结果,我们可以得出如下结论:

对于包装实例本身

相同点:Promise.all()Promise.allSettled()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

不同点:

  • Promise.allSettled()不管参数中的promisefulfilled还是rejected,都会等参数中的实例都返回结果,包装实例才会结束。
  • Promise.all()中的参数的状态都变成fulfilled,返回的结果状态才会变成fulfilled;如果参数之中有一个被rejected,返回结果的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给结果的回调函数。

返回的数据结构

Promise.allSettled():回调接收的结果与入参时的promise实例一一对应,且结果的每一项都是一个对象,告诉你结果和值,对象内都有一个属性叫“status”,用来明确知道对应的这个promise实例的状态(fulfilledrejected),fulfilled时,对象有value属性,rejected时有reason属性,对应状态的返回值。

适用场景:promise.allSettled()不论接受入参的promise本身的状态,会返回所有promise的结果,但这一点Promise.all做不到,如果你需要知道所有入参的异步操作的所有结果,或者需要知道这些异步操作是否全部结束,应该使用promise.allSettled()。如多张图片选择后一起上传时需判断多张图片的异步上传结果,成功了几张,失败了几张,这时我们就可以选择用promise.allSettled()

Promise.all():只有当所有入参的promise实例都是fulfilled状态,才会在Promise.all().then()方法中结果,返回结果也是与入参一一对应,结果中只包含实际的resolve的结果,不包含类似allSettledstatusvalue属性。

可选链操作符

// ?.
function main(config){
    // const dbHost = config && config.db && config.db.host; 等价于下面的代码
    const dbHost = config?.db?.host;

    console.log(dbHost);//4-可选链操作符.html:15 192.168.1.100
}

main({
    db: {
        host:'192.168.1.100',
        username: 'root'
    },
    cache: {
        host: '192.168.1.200',
        username:'admin'
    }
})

动态 import 导入

需要的时候才引入js

// demo.js
export default hello(){
    console.log("hello world")
}

// 在需要的地方动态引入
btn.onclick = function(){
    import('./demo.js').then(module=>{
       module.hello()
    })
}
//或者如下动态引入
<script src="./demo.js" type="module"></script>

globalThis 对象

在哪里使用都是全局对象。

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是window,但 NodeWeb Worker 没有window

  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self

  • Node 里面,顶层对象是global,但其他环境都不支持。 同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

  • 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined

  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined

  • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。 综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

// 方法一
(typeof window !== 'undefined'
   ? window
   : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
     ? global
     : this);

// 方法二
var getGlobal = function () {
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('unable to locate global object');
};

ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this

ES12新特性(2021)

String.prototype.replaceAll(searchValue, replaceValue)

看到replaceAll这个词,相比很容易联想到replace。在JavaScript中,replace方法只能是替换字符串中匹配到的第一个实例字符,而不能进行全局多项匹配替换,唯一的办法是通过正则表达式进行相关规则匹配替换。

replaceAll则是返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。

let string = 'I like 前端,I like 前端公虾米'

//使用replace
let replaceStr = string.replace('like','love')
console.log(replaceStr)  // 'I love 前端,I like前端公虾米'

//replace使用正则匹配所有
console.log(string.replace(/like/g,'love')) // 'I love 前端,I love 前端公虾米'

//使用replaceAll
let replaceAllStr = string.replaceAll('like','love')
console.log(replaceAllStr) // 'I love 前端,I love 前端公虾米'

需要注意的是,replaceAll在使用正则表达式的时候,如果非全局匹配(/g),则replaceAll()会抛出一个异常

let string = 'I like 前端,I like 前端公虾米'
console.log(string.replaceAll(/like/,'love')) //TypeError

Promise.any

Promise列表中的任意一个promise成功resolve则返回第一个resolve的结果状态; 如果所有的promisereject,则抛出异常表示所有请求失败。

Promise.any([
  new Promise((resolve, reject) => setTimeout(reject, 500, '哎呀,我被拒绝了')),
  new Promise((resolve, reject) => setTimeout(resolve, 1000, '哎呀,她接受我了')),
  new Promise((resolve, reject) => setTimeout(resolve, 2000, '哎呀,她也接受我了')),
])
.then(value => console.log(`输出结果: ${value}`))
.catch (err => console.log(err))

//输出
//输出结果:哎呀,她接受我了

再来看下另一种情况

Promise.any([
  Promise.reject('Error 1'),
  Promise.reject('Error 2'),
  Promise.reject('Error 3')
])
.then(value => console.log(`请求结果: ${value}`))
.catch (err => console.log(err))

//输出
AggregateError: All promises were rejected

Promise.anyPromise.race十分容易混淆,务必注意区分,Promise.race 一旦某个promise触发了resolve或者reject,就直接返回了该状态结果,并不在乎其成功或者失败。

WeakRefs

使用WeakRefsClass类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)

当我们通过(constletvar)创建一个变量时,垃圾收集器GC将永远不会从内存中删除该变量,只要它的引用仍然存在可访问。WeakRef对象包含对对象的弱引用。对对象的弱引用是不会阻止垃圾收集器GC恢复该对象的引用,则GC可以在任何时候删除它。

WeakRefs在很多情况下都很有用,比如使用Map对象来实现具有很多需要大量内存的键值缓存,在这种情况下最方便的就是尽快释放键值对占用的内存。

目前,可以通过WeakMap()或者WeakSet()来使用WeakRefs

举个栗子

我想要跟踪特定的对象调用某一特定方法的次数,超过1000条则做对应提示

let map = new Map()
function doSomething(obj){
	...
}
function useObject(obj){
	doSomething(obj)
  
  let called = map.get(obj) || 0
  called ++ 
  
  if(called>1000){
     console.log('当前调用次数已经超过1000次了,over')
  }
  
  map.set(obj, called)
}

如上虽然可以实现我们的功能,但是会发生内存溢出,因为传递给doSomething函数的每个对象都永久保存在map中,并且不会被GC回收,因此我们可以使用WeakMap

let wmap = new WeakMap()
function doSomething(obj){
	...
}
function useObject(obj){
	doSomething(obj)
  
  let called = wmap.get(obj) || 0
  
  called ++
  
  if(called>1000){
     console.log('当前调用次数已经超过1000次了,over')
  }
  
  wmap.set(obj, called)
}

因为是弱引用,所以WeakMapWeakSet的键值对是不可枚举

WeakSetWeakMap相似,但是每个对象在WeakSet中的每个对象只可能出现一次,WeakSet中所有对象都是唯一的

let ws = new WeakSet()
let foo = {}
let bar = {}

ws.add(foo)
ws.add(bar)

ws.has(foo) //true
ws.has(bar) //true

ws.delete(foo) //删除foo对象
ws.has(foo) //false 已删除
ws.has(bar) //仍存在

WeakSetSet相比有以下两个区别

WeakSet只能是对象集合,而不能是任何类型的任意值;

WeakSet弱引用,集合中对象引用为弱引用,如果没有其他对WeakSet对象的引用,则会被GC回收,最后,WeakRef实例有一个方法deref,返回引用的原始对象,如果原始对象被回收,则返回undefined

const cache = new Map();

const setValue =  (key, obj) => {
  cache.set(key, new WeakRef(obj));
};

const getValue = (key) => {
  const ref = cache.get(key);
  if (ref) {
    return ref.deref();
  }
};

const fibonacciCached = (number) => {
  const cached = getValue(number);
  if (cached) return cached;
  const sum = calculateFibonacci(number);
  setValue(number, sum);
  return sum;
};

对于缓存远程数据来说,这可能不是一个好主意,因为远程数据可能会不可预测地从内存中删除。在这种情况下,最好使用LRU之类的缓存。

逻辑运算符和赋值表达式

逻辑运算符和赋值表达式,新特性结合了逻辑运算符(&&,||,??)和赋值表达式而JavaScript已存在的复合赋值运算符有:

  • 操作运算符:+=、 -= 、*= 、/= 、%= 、**=

  • 位操作运算符:&=、 ^=、 |=、

  • 按位运算符:<<=、 >>=、 >>>=

现有的的运算符,其工作方式都可以如此来理解

表达式:a op= b

等同于:a = a op b

逻辑运算符和其他的复合赋值运算符工作方式不同

表达式:a op= b

等同于:a = a op (a = b)

a ||= b
//等价于
a = a || (a = b)

a &&= b
//等价于
a = a && (a = b)

a ??= b
//等价于
a = a ?? (a = b)

为什么不再是跟以前的运算公式a = a op b一样呢,而是采用a = a op (a = b)。因为后者当且仅当a的值为false的时候才计算赋值,只有在必要的时候才执行分配,而前者的表达式总是执行赋值操作。

??=可用来补充/初始化缺失的属性

const pages = [
  {
  	title:'主会场',
    path:'/'
  },
  {
    path:'/other'
  },
  ...
]
  
for (const page of pages){
	page.title ??= '默认标题'
}
console.table(pages)
//(index)  title       		path
//0        "主会场"   	  "/"
//1        "默认标题"  	 "/other"

小结: &&=:当LHS值存在时,将RHS变量赋值给LHS ||=:当LHS值不存在时,将RHS变量赋值给LHS ??= :当LHS值为null或者undefined时,将RHS变量赋值给LHS

数字分隔符

数字分隔符,可以在数字之间创建可视化分隔符,通过_下划线来分割数字,使数字更具可读性

const money = 1_000_000_000

//等价于
const money = 1000000000

const totalFee = 1000.12_34
//等价于
const totalFee = 1000.1234

该新特性同样支持在八进制数中使用

const number = 0o123_456
//等价于
const number = 0o123456