17.(ECMAScript)es11完全解读
作者:互联网
文章目录
- 1. 重点提炼
- 2. 字符串扩展
- 3. 动态导入:Dynamic import()
- 4. 新的原始数据类型:BigInt
- 5. Promise扩展:Promise.allSettled()
- 6. 全局对象:globalThis
- 7. 可选链:Optional chaining
- 8. 空值合并运算符:Nullish coalescing Operator
1. 重点提炼
- String扩展
- 动态导入
- BigInt
- Promise.allSettled()
- 全局对象——globalThis
- 可选链
- 空值合并运算符
2. 字符串扩展
- 全局模式捕获:
String.prototype.matchAll()
在正则表达式中经常使用match
,匹配正则。
2.1 定义一个html模版的字符串。 => 需求:获取div标签中的内容。
定义一个html
模版的字符串。 => 需求:获取div
标签中的内容。
2.1.1 exec g实现
如何用一个正则表达式来得到所有匹配项,可以使用exec
与g
修饰符,如果正则表达式有/g
标志,那么多次调用.exec()
就会得到所有匹配的结果。 如果没有匹配的结果,.exec()
就会返回null
。在这之前会返回每个匹配的匹配对象。 这个对象包含捕获的子字符串和更多信息。
即在正则中首先会想到exec
方法,即执行,可以对某个字符串进行正则匹配,并且可以结合g
修饰符,进行全局匹配。
封装一个方法,其第一个参数是正则表达式,第二个参数是对应目标字符串。
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是p</p>
<div>第二个div</div>
<span>这是span</span>
<div>第三个div</div>
</body>
</html>
`
// exec g
function selectDiv(regExp, str){
let matches = [] // 输出匹配结果数组
while(true){
const match = regExp.exec(str)
// 未匹配上
if(match == null){
break
}
matches.push(match)
}
return matches
}
const regExp = /<div>(.*)<\/div>/g
const res = selectDiv(regExp, str)
console.log(res)
其返回的数组第0
个,是整个文本匹配的结果,即未分组前的整体。从第1
个开始就是子表达式匹配的结果了,即分组。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.12
Branch: branch02commit description:a4.12(字符串扩展——exec使用1)
tag:a4.12
因此如果需要获取div
标签里的内容,即该分组,应该获取下标为1
的数组。
function selectDiv(regExp, str){
let matches = [] // 输出匹配结果数组
while(true){
// console.log(regExp.lastIndex)
const match = regExp.exec(str)
// console.log(match)
// 未匹配上
if(match == null){
break
}
matches.push(match[1])
}
return matches
}
const regExp = /<div>(.*)<\/div>/g
const res = selectDiv(regExp, str)
console.log(res)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.13
Branch: branch02commit description:a4.13(字符串扩展——exec使用2)
tag:a4.13
2.1.2 /g作用
注意:如果不全局匹配 =>
const regExp = /<div>(.*)<\/div>/
如上代码会陷入死循环,因为不是全局匹配,则每一次都是从字符串的开头进行匹配,则match
永远不可能是null
,则陷入死循环。
实际正则表达式中存在一个lastIndex
属性(初始值为0
),每次执行exec
的时候,实际是根据lastIndex
属性值去执行(从该属性的位置开始执行,即决定开始匹配的位置)。如果正则表达式没有/g
标志,那么运行一次.exec()
时,不会改变lastIndex
的值,导致下一次运行exec()
时,匹配仍旧是从字符串0
的位置开始。当正则表达式加了/g
标志后,运行一次exec()
,正则表达式的lastIndex
就会改变,下次运行exec()
就会从前一次的结果之后开始匹配。
不过如果没有使用/g
的正则模式,.match
的效果和RegExp.prototype.exec()
是一致的。
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是p</p>
<div>第二个div</div>
<span>这是span</span>
<div>第三个div</div>
</body>
</html>
`
// exec g
function selectDiv(regExp, str){
let matches = [] // 输出匹配结果数组
while(true){
console.log(regExp.lastIndex)
const match = regExp.exec(str)
console.log(match)
// 未匹配上
if(match == null){
break
}
matches.push(match[1])
}
return matches
}
const regExp = /<div>(.*)<\/div>/g
const res = selectDiv(regExp, str)
console.log(res)
第一次从0
的位置开始找,然后匹配上了第一个div
;这个时候字符是第39
个,然后隔了17
个字符(找到的部分),下一次则从39+17=56
的位置开始找;然后依次往下开始找,一直找到头。
故如果没有全局匹配,每一次的lastIndex
都是0
。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.14
Branch: branch02commit description:a4.14(字符串扩展——lastIndex属性)
tag:a4.14
2.1.3 match实现
如果用 .match
方法结合/g
的正则模式,将会把所有的匹配打包成一个数组返回,换句话说所有的捕获被忽略。
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是p</p>
<div>第二个div</div>
<span>这是span</span>
<div>第三个div</div>
</body>
</html>
`
const regExp = /<div>(.*)<\/div>/g
// match
console.log(str.match(regExp))
用match
方法也可进行匹配,但是和需求则有一些的出入,需求不用div
标签,仅仅是div
标签中的内容。
match
的作用就是找到包含的结果,而exec
可以设置子表达式,即捕获组,打算match
方法不行。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.15
Branch: branch02commit description:a4.15(字符串扩展——match)
tag:a4.15
2.1.4 replace实现
可以使用一个技巧通过.replace()
收集捕获,我们使用一个函数来计算替换值。
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是p</p>
<div>第二个div</div>
<span>这是span</span>
<div>第三个div</div>
</body>
</html>
`
const regExp = /<div>(.*)<\/div>/g
// replace
function selectDiv(regExp, str){
let matches = []
str.replace(regExp, (all, first) => {
matches.push(first)
})
return matches
}
const res = selectDiv(regExp, str)
console.log(res)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.16
Branch: branch02commit description:a4.16(字符串扩展——replace)
tag:a4.16
replace
方法的第一个参数可以是正则的规则,第二个参数是一个回调函数,或者一般都是用一个固定的字符串。
回调函数
的第一个参数,是完整的匹配结果,第二个参数是第一个子表达式的结果(如果有多个则依次类推),和exec
有些类似。
第三个参数 是offset
, 匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 'abcd'
,匹配到的子字符串是 'bc'
,那么这个参数将会是 1
)
第四个参数string
,被匹配的原字符串。
官网例子 =>
[^\d]*
=> 非数字(匹配多次)
\d*
=> 数字(匹配多次)
[^\w]*
=> 非数字、字母、下划线(匹配多次)
function replacer(match, p1, p2, p3, offset, string) {
// p1 is nondigits, p2 digits, and p3 non-alphanumerics
return [p1, p2, p3].join(' - ');
}
var newString = 'abc12345#$*%'.replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
console.log(newString); // abc - 12345 - #$*%
2.1.5 matchAll
最为简洁的方式 =>es11
=> matchAll
str.matchAll(regexp)
matchAll()
方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代
参数 | 含义 | 必选 |
---|---|---|
regexp | 正则表达式对象 | Y |
根据正则匹配字符串中所有的匹配项得出结果并返回一个迭代器,利用for…of
迭代。
注意
- 如果所传参数不是一个正则表达式对象,则会隐式地使用
new RegExp(obj)
将其转换为一个RegExp
- 返回值一个迭代器,但是不可重用,结果耗尽需要再次调用方法,获取一个新的迭代器
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是p</p>
<div>第二个div</div>
<span>这是span</span>
<div>第三个div</div>
</body>
</html>
`
const regExp = /<div>(.*)<\/div>/g
// matchAll
function selectDiv(regExp, str){
let matches = []
for(let match of str.matchAll(regExp)){
matches.push(match[1])
}
return matches
}
const res = selectDiv(regExp, str)
console.log(res)
实际和exec g
方法类似,但是更为简洁。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.17
Branch: branch02commit description:a4.17(字符串扩展——matchAll)
tag:a4.17
报错,matchAll
要求必须有全局的匹配参数,即要求必须写g
,因此该方法更为安全,不容易死循环。
因此在项目中,如果有需求需要用正则匹配字符串中所有的匹配项,强烈推荐使用matchAll
。
const regExp = /<div>(.*)<\/div>/
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.18
Branch: branch02commit description:a4.18(字符串扩展——matchAll安全性)
tag:a4.18
3. 动态导入:Dynamic import()
按需 import
提案几年前就已提出,如今终于能进入ES
正式规范。这里个人理解成“按需”更为贴切。现代前端打包资源越来越大,打包成几M
的JS
资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。
=> 按需导入,实际就是懒加载。目的就是加快首屏渲染速度,不需要一进页面,加载大量的资源,部分页面可以用户进入的时候再加载资源。
实现 => 点击按钮,再进行导入之前我封装的ajax
请求的文件。
ajax
是通过export default
导出的,因此这里import
返回的Promise
参数就是该对象,利用default
直接调用即可。
const oBtn = document.querySelector('#btn')
oBtn.addEventListener('click', () => {
import('./ajax').then(mod => {
console.log(mod)
mod.default('static/a.json', res => {
console.log(res)
})
})
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.19
Branch: branch02commit description:a4.19(动态导入:Dynamic import())
tag:a4.19
在Vue
生态圈中经常用到vue-router
=> 路由懒加载
就应用该原理。并且webpack
目前已很好的支持了该特性。
4. 新的原始数据类型:BigInt
es11
之前,支持的最大整数,是2
的53
次方。哪怕对其加1
,最后的结果还是2
的53
次方。
Number.MAX_SAFE_INTEGER == 2的53次方 - 1
const max = 2 ** 53
console.log(max)
console.log(Number.MAX_SAFE_INTEGER)
console.log(max === max + 1) // true
es11
之前,所谓的整型是存在最大数限制的。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.20
Branch: branch02commit description:a4.20(新的原始数据类型:BigInt——es11之前,所谓的整型是存在最大数限制的)
tag:a4.20
4.1 数字后面增加n
但如果现在需求,需要更大数字。 => 利用es11
引进的新的原始数据类型:BigInt
=> 数字末尾加n
BigInt
,表示一个任意精度的整数,可以表示超长数据,可以超出2
的53
次方。
const bigInt = 9007199254740993n
console.log(bigInt)
console.log(typeof bigInt) // bigint
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.21
Branch: branch02commit description:a4.21(新的原始数据类型:BigInt——基本使用)
tag:a4.21
BigInt
数据类型对应小一些数,与正常数组类型对比,两等成立,三等不成立。
即与普通数相比,同样的数值相等但类型不等。
console.log(1n == 1) // true
console.log(1n === 1) // false
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.22
Branch: branch02commit description:a4.22(新的原始数据类型:BigInt——与普通数相比,同样的数值相等但类型不等)
tag:a4.22
4.2 使用 BigInt 函数
除了直接在数字末尾加n
,也可通过BigInt
函数传递参数创建BigInt
数据。
很大的数也可进行运算,但是出来的结果末尾带n
,如果不想要n
,可以调用toString
方法。
注意:超过范围的数字,只能用字符串存储,用number类型是存不下的。
const bigInt = 9007199254740993n
const bigInt2 = BigInt(9007199254740993n)
console.log(bigInt2)
const num = bigInt + bigInt2
console.log(num.toString())
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.23
Branch: branch02commit description:a4.23(新的原始数据类型:BigInt——特性)
tag:a4.23
5. Promise扩展:Promise.allSettled()
Promise.allSettled()
allSettled() vs all()
Promise
是es6
提出,作用是对于异步状态进行管理的,其内有一个静态方法是all
,它的作用是,其参数是一个数组,数组里的每一个元素都是一个promise
对象,需要等待数组中的所有promise
对象都执行完成以后,如果想执行某些逻辑操作,就可将其放在all
里。
实际Promise.all
存在一些缺陷,满足不了有些应用场景。
它的最大问题就是如果其中某个任务出现异常(reject
),所有任务都会挂掉,Promise
直接进入 reject
状态。
Promise.all([
Promise.resolve({
code: 200,
data: [1, 2, 3]
}),
Promise.resolve({
code: 200,
data: [4, 5, 6]
}),
Promise.resolve({
code: 200,
data: [7, 8, 9]
}),
]).then(res=>{
console.log(res)
console.log('成功')
}).catch(err=>{
console.log(err)
console.log('失败')
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.24
Branch: branch02commit description:a4.24(Promise扩展——Promise.all都成功)
tag:a4.24
假设以上三次请求,中间的请求是失败的,Promise.all
会认为都是失败的。这样就存在一个问题,请求成功的结果就获取不到的。这种一个请求失败,就导致所有请求失败的需求显然是不太好的。
Promise.all([
Promise.resolve({
code: 200,
data: [1, 2, 3]
}),
Promise.reject({
code: 500,
data: []
}),
Promise.resolve({
code: 200,
data: [7, 8, 9]
}),
]).then(res=>{
console.log(res)
console.log('成功')
}).catch(err=>{
console.log(err)
console.log('失败')
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.25
Branch: branch02commit description:a4.25(Promise扩展——Promise.all对应一个请求失败)
tag:a4.25
实际最好失败的请求则返回失败,成功的请求也可以返回对应的结果。 => 这种场景就无法使用Promise.all
了。
这就需要一种机制,对于并发任务的处理,不管哪个是成功的?还是失败的?我们都可以得到对应的状态。 => Promise.allSettled()
Promise.allSettled([
Promise.resolve({
code: 200,
data: [1, 2, 3]
}),
Promise.reject({
code: 500,
data: []
}),
Promise.resolve({
code: 200,
data: [7, 8, 9]
}),
]).then(res=>{
console.log(res)
console.log('成功')
}).catch(err=>{
console.log(err)
console.log('失败')
})
虽然并发请求返回三个对象,但是每一个都对应一个promise
的状态。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.26
Branch: branch02commit description:a4.26(Promise扩展——Promise.allSettled()基本使用)
tag:a4.26
根据不同的状态,可以进行对应逻辑处理。如:只过滤出成功的状态。
Promise.allSettled([
Promise.resolve({
code: 200,
data: [1, 2, 3]
}),
Promise.reject({
code: 500,
data: []
}),
Promise.resolve({
code: 200,
data: [7, 8, 9]
}),
]).then(res=>{
const data = res.filter(item => item.status === 'fulfilled')
console.log(data)
}).catch(err=>{
console.log(err)
console.log('失败')
})
也可以再对失败的请求,给用户一个提示等等。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.27
Branch: branch02commit description:a4.27(Promise扩展——Promise.allSettled()只过滤出成功的状态)
tag:a4.27
6. 全局对象:globalThis
Javascript
在不同的环境获取全局对象有不通的方式:
- 提供了一个标准的方式去获取不同环境下的全局对象
node
端全局变量 =>global
web
端全局变量 =>window
和self
(打开浏览器创建的窗口其实就是window
和self
,这两者是一个东西)
note:
self
=> 打开任何一个网页,浏览器会首先创建一个窗口,这个窗口就是一个window
对象,也是js
运行所依附的全局环境对象和全局作用域对象。self
指窗口本身,它返回的对象跟window
对象是一模一样的。也正因为如此,window
对象的常用方法和函数都可以用self
代替window
。
self.setTimeout(()=>{
console.log('es2020')
}, 1000)
es2020
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.28
Branch: branch02commit description:a4.28(全局对象:globalThis——self)
tag:a4.28
在之前环境中,想要获取任何环境下的全局变量则需要判断。
const getGlobal = () => {
if (typeof self !== 'undefined') {
return self
}
if (typeof window !== 'undefined') {
return window
}
if (typeof global !== 'undefined') {
return global
}
throw new Error('无法找到全局对象')
}
const global = getGlobal()
console.log(global)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.29
Branch: branch02commit description:a4.29(全局对象:globalThis——在之前环境中,想要获取任何环境下的全局变量则需要判断)
tag:a4.29
es11
中使用globalThis
,在任何环境中都可以获取到全局对象。
globalThis
提供了一个标准的方式来获取不同环境下的全局 this
对象(也就是全局对象自身)。不像 window
或者 self
这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis
,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this
就是 globalThis
。
console.log(globalThis)
web端
node端
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.30
Branch: branch02commit description:a4.30(全局对象:globalThis——基本使用)
tag:a4.30
7. 可选链:Optional chaining
在开发当中会有很多对象,对象下面属性可能有很多层,经常需要判断对象下面有没有该属性,之前判断都非常麻烦,有了可选链会更为方便。
即可让我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。
在之前的语法中,想获取到深层属性或方法,不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property...
这种错误,这极有可能让你整个应用挂掉。
调用未知或者后端传来的对象,必须使用如下方式,否则万一属性没有就会报错,这样会非常不安全。
const user = {
address: {
street: 'xx街道',
getNum() {
return '80号'
}
}
}
const street = user && user.address && user.address.street
console.log(street)
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(num)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.31
Branch: branch02commit description:a4.31(可选链:Optional chaining——之前判断方式)
tag:a4.31
可选链 => ?.
可选链中的 ?
表示如果问号左边表达式
有值, 就会继续查询问号后面的字段。根据下面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全。
// 可选链
const user = {
address: {
street: 'xx街道',
getNum() {
return '80号'
}
}
}
const street = user?.address?.street
console.log(street)
const num = user?.address?.getNum?.()
console.log(num)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.32
Branch: branch02commit description:a4.32(可选链:Optional chaining——基本使用)
tag:a4.32
8. 空值合并运算符:Nullish coalescing Operator
空值合并运算符
(??
)是一个 逻辑运算符 。当左侧操作数为 null
或 undefined
时,其返回右侧的操作数。否则返回左侧的操作数。
很多时候需要给某些值设置默认值,经常使用或运算符设置默认值,但是或运算符会有一些缺陷。
因此es11
提出空值合并运算符
。
const b = 2
const a = b || 5
console.log(a)
2
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.33
Branch: branch02commit description:a4.33(空值合并运算符——或运算符-默认值有值的情况)
tag:a4.33
const b = 0
const a = b || 5
console.log(a)
弊端:0
代表false
,因此如果值是0
,会认为是没值。
注意:一般设置默认值都是为了防止null
和undefined
。但是如果是false
和0
,也被赋值默认值,这是很大的弊端。
5
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.34
Branch: branch02commit description:a4.34(空值合并运算符——或运算符-弊端)
tag:a4.34
空值合并运算符??
我们仅在第一项为 null
或 undefined
时设置默认值
const b = null
const a = b ?? 6
console.log(a)
6
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.35
Branch: branch02commit description:a4.35(空值合并运算符——null)
tag:a4.35
const b = false
const a = b ?? 6
console.log(a)
false
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a4.36
Branch: branch02commit description:a4.36(空值合并运算符——false)
tag:a4.36
(后续待补充)
标签:const,log,17,Promise,ECMAScript,regExp,console,es11,match 来源: https://blog.csdn.net/u013946061/article/details/113849445