前言
ES2020 (ES11)是 ECMAScript 对应 2020 年的版本。这个版本不像 ES6 (ES2015)那样包含大量新特性。但也添加了许多有趣且有用的特性。本文以简单的代码示例来介绍 ES2020新特性。这样,你可以很快理解这些新功能,而不需要多么复杂的解释,好了,废话不多说我们进入正文🔛
私有变量
类的主要作用之一是将我们的代码包含在可重用的模块中。所以会在许多不同地方使用到同一个类,但是又的类里面的一些属性,为了安全或者是其他的目的,不想让其他的类能够去访问他,修改他,只能他自己用,对于一些学过c#,java等语言的同学来说是比较好理解的,就是 Private
一个访问修饰符,那么在我们的js当中该怎么办了,
别担心,他们有的咱js也有,在ES11中也为我们提供了私有变量,通过在变量或函数前面添加一个哈希符号#
,可以将它们设为私有属性,只在类内部可用。
这是我们普通的一个类
class Money{
constructor(money){
this.money=money;
}
}
let RMB=new Money(999);
RMB.money=999999;//这还了得,怎么可以随便改钱的金额呢
console.log(RMB.money);//可以通过对象的形式去访问
由于安全,我们不能让其他的类来随便更改我们类里面的属性的值,我们可以使用 私有属性,是只能在类里面进行访问
class Money{
#money=0;//声明这个属性是私有属性
constructor(money){
this.#money=money;
}
}
let RMB=new Money(999);
//RMB.#money=999999;//Uncaught SyntaxError: Private field '#money' must be declared in an enclosing class,
console.log(RMB.#money);//Uncaught SyntaxError: Private field '#money' must be declared in an enclosing class
上面的代码报错了,哭唧唧,拿不到私有属性的值了,怎么办??
我需要使用我类里面的钱,怎么获取
我们可以在类里面写两个方法,取数据,写入数据,可以进行修改,但是你不能任意改,必须通过我的方法去改,我们可以看看下面的代码
class Money{
#money=0;//声明这个属性是私有属性
constructor(money){
this.#money=money;
}
getMoney(){//获取值
return this.#money;
}
setMoney(money){//写入值
this.#money+=money;
}
}
let RMB=new Money(999);
RMB.setMoney(8848)
console.log(RMB.getMoney());
好了,私有变量(属性),我们就说到这里了,下面我们来看看空值合并运算符吧!
空值合并运算符
在我们平常的工作中,在取一个对象里面的属性,如果这个属性没有值,我们会这样给他一个默认值
let user={
name:'Symbol卢',
age:18
}
console.log(user.name,"name")//Symbol卢 name
console.log(user.like,"like")//undefined "like"
//对象里面没有这个属性的时候,他一个默认值
console.log(user.like || '写代码',"like2")//写代码 like2
//ES2020 的?? 空值合并操作符
console.log(user.sex??'男',"sex"); //男 sex
//不存在这个sex属性,就会走后面的默认值,如果存在这个sex属性,就不会走后面的默认值
**空值合并运算符(_??_)**是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
??和 || 的区别是什么呢??
他们两个最大的区别就是 ' '和 0,??的左侧 为 ' '或者为 0 的时候,依然会返回左侧的值;
|| 会对左侧的数据进行boolean类型转换,所以' '和 0 会被转换成false,返回右侧的值
可选链操作符
减少访问深层对象时判断属性存不存在的问题。
const a = {};
console.log(a.b.c); // 这里会报Err, 无法从undefined中得到c
console.log(person?.profile?.name); // ""
console.log(person?.hobby?.work); // undefined
console.log(person?.hobby?.work ?? "no Work"); // no Work
可选链中的 ?
表示如果问号左边表达式有值, 就会继续查询问号后面的字段。根据上面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全。
注意:?. 不能用来赋值。
赋值,我们通常采用 可选链操作符
和 空值合并运算符
进行搭配使用
let user={
name:'Symbol卢',
age:18,
skill:{
Web:{
html:'50%',
css:'60%',
js:'80%'
},
Server:{
node:'80%',
php:'50%',
net:'60%'
}
}
}
console.log(user.name,"name")//Symbol卢 name
console.log(user.like,"like")//undefined "like"
//我们平常的工作中,会这样使用,当我们的对象里面没有这个属性的时候,这样给他一个默认值
console.log(user.like || '写代码',"like2")//写代码 like2
//ES2020 的??
console.log(user.sex??'男',"sex"); //不存在这个sex属性,就会走后面的默认值,如果存在这个sex属性,就不会走后面的默认值
console.log(user.skill.Web.js,"js")//80% js
//console.log(user.skill.Database.MySql,"MySql")//Uncaught TypeError: Cannot read property 'MySql' of undefined
//空值合并运算符 与 可选链 相结合,可以很轻松处理多级查询并赋予默认值问题
console.log(user?.skill?.Database?.MySql ?? '88%',"MySql")
BigInt
关于js的浮点数的一个精度问题,最典型的就是 0.1+0.2!= 0.3
console.log(0.1+0.2,'0.1+0.2')//0.30000000000000004 "0.1+0.2"
console.log(0.1+0.2==0.3,"0.1+0.2==0.3")//false "0.1+0.2==0.3"
JavaScript可以处理的最大数字是2 ^ 53,通过MAX_SAFE_INTEGER可以查出这个值:
JavaScript 中 Number
类型只能安全的表示-(2^53 -1)
至 2^53 -1
范的值,即 Number.MIN_SAFE_INTEGER
至 Number.MAX_SAFE_INTEGER
,超出这个范围的整数计算或者表示会丢失精度。
let maxNum=Number.MAX_SAFE_INTEGER;
console.log(maxNum)//9007199254740991
let num1=Number.MAX_SAFE_INTEGER+1;
let num2=Number.MAX_SAFE_INTEGER+2;
console.log(num1==num2,"num1==num2")//true 超过这个范围,就精度丢失了
为解决此问题,ES2020 提供一种新的数据类型:BigInt
。使用 BigInt
有两种方式:
- 在整数字面量后面加
n
。
let bigIntNum1=9007199254740999n;
console.log(typeof bigIntNum1)//bigint
- 使用
BigInt
函数。
let bigIntNum2=BigInt(90071992547409999);
console.log(typeof bigIntNum2)//bigint
接下来我们再对两位数进行判断:
console.log(bigIntNum1==bigIntNum2)//false
//已经解决了之前超过这个精度不能进行计算的问题
我们再通过 BigInt
, 我们可以安全的进行大数整型计算。
let BigNum=bigIntNum1+bigIntNum2;
console.log(BigNum.toString())//99079191802150999
注意:
BigInt
是一种新的数据原始(primitive)类型。注意标准数字与BigInt
数字不能混合使用。
`typeof 9007199254740993n; // -> 'bigint'
`
- 尽可能避免通过调用函数
BigInt
方式来实例化超大整型。因为参数的字面量实际也是Number
类型的一次实例化,超出安全范围的数字,可能会引起精度丢失。
动态导入
在项目中,某些功能可能很少使用,而导入所有依赖项可能只是浪费资源。现在可以使用async / await
在需要时动态导入依赖项,可以在初始化的时候不全部加载逻辑资源,只进行按需加载,这样可以让首屏的渲染速度更快,虽然我们的前端工程化项目使用的webpack已经很好支持了按需导入,但是现在能够在2020正式的进入ES的规范,我们的js也是越来越强大了。
demo.js 导出模块:
export const sum=(num1,num2)=>num1+num2;
动态导入:
let fun=async(num1,num2)=>{
let model=await import('./demo.js');
console.log(model.sum(num1,num2),"两个数的和")
}
fun(8,9)//17 "两个数的和"
//报错
//Access to script at 'file:///C:/Users/Administrator/Desktop/es11Demo/demo.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
//demo.js:1 Failed to load resource: net::ERR_FAILED
//demo.html:13 Uncaught (in promise) TypeError: Failed to fetch dynamically imported module: file:///C:/Users/Administrator/Desktop/es11Demo/demo.js
//报错需要在http服务器环境下,才可以
在当前项目的 根目录上创建http服务器 (电脑里面已经有node环境)
npm i http-server -g
安装依赖
然后在目录 里面 执行 http-server
globalThis
JavaScript 在不同的环境获取全局对象有不同的方式,NodeJS 中通过 global
, Web 中通过 window
, self
等,有些甚至通过 this
获取,但通过 this
是及其危险的,this
在 JavaScript 中异常复杂,它严重依赖当前的执行上下文,这些无疑增加了获取全局对象的复杂性。
全局变量 window:是一个经典的获取全局对象的方法。但是它在 Node.js 和 Web Workers 中并不能使用
全局变量 self:通常只在 Web Workers 和浏览器中生效。但是它不支持 Node.js。
全局变量 global:只在 Node.js 中生效
我们写的一个js文件,可能在浏览器上运行,也可能在node环境下运行,也可能在 Web Workers 环境中运行,
这时候的这个全局变量就不统一我们可以使用以下的方法去做一个判断,过去获取全局对象,可通过一个全局函数:
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeofwindow !== 'undefined') { returnwindow; }
if (typeof global !== 'undefined') { return global; }
thrownewError('unable to locate global object');
};
var globals = getGlobal();
而到我们的ES11中为我们提供了 globalThis
,
而 globalThis
目的就是提供一种标准化方式访问全局对象,有了 globalThis
后,你可以在任意上下文,任意时刻都能获取到全局对象。
globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身),所以不用担心运行环境。
// worker.js
console.log(globalThis === self) //true
// node.js
console.log(globalThis === global) //true
// 浏览器
console.log(globalThis === window) //true
Promise.all 缺陷
都知道 Promise.all
具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject
),所有任务都会挂掉,Promise
直接进入 reject
状态。
想象这个场景:你的页面有三个区域,分别对应三个独立的接口数据,使用 Promise.all
来并发三个接口,如果其中任意一个接口服务异常,状态是 reject,这会导致页面中该三个区域数据全都无法渲染出来,因为任何 reject
都会进入 catch 回调, 很明显,这是无法接受的,如下:
let a= new Promise((resolve,reject)=>{
//异步操作...
resolve({ code: 200,msg:"请求成功"})
})
let b= new Promise((resolve,reject)=>{
//异步操作...
resolve({ code: 200,msg:"请求成功"})
})
let c= new Promise((resolve,reject)=>{
//异步操作...
reject({ code: 500,msg:"服务器出现异常"})
})
//使用Promise.all 进行并发执行异步任务
Promise.all([a,b,c])
.then((res) => {
// 只有 上面所有的请求都是 resolve (成功) 的时候才会进入此回调中
console.log(res,"res")
})
.catch((error) => {
// 上面的请求中,只要有一个是reject (失败) 就会进入此回调
console.log(error,"error")
// error: {code: 500, msg: "服务异常"}
})
Promise.allSettled
当我们处理多个promise
时,尤其是当它们相互依赖时,记录每个事件在调试中发生的错误可能很有用。使用Promise.allSettled
,它会创建一个新的promise
,在所有promise
完成后返回一个包含每个promise
结果的数组。
let a= new Promise((resolve,reject)=>{
//异步操作...
resolve({ code: 200,msg:"请求成功"})
})
let b= new Promise((resolve,reject)=>{
//异步操作...
resolve({ code: 200,msg:"请求成功"})
})
let c= new Promise((resolve,reject)=>{
//异步操作...
reject({ code: 500,msg:"服务器出现异常"})
})
//使用进行并发请求
Promise.allSettled([a,b,c]).then((data=>{
console.log(data,"data")
}))
// 返回的数据中 ,status: "fulfilled" 表示请求成功,status: "rejected" 表示请求失败
Promise.allSettled 的优势
Promise.allSettled
跟 Promise.all
类似 都是进行并发请求,但是,我们在上面的两个例子中,显然是已经看到了他们的一些区别,在使用 Promise.all
进行并发请求的时候,只要有一个请求出现问题(异常),所有的请求正常的也都不能拿到数据,但是在我们的业务的开发中,我们需要保障我们业务的最大的可访问性,就是在我们执行并发任务中,不管我这个并发任务中的一任何个任务是正常还是异常,我们都需要拿到返回的对应的状态,在ES11中 Promise.allSettled
就为我们解决了这个问题,它和 Promise.all
类似,参数接受一个Promise的数组,返回一个新的Promise,也就是说当Promise全部处理完成后,我们可以拿到每个Promise的状态, 而不管是否处理成功。我们可以在 then
里面通过 filter
来过滤出想要的业务逻辑结果,这样就解决了Promise.all
的缺陷。