# ES7~11 新增内容
通过 nrm 来管理 npm 源;有下面一些命令:
# 查看可选源
nrm ls
# 测试源响应时间
nrm test taobao
# 切换源
nrm use taobao
# 增加定制源
nrm add smart http://192.168.12.122:4546
# 删除源
nrm del smart
# ES7 相关
# Array.prototype.includes()
在 ES7 之前想判断数组中是否包含一个元素,基本可以这样写:
console.log(array1.find((item) => {
return item === 2
}))
// 或者
console.log(array1.filter((item) => {
return item === 2
}).length > 0)
ES7 引入的 Array.prototype.includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
- 基本用法
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es6')) // true
console.log(arr.includes('es9')) // false
- 接收俩个参数:要搜索的值和搜索的开始索引。第二个参数可选。从该索引处开始查找 searchElement。如果为负值,
const arr = ['es6', 'es7', 'es8']
console.log(arr.includes('es7', 1)) // true
console.log(arr.includes('es7', 2)) // false
console.log(arr.includes('es7', -1)) // false
console.log(arr.includes('es7', -2)) // true
- 与 indexOf() 比较
['a', 'b', 'c'].includes('a') // true
['a', 'b', 'c'].indexOf('a') > -1 // true
console.log(arr.indexOf('es7')) // 1
console.log(arr.indexOf('es7') > -1) // true
注意:只能判断简单类型的数据,对于复杂类型的数据,比如对象类型的数组,二维数组,这些是无法判断的.
const arr = [1, [2, 3], 4]
arr.includes([2, 3]) //false
arr.indexOf([2, 3]) //-1
- 优缺点比较
两者都是采用 === 的操作符来作比较的,不同之处在于:对于 NaN 的处理结果不同。我们知道 js 中 NaN === NaN 的结果是 false, indexOf() 也是这样处理的,但是 includes() 不是这样的。
const demo = [1, NaN, 2, 3]
demo.indexOf(NaN) // -1
demo.includes(NaN) // true
如果只想知道某个值是否在数组中存在,而并不关心它的索引位置,建议使用 includes()。如果想获取一个值在数组中的位置,那么只能使用 indexOf 方法。
# 幂运算符**
如果不使用任何函数,如何实现一个数的求幂运算?
function pow(x, y) {
let res = 1
for (let i = 0; i < y; i++) {
res *= x
}
return res
}
pow(2, 10)
// 1024
除了自己封装函数来实现,也可是使用 Math.pow() 来完成。
Math.pow() 函数返回基数(base)的指数(exponent)次幂。
console.log(Math.pow(2, 10)) // 1024
在 ES7 可以这样写了:
console.log(2 ** 10) // 1024
# ES8 相关
# async / await
async 和 await 是一种更加优雅的异步编程解决方案,是Promise 的拓展,在我们处理异步的时候,比起回调函数,Promise的then方法会显得较为简洁和清晰,但是在处理多个彼此之间相互依赖的请求的时候,就会显的有些繁琐。这时候,用async/await更加优雅。
我们知道 JavaScript 是单线程的,使用 Promise 之后可以让我们书写异步操作更加简单,而 async 是让我们写起 Promise 像同步操作。
- 基本语法
前面添加了 async 的函数在执行后都会自动返回一个 Promise 对象:
async function foo() {
return 'imooc' // Promise.resolve('imooc')
}
console.log(foo()) // Promise
await 后面需要跟异步操作,不然就没有意义,而且 await 后面的 Promise 对象不必写 then,因为 await 的作用之一就是获取后面Promise 对象成功状态传递出来的参数。
function timeout() {
return new Promise(resolve => {
setTimeout(() => {
console.log(1)
resolve() // resolve('success')
}, 1000)
})
}
// 不加 async 和 await 是2、1 ;加了是 1、2
async function foo() {
await timeout() // let res = await timeout() res 是 success
console.log(2)
}
foo()
对于失败的处理:
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('success')
reject('error')
}, 1000)
})
}
async function foo() {
return await timeout()
}
foo().then(res => {
console.log(res) // resolve success
}).catch(err => {
console.log(err) // reject error
})
# Object 扩展
之前的语法如何获取对象的每一个属性值:
const obj = {
name: 'jie',
web: 'jiegiser.github.io',
course: 'webgis'
}
console.log(Object.keys(obj))
const res = Object.keys(obj).map(key => obj[key])
console.log(res)
// ["jie", "jiegiser.github.io", "webgis"]
ES8 中对象扩展补充了两个静态方法,用于遍历对象:Object.values(),Object.entries()
- Object.values()
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for...in,但是 for...in 还会遍历原型上的属性值)。
const obj = {
name: 'jie',
web: 'jiegiser.github.io',
course: 'webgis'
}
console.log(Object.values(obj))
// ["jie", "jiegiser.github.io", "webgis"]
- Object.entries() Object.entries() 方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致。(区别在于 for-in 循环也枚举原型链中的属性)
const obj = {
name: 'jie',
web: 'jiegiser.github.io',
course: 'webgis'
}
console.log(Object.entries(obj))
// [["name", "jie"]
// ["web", "jiegiser.github.io"]
// ["course", "webgis"]]
for (let [key, value] of Object.entries(obj)) {
console.log(key, value)
}
// name jie
// web jiegiser.github.io
// course webgis
- Object.getOwnPropertyDescriptors()
对象有以下描述符:
- value [属性的值]
- writable [属性的值是否可被改变]
- enumerable [属性的值是否可被枚举]
- configurable [描述符本身是否可被修改,属性是否可被删除]
获取对象指定属性的描述符:
const obj = {
name: 'jie',
web: 'jiegiser.github.io',
course: 'webgis'
}
console.log(Object.getOwnPropertyDescriptor(obj, 'name'))
// {value: "jie", writable: true, enumerable: true, configurable: true}
如果想获取对象的所有属性的描述符:
console.log(Object.getOwnPropertyDescriptors(obj))
# String 扩展
在 ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾;
- String.prototype.padStart()
把指定字符串填充到字符串头部,返回新字符串;该方法有两个参数,第一个参数是目标字符要保持的长度值;第二个参数是如果目标字符的长度不够需要的补白字符,默认为空;如下例子:
const str = 'gis'
console.log(str.padStart(8, 'x')) // xxxxxgis
console.log(str.padEnd(8, 'y')) // gisyyyyy
console.log(str.padStart(8)) // gis
- 场景1:日期格式化
希望把当前日期格式化城:yyyy-mm-dd的格式:
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = (now.getDate()).toString().padStart(2, '0')
console.log(`${year}-${month}-${day}`)
- 场景2:数字替换
// 数字替换,比如手机号,身份证号
const tel = '13012345678'
const newTel = tel.slice(-4).padStart(tel.length, '*')
console.log(newTel) // *******5678
- String.prototype.padEnd()
方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。
const str = 'gis'
console.log(str.padEnd(8, 'y')) // gisyyyyy
- 场景:时间戳统一长度
在 JS 前端我们处理时间戳的时候单位都是 ms 毫秒,但是,后端同学返回的时间戳则不一样是毫秒,可能只有 10 位,以 s 秒为单位。所以,我们在前端处理这个时间戳的时候,保险起见,要先做一个 13 位的补全,保证单位是毫秒。
// 伪代码
console.log(new Date().getTime()) // 时间戳 13 位的
timestamp = +String(timestamp).padEnd(13, '0')
# 尾逗号 Trailing commas
ES8 允许函数的最后一个参数有尾逗号(Trailing comma)。
此前,函数定义和调用时,都不允许最后一个参数后面出现逗号。
function clownsEverywhere(
param1,
param2
) {}
clownsEverywhere(
'foo',
'bar'
)
上面代码中,如果在param2或bar后面加一个逗号,就会报错。
如果像上面这样,将参数写成多行(即每个参数占据一行),以后修改代码的时候,想为函数 clownsEverywhere 添加第三个参数,或者调整参数的次序,就势必要在原来最后一个参数后面添加一个逗号。这对于版本管理系统来说,就会显示添加逗号的那一行也发生了变动。这看上去有点冗余,因此新的语法允许定义和调用时,尾部直接有一个逗号。
function clownsEverywhere(
param1,
param2,
) {}
clownsEverywhere(
'foo',
'bar',
)
这样的规定也使得,函数参数与数组和对象的尾逗号规则,保持一致了。
# ES9 相关
# for await of
异步迭代器(for-await-of):循环等待每个 Promise 对象变为 resolved 状态才进入下一步。
下面是实现迭代的方法:
const arr = ['es5', 'es6', 'es7']
arr[Symbol.iterator] = function () {
let nextIndex = 0
return {
next() {
return nextIndex < arr.length ? {
value: arr[nextIndex++],
done: false
} : {
value: undefined,
done: true
}
}
}
}
我们知道 for...of 是同步运行的,有时候一些任务集合是异步的,那这种遍历怎么办呢?
function Gen(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), item.then(console.log))
}
}
test()
// 1560090138232 Promise {<pending>}
// 1560090138234 Promise {<pending>}
// 1560090138235 Promise {<pending>}
// 100
// 2000
// 3000
这里写了几个小任务,分别是 2000ms 、100ms、3000ms 后任务结束。在上述遍历的过程中可以看到三个任务是同步启动的,然后输出上也不是按任务的执行顺序输出的,这显然不太符合我们的要求。
我们可以使用 async 来实现异步迭代:
function Gen(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), await item.then(console.log))
}
}
test()
// 2000
// 1560091834772 undefined
// 100
// 1560091836774 undefined
// 3000
// 1560091836775 undefined
在 ES9 中也可以用 for...await...of 的语法来操作:
function Gen(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for await (let item of arr) {
console.log(Date.now(), item)
}
}
test()
// 1560092345730 2000
// 1560092345730 100
// 1560092346336 3000
上面的 for...await...of 的语法是异步可迭代协议(Symbol.asyncIterator),类似 for of 这种循环需要遵循 Symbol.interator
可迭代协议一样;Symbol.asyncIterator 异步可迭代协议如下:也是 next 函数返回 done、value 两个属性,不过遍历的值是异步函数;
let obj = {
count: 0,
Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve({ done: false, value: time })
}, time)
})
},
[Symbol.asyncIterator] () {
let self = this
return {
next () {
self.count++
if (self.count < 4) {
return self.Gen(Math.random() * 1000)
} else {
return Promise.resolve({
done: true,
value: ''
})
}
}
}
}
}
async function test () {
for await (let item of obj) {
console.log(Date.now(), item)
}
}
// 1560093560200 649.3946561938179
// 1560093560828 624.6310222512955
// 1560093561733 901.9497480464518
# RegExp Updates 正则扩展
- dotAll 模式 正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用 u 修饰符解决;另一个是行终止符(line terminator character)。
- U+000A 换行符(\n)
- U+000D 回车符(\r)
- U+2028 行分隔符(line separator)
- U+2029 段分隔符(paragraph separator)
dotAll 模式就是 s 修饰符;他可以匹配换行、回车、行分割、段分割符;
console.log(/foo.bar/.test('foo\nbar')) // false
console.log(/foo.bar/s.test('foo\nbar')) // true
在 ES5 中我们都是这么解决的:
console.log(/foo[^]bar/.test('foo\nbar')) // true
// or
console.log(/foo[\s\S]bar/.test('foo\nbar')) // true
那如何判断当前正则是否使用了 dotAll 模式呢?
const re = /foo.bar/s // Or, `const re = new RegExp('foo.bar', 's')` .
console.log(re.test('foo\nbar')) // true
console.log(re.dotAll) // true
console.log(re.flags) // 's
- 具名组匹配
我们在写正则表达式的时候,可以把一部分用()包裹起来,被包裹起来的这部分称作“分组捕获”。
console.log('2020-05-01'.match(/(\d{4})-(\d{2})-(\d{2})/))
// ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: undefined]
这个正则匹配很简单,按照 match 的语法,没有使用 g 标识符,所以返回值第一个数值是正则表达式的完整匹配,接下来的第二个值到第四个值是分组匹配(2020, 05, 01)。
此外 match 返回值还有几个属性,分别是 index、input、groups。
index [匹配的结果的开始位置] input [搜索的字符串] groups [一个捕获组数组 或 undefined(如果没有定义命名捕获组)]
我们通过数组来获取这些捕获:
let t = '2020-05-01'.match(/(\d{4})-(\d{2})-(\d{2})/)
console.log(t[1]) // 2020
console.log(t[2]) // 05
console.log(t[3]) // 01
上文中重点看下 groups 的解释,这里提到了命名捕获组的概念,如果没有定义 groups 就是 undefined。很明显,我们上述的返回值就是 undefined 间接说明没有定义命名捕获分组。那什么是命名捕获分组呢?
console.log('2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/))
// ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}]
这段代码的返回值 groups 已经是 Object 了,具体的值是:
groups: {
year: "2020",
month: "05",
day: "01"
}
这个 Object 的 key 就是正则表达式中定义的,也就是把捕获分组进行了命名。想获取这些捕获可以这样做:
let t = '2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
// ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}]
console.log(t.groups.year) // 2020
console.log(t.groups.month) // 05
console.log(t.groups.day) // 01
- 后行断言
在 ES9 之前 JavaScript 正则只支持先行断言,不支持后行断言。简单复习下先行断言的知识:
let test = 'hello world'
console.log(test.match(/hello(?=\sworld)/))
// ["hello", index: 0, input: "hello world", groups: undefined]
这段代码要匹配后面是 world 的 hello,但是反过来就不成:
let test = 'world hello'
console.log(test.match(/hello(?=\sworld)/))
// null
比如我们想判断前面是 world 的 hello,这个代码是实现不了的。在 ES9 就支持这个后行断言了:
let test = 'world hello'
console.log(test.match(/(?<=world\s)hello/))
// ["hello", index: 6, input: "world hello", groups: undefined]
(?<...)是后行断言的符号,(?...)是先行断言的符号,然后结合 =(等于)、!(不等)、\1(捕获匹配)。
# Object Rest & Spread 对象扩展
在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const input = {
a: 1,
b: 2
}
const output = {
...input,
c: 3
}
console.log(output) // {a: 1, b: 2, c: 3}
我们再来看下 Object rest 的示例:
const input = {
a: 1,
b: 2,
c: 3
}
let { a, ...rest } = input
console.log(a, rest) // 1 {b: 2, c: 3}
注意这是浅拷贝
# Promise.prototype.finally()
指定不管最后状态如何都会执行的回调函数。 Promise.prototype.finally() 方法返回一个 Promise,在promise执行结束时,无论结果是 fulfilled 或者是 rejected,在执行 then() 和 catch() 后,都会执行 finally 指定的回调函数。这为指定执行完promise后,无论结果是 fulfilled 还是 rejected 都需要执行的代码提供了一种方式,避免同样的语句需要在 then() 和 catch() 中各写一次的情况。例如,我们一般写得 loading 状态;可以直接写在 finally 里面去停止 loading;
其他,比如数据库断开链接:
let connection
db.open()
.then(conn => {
connection = conn
return connection.select({
name: 'Jane'
})
})
.then(result => {
// Process result
// Use `connection` to make more queries
})···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close()
})
# 字符串扩展
放松对标签模板里字符串转义的限制, 遇到不合法的字符串转义返回 undefined,并且从 raw 上可获取原字符串。
ES9 开始,模板字符串允许嵌套支持常见转义序列,移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。
- 带标签的模板字符串
const foo = (a, b, c, d) => {
console.log(a)
console.log(b)
console.log(c)
console.log(d)
}
const name = 'jie'
const age = 18
foo `这是${name},他的年龄是${age}岁`
// ["这是", ",他的年龄是", "岁", raw: Array(3)]
// raw: (3) ["这是", ",他的年龄是", "岁"]
// jie
// 18
// undefined
ES9 标准移除了对 ECMAScript 带标签的模板字符串中转义序列的语法限制。
function tag(strs) {
console.log(strs)
// strs[0] === undefined
// strs.raw[0] === "\\unicode and \\u{55}"
}
// 在标签函数中使用
tag `\u{61} and \u{62}` //
tag `\u{61} and \unicode` // 结果是 undefined
// 之前的版本会报错:Invalid Unicode escape sequence
// 无效的Unicode转义序列
// 报错:
let bad = `bad escape sequence: \unicode`
# ES10 相关
# Object.fromEntries()
方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。
const obj = {
name: 'imooc',
course: 'es'
}
const entries = Object.entries(obj)
console.log(entries)
// [Array(2), Array(2)]
// ES10
const fromEntries = Object.fromEntries(entries)
console.log(fromEntries)
// {name: "imooc", course: "es"}
我们可以将 Map 转 Object:
const map = new Map()
map.set('name', 'imooc')
map.set('course', 'es')
console.log(map)
// Map(2) {"name" => "imooc", "course" => "es"}
const obj = Object.fromEntries(map)
console.log(obj)
// {name: "imooc", course: "es"}
也可以过滤对象的属性:
const course = {
math: 80,
english: 85,
chinese: 90
}
const res = Object.entries(course).filter(([key, val]) => val > 80)
console.log(res) // 0: (2) ["english", 85] 1: (2) ["chinese", 90]
console.log(Object.fromEntries(res))
// {english: 85, chinese: 90}
# String 扩展
- String.prototype.trimStart()
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
- String.prototype.trimEnd()
trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。
const str = ' jie '
// 去掉前面的空格
console.log(str.replace(/^\s+/g, ''))
// 去掉后面的空格
console.log(str.replace(/\s+$/g, ''))
// 去掉前面的空格
console.log(str.trimStart())
console.log(str.trimLeft())
// 去掉后面的空格
console.log(str.trimEnd())
console.log(str.trimRight())
// 去掉所有空格
console.log(str.trim())
# Array 扩展
- Array.prototype.flat() flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
语法:const newArray = arr.flat(depth) depth 指定要提取嵌套数组的结构深度,默认值为 1
const numbers = [1, 2, [3, 4, [5, 6]]]
// 此时 flat 的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]] 这个数组会扁平会处理,不会再继续遍历内部的元素是否还有数组
console.log(numbers.flat())
// [1, 2, 3, 4, [5, 6]]
console.log(numbers.flat(2))
// [1, 2, 3, 4, 5, 6]
// 使用 Infinity 也是一样的效果
console.log(numbers.flat(Infinity))
// [1, 2, 3, 4, 5, 6]
- Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const numbers = [1, 2, 3]
numbers.map(x => [x * 2]) // [[2], [4], [6]]
numbers.flatMap(x => [x * 2]) // [2, 4, 6]
这个示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下下面的示例:
let arr = ['今天天气不错', '', '早上好']
arr.map(s => s.split(''))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
arr.flatMap(s => s.split(''))
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
# 修订 Function.prototype.toString()
函数是对象,并且每个对象都有一个 .toString() 方法,因为它最初存在于Object.prototype.toString() 上。所有对象(包括函数)都是通过基于原型的类继承从它继承的。这意味着我们以前已经有 funcion.toString() 方法了。
Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。
这意味着还将返回注释、空格和语法详细信息。
function foo() {
// es10新特性
console.log('imooc')
}
console.log(foo.toString())
// function foo() {
// es10新特性
// console.log('imooc')
// }
// 直接在方法名 toString()
console.log(Number.parseInt.toString())
// function parseInt() { [native code] }
# 可选的 Catch Binding
在 ES10 之前我们都是这样捕获异常的:
try {
// tryCode
} catch (err) {
// catchCode
}
在这里 err 是必须的参数,在 ES10 可以省略这个参数:
// 省略 catch 绑定的参数和括号
try {
console.log('Foobar')
} catch {
console.error('Bar')
}
# JSON 扩展
- JSON superset (JSON 超集)
什么是 JSON 超集?,简而言之就是让 ECMAScript 兼容所有 JSON 支持的文本。 ECMAScript 曾在标准 JSON.parse 部分阐明 JSON 确为其一个子集,但由于 JSON 内容可以正常包含 \u2028
行分隔符 与 \u2029
段分隔符,而 ECMAScript 却不行。
// 在这之前是不支持分隔符在代码中
eval('var str = "ji"; \u2029 function foo() { return str; };')
console.log(foo())
// ji
- JSON.stringify() 增强能力
SON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
// \uD83D\uDE0E emoji 多字节的一个字符
console.log(JSON.stringify('\uD83D\uDE0E')) // 笑脸
// 如果我们只去其中的一部分 \uD83D 这其实是个无效的字符串
// 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列
console.log(JSON.stringify('\uD83D')) // "\ud83d"
# Symbol 扩展
- Symbol.prototype.description
我们知道,Symbol 的描述只被存储在内部的 Description ,没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:
const name = Symbol('es')
console.log(name.toString()) // Symbol(es)
console.log(name) // Symbol(es)
console.log(name === 'Symbol(es)') // false
console.log(name.toString() === 'Symbol(es)') // true
现在可以通过 description 方法获取 Symbol 的描述:
const name = Symbol('es')
console.log(name.description) // es
console.log(name.description === 'es') // true
description 是只读的属性;
# ES11 相关
# String 扩展
- String.prototype.matchAll()
matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器;
下面例子是想要获取到 div 里面的内容
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是一个p</p>
<span>span</span>
<div>第二个div</div>
<body>
</html>
`
// 1. exec 正则规则匹配获取
function selectDiv(regExp, str) {
let matches = []
while (true) {
// console.log(regExp.lastIndex)
const match = regExp.exec(str)
if (match === null) {
break
}
matches.push(match[1])
}
return matches
}
// 小括号表示捕获组
const regExp = /<div>(.*)<\/div>/g
const res = selectDiv(regExp, str)
console.log(res) // ["第一个div", "第二个div"]
// 2. 字符串 match 方法
console.log(str.match(regExp)) // ["<div>第一个div</div>", "<div>第二个div</div>"]
// 3. replace 方法
function selectDiv(regExp, str) {
let matches = []
str.replace(regExp, (all, first) => {
console.log(all, first)
// <div>第一个div</div> 第一个div
// <div>第二个div</div> 第二个div
matches.push(first)
})
return matches
}
const res = selectDiv(regExp, str)
console.log(res) // ["第一个div", "第二个div"]
使用 ES11 的 matchAll() 方法:
const str = `
<html>
<body>
<div>第一个div</div>
<p>这是一个p</p>
<span>span</span>
<div>第二个div</div>
<body>
</html>
`
const regExp = /<div>(.*)<\/div>/g
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) // ["第一个div", "第二个div"]
# Dynamic Import
现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。
如下例子:页面上有一个按钮,点击按钮才去加载 ajax 模块。这里的 ajax 是自己封装的一个方法
const oBtn = document.querySelector('#btn')
oBtn.addEventListener('click', () => {
import('./ajax').then(mod => {
mod.default('static/a.json', res => {
console.log(res)
})
})
})
# BigInt
BigInt,表示一个任意精度的整数,可以表示超长数据,可以超出 2 的 53 次方。
Js 中 Number 类型只能安全的表示-(2^53-1)至 2^53-1 范的值
console.log(2 ** 53) // es7 幂运算符
console.log(Number.MAX_SAFE_INTEGER) // 最大值-1
使用 BigInt 有两种方式:
- 方式一:数字后面增加 n
const bigInt = 9007199254740993n
console.log(bigInt) // 9007199254740993n
console.log(typeof bigInt) // bigint
console.log(1n == 1) // true
console.log(1n === 1) // false 值相同,类型不同
- 方式二:使用 BigInt 函数
const bigIntNum = BigInt(9007199254740993n)
console.log(bigIntNum) // 9007199254740993n
也可以进行相加:
const bigIntNum1 = BigInt(9007199254740993n)
const bigIntNum2 = BigInt(9007199254740993n)
console.log(bigIntNum1 + bigIntNum2) // 18014398509481986n
# Promise.allSettled()
Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入 reject 状态。
场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据:
Promise.all([
Promise.reject({
code: 500,
msg: '服务异常'
}),
Promise.resolve({
code: 200,
data: ['1', '2', '3']
}),
Promise.resolve({
code: 200,
data: ['4', '5', '6']
})
]).then(res => {
console.log(res)
console.log('成功')
}).catch(err => {
console.log(err)
console.log('失败')
})
我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态:
Promise.allSettled([
Promise.reject({
code: 500,
msg: '服务异常'
}),
Promise.resolve({
code: 200,
data: ['1', '2', '3']
}),
Promise.resolve({
code: 200,
data: ['4', '5', '6']
})
]).then(res => {
console.log(res)
console.log('成功')
}).catch(err => {
console.log(err)
console.log('失败')
})
# globalThis
提供了一个标准的方式去获取不同环境下的全局对象;
Javascript 在不同的环境获取全局对象有不通的方式:
- node 中通过 global
- web 中通过 window, self 等.
self:打开任何一个网页,浏览器会首先创建一个窗口,这个窗口就是一个 window 对象,也是js运行所依附的全局环境对象和全局作用域对象。self 指窗口本身,它返回的对象跟window对象是一模一样的。也正因为如此,window 对象的常用方法和函数都可以用 self 代替 window。
self.setTimeout(() => {
console.log(123)
}, 1000)
以前想要获取全局对象,可通过一个全局函数
const getGlobal = () => {
if (typeof self !== 'undefined') {
return self
}
if (typeof window !== 'undefined') {
return window
}
if (typeof global !== 'undefined') {
return global
}
throw new Error('无法找到全局对象')
}
const globals = getGlobal()
console.log(globals)
globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。
console.log(globalThis)
# 可选链 Optional chaining
可让我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。
const user = {
address: {
street: 'xx街道',
getNum() {
return '80号'
}
}
}
在之前的语法中,想获取到深层属性或方法,不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property... 这种错误,这极有可能让你整个应用挂掉。
const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)
用了 Optional Chaining ,上面代码会变成:
const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)
# 空值合并运算符(Nullish coalescing Operator)
空值合并运算符(??)是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。
// 如果设置为 false '' 0 都会判断为 false;执行 5
const b = 0 // 或者 null undefined false ''
const a = b || 5
console.log(a)
空值合并运算符 ?? 我们仅在第一项为 null 或 undefined 时设置默认值
// false 0 无效,只有值为 null 或 undefined 时才会取后面的数
const a = b ?? 123
console.log(a)