JavaScript-ES11~ES12新特性使用教程

x33g5p2x  于2022-07-19 转载在 Java  
字(10.4k)|赞(0)|评价(0)|浏览(304)

JavaScript-ES9~ES10新特性使用教程

ES11

动态 import ()按需导入

用了实现按需导入,import()是一个类似函数的语法关键字,类似super(),它接收一个字符串作为模块标识符,并返回一个 promise
在 ES 2015 定义的模块语法中,所有模块导入语法都是静态声明的

import aExport from "./module"
import * as exportName from "./module"
import { export1, export2 as alias2 } from "./module"
import "./module"

虽然这套语法已经可以满足绝大多数的导入需求,而且还可以支持实现静态分析以及树抖动等一系列重要的功能。但却无法满足一些需要动态导入的需求。例如:

  • 需要根据浏览器兼容性有选择地加载一些支持库,
  • 在实际需要时才加载某个模块的代码,再
  • 只是单纯地希望延迟加载某些模块来以渐进渲染的方式改进加载体验
    在实际工作中也算是比较常见的需求。若没有动态导入,将难以实现这些需求。虽然我们可以通过创建 script 标签来动态地导入某些脚本,但这是特定于浏览器环境的实现方式,也无法直接和现有的模块语法结合在一起使用,所以只能作为内部实现机制,但不能直接暴露给模块的使用者。

但是动态 import () 解决了这个问题。他可以在任何支持该语法的平台中使用,比如 webpack、node 或 浏览器环境。并且模块标识符的格式则是由各平台自行指定,比如 webpack 及 node 支持使用模块名直接加载 node_modules 中的模块,而浏览器支持使用 url 加载远程模块。

const moduleSpecifier = './utils.mjs';
  import(moduleSpecifier)
    .then((module) => {
      module.default();
      // → logs 'Hi from the default export!'
      module.doStuff();
      // → logs 'Doing stuff…'
    });

于 import() 返回一个 Promise,就可以使用 async/await 来代替 Promise 的 then() 调用风格。

(async () => {
    const moduleSpecifier = './utils.mjs';
    const module = await import(moduleSpecifier)
    module.default();
    // → logs 'Hi from the default export!'
    module.doStuff();
    // → logs 'Doing stuff…'
  })();

尽管 import() 看起来类似一个函数调用,实际上它是一个语法,只是使用了圆括号而已,类似 super()。这意味着 import() 的原型并不是 Function.prototype,所以你也不能对它使用 call() 或者 apply()。

空值合并运算符(?? )

空值合并操作符(??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

换句话说,如果第一个参数不是 null/undefined,则 ?? 返回第一个参数。否则,返回第二个参数。

空值合并运算符并不是什么全新的东西。它只是一种获得两者中的第一个“已定义的”值的不错的语法。

我们可以使用我们已知的运算符重写 result = a ?? b,像这样:

result = (a !== null && a !== undefined) ? a : b;

通常 ?? 的使用场景是,为可能是未定义的变量提供一个默认值。

clet user = "John";
alert(user ?? "Anonymous"); // John

我们还可以使用 ?? 序列从一系列的值中选择出第一个非 null/undefined 的值。

let firstName = null;
let lastName = null;
let nickName = "Supercoder";

// 显示第一个已定义的值
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder

与 || 比较
或 || 运算符自 JavaScript 诞生就存在,因此开发者长期将其用于这种目的。另一方面,空值合并运算符 ?? 是最近才被添加到 JavaScript 中的,它的出现是因为人们对 || 不太满意。
它们之间重要的区别是:

  • || 返回第一个 真 值。
  • ?? 返回第一个 已定义的 值。

换句话说 || 无法区分 false、0、空字符串 “” 和 null/undefined。它们都一样 —— 假值(falsy values)。

出于安全原因,JavaScript 禁止将 ?? 运算符与 && 和 || 运算符一起使用,除非使用括号明确指定了优先级。 下面的代码会触发一个语法错误:let x = 1 && 2 ?? 3; // Syntax error 可以明确地使用括号来解决这个问题: let x = (1 && 2) ?? 3; // 正常工作了

可选链接?

当我们需要尝试访问某个对象中的属性或方法而又不确定该对象是否存在时,该语法可以极大的简化我们的代码,比如下面这种情况:

const el = document.querySelector(".class-a")
const height = el.clientHeight

当我们并不知道页面中是否真的有一个类名为 class-a 的元素,因此在访问clientHeight之前为了防止bug产生需要先进行一些判断:

const height = el ? el.clientHeight : undefined

上面的写法虽然可以实现,但是的确有人会觉得麻烦,而使用「可选链操作符」 ,就可以将代码简化成如下形式:

const height = el?.clientHeight

需要获取某个对象中的属性,可以使用该语法:

a?.b
a?.[x]

上面的代码中,如果 a 为undefined或null,则表达式会立即返回undefined,否则返回所访问属性的值。也就是说,它们与下面这段代码是等价的:

a == null ? undefined : a.b
a == null ? undefined : a[x]

在尝试调用某个方法时,也可以使用该语法:

a?.()

同样是如果 a 为undefined或null,则返回undefined,否则将调用该方法。不过需要额外注意的是,该操作符并不会判断 a 是否是函数类型,因此如果 a 是一个其它类型的值,那么这段代码依然会在运行时抛出异常。

在访问某个对象较深层级的属性时,也可以串联使用该操作符:

a?.b?.[0]?.()?.d

可能有人会懒得先去判断是否真的有必要,就给访问链路中的每个属性都加上该操作符。但类似上面代码中所展示的那样,这种代码可读性比较差。而且若真的有一个应当存在的对象因为某些 bug 导致它没有存在,那么在访问它时就应当是抛出异常,这样可以及时发现问题,而不是使它被隐藏起来。 建议访问较深层级的时候,只在必要的时候才使用可选链操作符。

BigInt

在 ES 中,所有 Number 类型的值都使用 64 位浮点数格式存储,因此 Number 类型可以有效表示的最大整数为 2^53。而使用新的 BigInt 类型,可以操作任意精度的整数。
有两种使用方式:
1、在数字字面量的后面添加后缀n;
2、使用其构造函数BigInt

const bigInt = 9007199254740993n
const bigInt = BigInt(9007199254740992)

// 在超过 Number 最大整数限制时,我们也可以改为传入一个可能被正确解析的字符串
const bigInt = BigInt('9007199254740993')

和 Number 类似,BigInt 也支持+、-、*、**、%运算符:

3n + 2n    // => 5n
3n * 2n    // => 6n
3n ** 2n   // => 9n
3n % 2n    // => 1n

但因为 BigInt 是纯粹的整数类型,无法表示小数位,因此 BigInt 的除法运算(/)的结果值依然还是一个整数,即向下取整:

const bigInt = 3n;
bigInt / 2n;    // => 1n,而不是 1.5n

同样也位支持位运算符,除了无符号右移运算符:

1n & 3n    // => 1n
1n | 3n    // => 3n
1n ^ 3n    // => 2n
~1n        // => -2n
1n << 3n   // => 8n
1n >> 3n   // => 0n
1n >>> 3n  // Uncaught TypeError: BigInts have no unsigned right shift, use >> instead

BigInt 可以和字符串之间使用+运算符连接

1n + ' Number'   // => 1 Number
'Number ' + 2n   // => Number 2

下面这些场景不支持使用BigInt:

  1. BigInt 无法和 Number 一起运算,会抛出类型异常1n + 1
  2. 一些内置模块如 Math 也不支持 BigInt,同样会抛出异常Math.pow(2n, 64n)
  3. BigInt 和 Number 相等,但并不严格相等,但他们之间可以比较大小1n == 1 // => true , 1n === 1 // => false
  4. BigInt 和 Number 之间是可以比较大小的:
1n < 2     // => true
1n < 1     // => false

2n > 1     // => true
2n > 2     // => false
  1. 而且在转换为 Boolean 值时,也和 Number 一样,0n 转为 false,其它值转为 true:
!!0n       // => false
!!1n       // => true
  1. BigInt 和 Number 之间只能使用对方的构造函数进行转换:
Number(1n) // => 1
BigInt(1)  // => 1n
  1. 但两者之间的转换也都有一些边界问题:
// 当 BigInt 值的精度超出 Number 类型可表示的范围时,会出现精度丢失的问题
Number(9007199254740993n)
// => 9007199254740992

// 当 Number 值中有小数位时,BigInt 会抛出异常
BigInt(1.1)
// VM4854:1 Uncaught RangeError: The number 1.1 cannot be converted to a BigInt because it is not an integer
  1. 配套地,在类型化数组中也提供了与 BigInt 对应的两个数组类型:BigInt64Array和BigUint64Array
const array = new BigInt64Array(4);
array[0]   // => 0n
const array1 = new BigUint64Array(4);
array1[0]   // => 0n

globalThis

console.log(window); //Window
    console.log(self);//Window
    console.log(frames);//Window
    console.log(globalThis );//Window

全局属性globalThis包含全局的this值,类似于全局对象 globalThis提供了一个标准的方式来获取不同环境下的全局this 对象(也就是全局对象自身)。不像window或者self这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的this就是globalThis

// 浏览器环境
console.log(globalThis);    // => Window {...}
 
// node.js 环境
console.log(globalThis);    // => Object [global] {...}
 
// web worker 环境
console.log(globalThis);    // => DedicatedWorkerGlobalScope {...}

通过使用globalThis,你的代码将在 window 和非 window 上下文中工作,而无需编写额外的检查或测试。在大多数环境中,globalThis直接引用该环境的全局对象

Promise.allSettled

在Promise上有提供一组组合方法 目前为止这类方法一共有如下四个,这四个方法之间仅有判断逻辑上的区别,也都有各自所适用的场景:
Promise.all 返回一个组合后的 promise,当所有 promise 全部切换为 fulfilled 状态后,该 promise 切换为 fulfilled 状态;但若有任意一个 >promise 切换为 rejected 状态,该 promise 将立即切换为 rejected 状态;
Promise.race 返回一个组合后的 promise,当 promise 中有任意一个切换为 fulfilled 或 rejected 状态时,该 promise 将立即切换为相同状态;
Promise.allSettled 返回一个组合后的 promise,当所有 promise 全部切换为 fulfilled 或 rejected 状态时,该 promise 将切换为 fulfilled 状态;
Promise.any 返回一个组合后的 promise,当 promise 中有任意一个切换为 fulfilled 状态时,该 promise 将立即切换为 fulfilled 状态,但只有所有 promise 全部切换为 rejected 状态时,该 promise 才切换为 rejected 状态。(ECMAScript2021 )

Promise.allSettled用法:
相对于 Promise.all 需要所有 promise都成功时才 resolve或者有一个失败时即reject,Promise.allSettled 只关心所有 promise 是不是都被 settle 了,不管其是 rejected状态的 promise,还是非 rejected状态(即fulfilled)的 promise, 我都可以拿到它的最终状态并对其进行处理

async function a() {
    const promiseA = fetch('/api/a')    // => rejected,  <Error: a>
    const promiseB = fetch('/api/B')    // => fulfilled, "b"

    const results = await Promise.allSettled([ promiseA, promiseB])

    results.length   // => 3
    results[0]       // => { status: "rejected", reason: <Error: a> }
    results[1]       // => { status: "fulfilled", value: "b" }
}

因为结果值是一个数组,所以你可以很容易地过滤出任何你感兴趣的结果信息:

// 获取所有 fulfilled 状态的结果信息
results.filter( result => result.status === "fulfilled" )
// 获取所有 rejected 状态的结果信息
results.filter( result => result.status === "rejected" )
// 获取第一个 rejected 状态的结果信息
results.find( result => result.status === "rejected" )

有时候在进行一个页面的初始化流程时,需要加载多份初始化数据,或执行一些其它初始化操作,而且通常会希望等待这些初始化操作全部完成之后再执行后续流程:

async function init() {
    setInited(false)
    setInitError(undefined)

    const results = await Promise.allSettled([
        loadDetail(),
        loadRecommentListFirstPage(),
        initSDK(),
    ])

    const errors = results
        .filter( result => result.status === "rejected" )
        .map( rejectedResult => rejectedResult.reason )

    if (errors.length) {
        setInitError(errors[0])
        $logs.error(errors)
    }

    setInited(true)
}

for-in 结构

不同的引擎已就如何迭代属性达成一致,从而使行为标准化 思考:for …in\of 区别

ES12

replaceAll

模式的所有匹配都会被替代项替换。模式可以是字符串或正则表达式,而替换项可以是字符串或针对每次匹配执行的函数。并返回一个全新的字符串

在没有这个特性之前,我们会这样写

const str = "student is a real student";
const newStr = str.replace(/student/g, "hahaha");
console.log(newStr); //hahaha is a real hahaha

有了replaceAll之后我们可以这么写了

const str = "student is a real student";
const newStr = str.replaceAll('student', "hahaha");
console.log(newStr);  //hahaha is a real hahaha

Promise.any

Promise.any() 是 ES2021 新增的特性,它接收一个 Promise 可迭代对象(例如数组),只要其中的一个 promise 成功,就返回那个已经成功的 promise如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和 AggregateError 类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起

const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.resolve('result'),
]

Promise.any(promises).then((value) => {
  console.log('value: ', value)
}).catch((err) => {
  console.log('err: ', err)
})

// value:  result

//如果所有传入的 promises 都失败:
const promises = [
  Promise.reject('ERROR A'),
  Promise.reject('ERROR B'),
  Promise.reject('ERROR C'),
]

Promise.any(promises).then((value) => {
  console.log('value:', value)
}).catch((err) => {
  console.log('err:', err)
  console.log(err.message)
  console.log(err.name)
  console.log(err.errors)
})

// err:AggregateError: All promises were rejected
// All promises were rejected
// AggregateError
// ["ERROR A", "ERROR B", "ERROR C"]

Promise.any 应用场景: 来自世界各地的用户访问网站,如果你有多台服务器,则尽量使用响应速度最快的服务器,在这种情况下,可以使用 Promise.any() 方法从最快的服务器接收响应

function getUser(endpoint) {
    return fetch(`https://superfire.${endpoint}.com/users`)
      .then(response => response.json());
  }
  
  const promises = [getUser("jp"), getUser("uk"), getUser("us"), getUser("au"), getUser("in")]
  
  Promise.any(promises).then(value => {
    console.log(value)
  }).catch(err => {
    console.log(err);
  })

逻辑赋值操作符 ??=、&&=、 ||=

ES12之前我们是这样写的

let a=10
a += 2;
let num = a || 222
let num = a ?? 222

ES12有了这个新的标准中,逻辑表达式的操作符(&&、||、??) 接下来,再来看下新标准中的逻辑运算符怎么用:

// 等同于 a = a || b
a ||= b;
// 等同于 c = c && d
c &&= d;
// 等同于 e = e ?? f
e ??= f;

WeakRef

WeakRef是一个 Class,一个WeakRef对象可以让你拿到一个对象的弱引用。这样,就可以不用阻止垃圾回收这个对象了。 可以使用其构造函数来创建一个WeakRef对象。

// anObject 不会因为 ref 引用了这个对象,而不会被垃圾回收
let ref = new WeakRef(anObject);

我们可以用WeakRef.prototype.deref()来取到anObject的值。但是,在被引用对象被垃圾回收之后,这个函数就会返回undefined。

// 如果 someObj 被垃圾回收了,则 obj 就会是 undefined
let obj = ref.deref();

下划线 (_) 分隔符

当你要写一个很长的数字的时候:

let x = 233333333

数字太长会导致可读性很差。使用了数字分隔符 _ (下划线),就可以让数字读的更清晰:

let x = 2_3333_3333
// x 的值等同于 233333333,只是这样可读性更强,不用一位一位数了

Intl.ListFormat

Intl.ListFormat 是一个构造函数,用来处理和多语言相关的对象格式化操作

const list = ['Apple', 'Orange', 'Banana']
new Intl.ListFormat('en-GB', { style: 'long', type: 'conjunction' }).format(list);
// "Apple, Orange and Banana"
new Intl.ListFormat('zh-cn', { style: 'short', type: 'conjunction' }).format(list);
// 会根据语言来返回相应的格式化操作
// "Apple、Orange和Banana"

自我感觉没啥用

Intl.DateTimeFormat

Intl.ListFormat 是一个用来处理多语言下的时间日期格式化的函数

let a = new Intl.DateTimeFormat("en" , {
  timeStyle: "short"
});
console.log('a = ', a.format(Date.now())); // "13:31"
let b = new Intl.DateTimeFormat("en" , {
  dateStyle: "short"
});
console.log('b = ', b.format(Date.now())); // "21.03.2012"
// 可以通过同时传入 timeStyle 和 dateStyle 这两个参数来获取更完整的格式化时间的字符串
let c = new Intl.DateTimeFormat("en" , {
  timeStyle: "medium",
  dateStyle: "short"
});
console.log('c = ', c.format(Date.now())); // "21.03.2012, 13:31"

timeStyle 和 dateStyle 配置项有三个(下面以timeStyle为例):

  • short:11:27 PM
  • medium:11:27:57 PM
  • long:11:27:57 PM GMT+11
    自我感觉没啥用

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复感谢,配合,希望我的努力对你有帮助^_^免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我。

相关文章