ES6
ES6在ES5的基础上新增了一系列特性,这里仅列出常用特性
变量的改变,添加了块级作用域的概念 let声明变量(块级作用域),let是更完美的var,它声明的全局变量不是全局属性widow的变量,这便解决了for循环中变量覆盖的问题 const声明常量(会计作用域)
// var
var a = [];
for (var index = 0; index < 10; index++) {
a[index]=function () {
console.log(index);// index会向上查找a作用域的index值
}
}
console.log(index); // 10
a[6](); // 10
// let
const a = [];
for (let index = 0; index < 10; index++) {
a[index]=function () { console.log(index); } // index只在本轮循环中生效,每一次循环都会新建一个变量
}
a[6](); // 6
console.log(index); // index not defined
字符串新增方法
let str = 'happy';
console.log(str.includes('ha')) // true
console.log(str.repeat(3)) // 'happyhappyhappy'
console.log(str.startsWith('ha')); // true,参数2为查找的位置
console.log(str.endsWith('p', 4)); // true,参数2为查找的字符串长度\
函数可以像C/C++那样设置默认参数值,增加数据容错能力
对象
键值对重名简写
function people(name, age) {
return {
name,
age
};
}
对象字面量简写
const people = {
name: 'lux',
getName () { // 省略冒号(:) 和function关键字
console.log(this.name)
}
}
提供对象对象合并
const obj1 = {
a: 1
}
const obj = Object.assign({c: 2}, obj1)
console.log(obj);
数据解构和rest参数
// 对象解构
const people = {
name: 'cs',
age: 25
}
const { name, age } = people; // 'cs', 25
// 数组解构
const arr = [1, 3, 4];
const [a, b, c] = arr; // 1, 3, 4
// rest参数,返回的是一个对象
const obj = {a: 2, b: 3, c: 4, d: 5};
const {a, ...rest} = obj; // 2 { b: 3, c: 4, d: 5 }
数据展开
// 展开对象(和上面Object.assign()一样,也是对象合并)
const obj = {a: 2, b: 3}
console.log({...obj, c: 5});// {a 2, b: 3, c: 5}
// 展开数组
const arr = [1, 3, 4]
console.log([...arr, 5]); // [1, 3, 4, 5]
Promise
Promise作为ES6的重要特性之一,被ES6列为正式规范。在控制台打印出来Promise是一个构造函数。
可以看到上面的Promise自身有我们常用的all、race、resoleve、reject等方法,原型中还有catch、then等方法,所以我们创建Promise实例时,将then会作为callback使用,避免回调地狱的出现。
// resolve、reject的使用
function getNum() {
const randomNum = Math.ceil(Math.random() * 10);
return new Promise((resolve, reject) => {
if (randomNum > 5) {
resolve('大于5');
} else {
reject('小于5');
}
})
}
getNum().then((result) => {
console.log('success', result);
}).catch((err) => {
console.log('err', err);
});
使用catch方法和then的第二个参数的效果一样,但是它还有你另外一个作用,那就是在then的第一个resolve回调函数中代码出错,不用卡死js的执行,而是会进入到catch中,捕获err原因。
Promise.all的提供了并行的操作能力,并且是在所有的一步操作执行完成后才执行回调。all接收一个数组参数,它会把所有异步操作的结果放进一个数组中传给then。
// all的使用
function async1 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('async1');
resolve('async1完成')
}, 1000);
})
}
function async2 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('async2');
resolve('async2完成')
}, 2000);
})
}
function async3 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('async3');
resolve('async3完成')
}, 3000);
})
}
Promise.all([async1(), async2(), async3()]).then((result) => {
console.log(result);
// do something...
}).catch((err) => {
console.log(err);
});
// result:
async1
async2
async3
[ 'async1完成', 'async2完成', 'async3完成' ]
该方法适用于游戏类一些场景,所有素材、静态资源请求都加载完成,在进行页面的初始化
race也提供了并行的操作能力,和all方法相反,大白话就是:all是以函数中执行最慢的那一个为准,race是以函数中执行最快的那一个为准。race接收一个数组参数,它会把执行最快的那个异步操作的结果传给then。
// 以上面的三个异步操作函数为例
Promise.race([async1(), async2(), async3()]).then((result) => {
console.log(result);
}).catch((err) => {
console.log(err);
});
// result:
async1
async1完成
async2
async3
该方法适用于请求超时的场景,比如请求图片资源,超过5秒就reject操作。
function requestImg() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('请求图片资源')
}, 6000);
})
}
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求超时');
}, 3000);
})
}
Promise.race([requestImg(), timeout()]).then((result) => {
console.log(result);
}).catch((err) => {
console.log(err);
});
// result:
请求超时
上面在请求超时完成后,其实requestImg()函数并没有停止执行,而是不会再进入resolve的回调函数中了。
Set
Set作为ES6新的数据解构(类数组),它的成员都是唯一的,因为最直接的使用场景便是去重、并、差、交集的使用。它使用的算法叫做“Same-value-zero equality”,类似精确运算的===,主要是NaN,这里它将两个视为相等。参见:http://es6.ruanyifeng.com/#docs/set-map#Set
// Set实例的常用方法和属性add,delete,clear,has、size
const s = new Set(['A', 'B', 'C']);
console.log(s); // Set { 'A', 'B', 'C' }
console.log(s.has('A')) // true,bool值
console.log(s.size) // 3
console.log(s.clear()) // Set {}
console.log(s.delete('A')) // true,bool值
同时Set实例配合常用遍历方法,实现并、交、差集。
const a =[1, 2, 3]
const b = [2, 3, 4];
// 并集
const s = Array.from(new Set([...a, ...b])); // [ 1, 2, 3, 4 ]
// 交集、差集
const bSet = new Set(b);
const interestSet = a.filter(v => bSet.has(v)); // [ 2, 3 ]
const interestSet = a.filter(v => !bSet.has(v)); // [ 1 ]
ES7
ES7在ES6的基础上添加三项内容:求幂运算符(**)、Array.prototype.includes()方法、函数作用域中严格模式的变更。
求幂运算符(**),这是一个中缀例子,效仿自Ruby等语法,使用更简洁
Math.pow(3, 2) === 3 ** 2 // 9
Array.prototype.includes()
数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
[1, 2, 3].indexOf(3) > -1 // true
等同于:
[1, 2, 3].includes(3) // true
简便性
includes方法略胜一筹,直接返回bool。indexOf需要返回数组下标,我们需要对下标值在进行操作,进而判断是否在数组中。
精确性
两者这都是通过===进行数据处理,但是对NaN数值的处理行为不同。includes对NaN的处理不会遵循严格模式去处理,所以返回true。indexOf会按照严格模式去处理,返回-1。
[1, 2, NaN].includes(NaN) // true
[1, 2, NaN].indexOf(NaN) // -1
如果仅仅查找数据是否在数组中,建议使用includes,如果是查找数据的索引位置,建议使用indexOf更好一些
async、await异步解决方案 提出场景有两个:JS是单线程、优化回调地狱的写法。
this.$http.jsonp('/login', (res) => {
this.$http.jsonp('/getInfo', (info) => {
// do something
})
})
})
在ES6中为了解决回调的书写方式,引入了Promise的then函数,业务逻辑很多的时候,需要链式多个then函数,语义会变得很不清楚。
new Promise((resolve, reject) => {this.login(resolve)})
.then(() => this.getInfo())
.then(() => {// do something})
.catch(() => { console.log("Error") })
Generator函数应运而生,它只是一个分段执行任务,通过状态指针next分布执行任务,但是流程管理不太方便(每个阶段何时执行不太明了),所以它只是一个中间产物。
const gen = function* () {
const f1 = yield this.login()
const f2 = yield this.getInfo()
};
ES8中把async和await变得更加方便,它其实就是Generator的语法糖。async/await是写异步代码的新方式,以前的方法有回调函数和Promise。相比于Promise,它更加简洁,并且处理错误、条件语句、获取中间值都更加方便。
async function asyncFunc(params) {
const result1 = await this.login()
const result2 = await this.getInfo()
}
如果想进一步了解async的具体时间,可以参见阮一峰的博客es6.ruanyifeng.com/#docs/async
Object.entries()
该方法会将某个对象的可枚举属性与值按照二维数组的方式返回。(如果目标对象是数组,则会将数组的下标作为键值返回)
Object.values({one: 1, two: 2}) // [1, 2]
Object.values({3: 'a', 1: 'b', 2: 'c'}) // ['b', 'c', 'a']
Object.extries([1, 3]) //[1, 3]
字符串填充padStart()、padEnd()
ES8提供了新的字符串填充方法,该方法可以使得字符串达到固定长度。它有两个参数,字符串目标长度和填充内容。
'react'.padStart(10, 'm') //'mmmmmreact'
'react'.padEnd(10, 'm') //' reactmmmmm'
'react'.padStart(3, 'm') // 'vue'
异步迭代器(for-await-of
):循环等待每个Promise
对象变为resolved
状态才进入下一步。
我们知道 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 {}
// 1560090138234 Promise {}
// 1560090138235 Promise {}
// 100
// 2000
// 3000
这里写了几个小任务,分别是 2000ms 、100ms、3000ms后任务结束。在上述遍历的过程中可以看到三个任务是同步启动的,然后输出上也不是按任务的执行顺序输出的,这显然不太符合我们的要求。
聪明的同学一定能想起来 await 的作用,它可以中断程序的执行直到这个 Promise 对象的状态发生改变,我们修改上面的代码:
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
从返回值看确实是按照任务的先后顺序进行的,其中原理也有说明是利用了 await
中断程序的功能。
在 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
从这个结果来看和第二种写法效果差不多,但是工作原理确完全不同,重点观察下输出的时间(Chrome Console), 第二种写法是代码块中有 await 导致等待 Promise 的状态而不再继续执行;第三种写法是整个代码块都不执行,等待 arr 当前的值(Promise状态)发生变化之后,才执行代码块的内容。
回想我们之前给数据结构自定义遍历器是同步的,如果想定义适合 for...await...of 的异步遍历器该怎么做呢?答案是 Symbol.asyncIterator。
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
前面在讲 function
的 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}
这块代码展示了 spread
语法,可以把 input
对象的数据都拓展到 output
对象,这个功能很实用。
我们再来看下 Object rest
的示例:
const input = {
a: 1,
b: 2,
c: 3
}
let { a, ...rest } = input
console.log(a, rest) // 1 {b: 2, c: 3}
当对象 key-value 不确定的时候,把必选的 key 赋值给变量,用一个变量收敛其他可选的 key 数据,这在之前是做不到的。
指定不管最后状态如何都会执行的回调函数。
Promise.prototype.finally() 方法返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。
基本语法
p.finally(onFinally)
示例
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
// reject('fail')
}, 1000)
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
}).finally(() => {
console.log('finally')
})
比如我们常用的场景:loading关闭
需要每次发送请求,都会有loading
提示,请求发送完毕,就需要关闭loading
提示框,不然界面就无法被点击。不管请求成功或是失败,这个loading
都需要关闭掉,这时把关闭loading
的代码写在finally
里再合适不过了。
Object.fromEntries()
方法 Object.fromEntries()
把键值对列表转换为一个对象,这个方法是和 Object.entries()
相对的。
Object.fromEntries([
['foo', 1],
['bar', 2]
])
// {foo: 1, bar: 2}
案例1:Object
转换操作
const obj = {
name: 'randy',
course: 'es'
}
const entries = Object.entries(obj)
console.log(entries)
// [Array(2), Array(2)]
// ES10
const fromEntries = Object.fromEntries(entries)
console.log(fromEntries)
// {name: "randy", course: "es"}
案例2:Map
转 Object
const map = new Map()
map.set('name', 'randy')
map.set('course', 'es')
console.log(map)
const obj = Object.fromEntries(map)
console.log(obj)
// {name: "randy", course: "es"}
案例3:过滤
course
表示所有课程,想请求课程分数大于80的课程组成的对象:
const course = {
math: 80,
english: 85,
chinese: 90
}
const res = Object.entries(course).filter(([key, val]) => val > 80)
console.log(res)
console.log(Object.fromEntries(res)
String.prototype.trimStart()
trimStart()
方法从字符串的开头删除空格,trimLeft()
是此方法的别名。
语法
str.trimStart() 或 str.trimLeft()
注意,虽然 trimLeft
是 trimStart
的别名,但是你会发现 String.prototype.trimLeft.name === 'trimStart'
,一定要记住
示例
let str = ' foo '
console.log(str.length) // 8
str = str.trimStart()
console.log(str.length) // 5
trimEnd()
方法从一个字符串的右端移除空白字符,trimRight
是 trimEnd
的别名。
语法
str.trimEnd() 或 str.trimRight()
注意,虽然 trimRight
是 trimEnd
的别名,但是你会发现 String.prototype.trimRight.name === 'trimEnd'
,一定要记住
示例
let str = ' foo '
console.log(str.length) // 8
str = str.trimEnd()
console.log(str.length) // 6
Array.prototype.flat()
flat()
方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const newArray = arr.flat(depth)
解释
参数 | 含义 | 必选 |
---|---|---|
depth | 指定要提取嵌套数组的结构深度,默认值为 1 | N |
示例
const numbers = [1, 2, [3, 4, [5, 6]]]
console.log(numbers.flat())
// [1, 2, 3, 4, [5, 6]]
此时 flat
的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]]
这个数组会扁平会处理,不会再继续遍历内部的元素是否还有数组
const numbers = [1, 2, [3, 4, [5, 6]]]
console.log(numbers.flat(2))
// [1, 2, 3, 4, 5, 6]
当 flat 的参数大于等于 2,返回值就是 [1, 2, 3, 4, 5, 6] 了。
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {// 返回新数组的元素 }[, thisArg])
解释
参数 | 含义 | 必选 |
---|---|---|
callback | 可以生成一个新数组中的元素的函数,可以传入三个参数:currentValue、index、array | Y |
thisArg | 遍历函数 this 的指向 | N |
示例
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(''))
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
函数是对象,并且每个对象都有一个 .toString() 方法,因为它最初存在于Object.prototype.toString() 上。所有对象(包括函数)都是通过基于原型的类继承从它继承的。这意味着我们以前已经有 funcion.toString() 方法了。
Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。
这意味着还将返回注释、空格和语法详细信息。
function foo() {
// es10新特性
console.log('randy')
}
console.log(foo.toString())
// 直接在方法名toString()
console.log(Number.parseInt.toString())
在 ES10
之前我们都是这样捕获异常的:
try {
// tryCode
} catch (err) {
// catchCode
}
在这里 err
是必须的参数,在 ES10
可以省略这个参数
try {
console.log('Foobar')
} catch {
console.error('Bar')
}
案例:验证参数是否为json格式
这个需求我们只需要返回true
或false
,并不关心catch
的错误。
const validJSON = json => {
try {
JSON.parse(json)
return true
} catch {
return false
}
}
const json = '{"name":"randy", "course": "es"}'
console.log(validJSON(json))
JSON.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.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
String.prototype.matchAll()
matchAll()
方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器
语法
str.matchAll(regexp)
解释
参数 | 含义 | 必选 |
---|---|---|
regexp | 正则表达式对象 | Y |
注意
new RegExp(obj)
将其转换为一个 RegExp
示例
const str = `
第一个div
这是一个p
span
第二个div
`
请找出所有的div元素。
function selectDiv(regExp, str) {
let matches = [];
for (let match of str.matchAll(regExp)) {
console.log(match);
matches.push(match[1]);
}
return matches;
}
const res = selectDiv(/(.*)<\/div>/g, str);
console.log(res); // ['第一个div', '第二个div']
Dynamic Import
按需 import 提案几年前就已提出,如今终于能进入ES正式规范。这里个人理解成“按需”更为贴切。现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。
页面上有一个按钮,点击按钮才去加载ajax模块。
const oBtn = document.querySelector('#btn')
oBtn.addEventListener('click', () => {
import('./ajax').then(mod => {
// console.log(mod)
mod.default('static/a.json', res => {
console.log(res)
})
})
})
当然,webpack
目前已很好的支持了该特性。
BigInt
在 ES10
增加了新的原始数据类型: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)
console.log(typeof bigInt) // bigint
console.log(1n == 1) // true
console.log(1n === 1) // false
方式二:使用 BigInt 函数
const bigIntNum = BigInt(9007199254740993)
console.log(bigIntNum)
Promise扩展
Promise.allSettled()
学习了ES新特性,我们都知道 Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常reject,所有任务都会挂掉,Promise直接进入 reject 状态。
Promise.allSettled()方法就是不管是否成功失败,都会返回结果并进入then方法。
场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据
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) // [{…}, {…}, {…}]
const data = res.filter(item => item.status === 'fulfilled')
console.log(data) // [{…}, {…}]
}).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 street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)
用了 Optional Chaining
,上面代码会变成
const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)
可选链中的 ? 表示如果问号左边表达式有值, 就会继续查询问号后面的字段。根据上面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全。
空值合并运算符(Nullish coa)lescing Operator
-
空值合并运算符 ?? 是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。
const b = 0
const a = b || 5
console.log(a) // 5
上面的例子并不符合我们的需求,因为它把0也当做无效值了。
空值合并运算符 ??
我们仅在第一项为 null
或 undefined
时设置默认值
const b = 0
const a = b || 5
console.log(a) // 0
ECMAScript2021(ES12)
String扩展
String.prototype.replaceAll()
replace() 方法仅替换一个字符串中某模式 pattern 的首个实例,
replaceAll() 会返回一个新字符串,该字符串中用一个替换项替换了原字符串中所有匹配了某模式的部分。模式可以是一个字符串或一个正则表达式,而替换项可以是一个字符串或一个应用于每个匹配项的函数。replaceAll() 相当于增强了 replace() 的特性,全量替换。
我们来看下面的例子
const str1 = "hello word";
console.log(str1.replace("o", "哦")); // hell哦 word
console.log(str1.replace(/o/g, "哦")); // hell哦 w哦rd
console.log(str1.replaceAll("o", "哦")); // hell哦 w哦rd
console.log(str1.replaceAll(/o/g, "哦")); // hell哦 w哦rd
Promise扩展
Promise.any()
Promise.any()和Promise.race()类似都是返回第一个结果,但是Promise.any()只返回第一个成功的,尽管某个 promise 的 reject 早于另一个 promise 的 resolve,仍将返回那个首先 resolve 的 promise。
如果都被reject则会抛出All promises were rejected错误。
Promise.any([
Promise.reject("1"),
Promise.resolve("2"),
Promise.resolve("3"),
])
.then((res) => console.log(res)) // 2
.catch((err) => console.error(err));
Promise.any([
Promise.resolve("1"),
Promise.resolve("2"),
Promise.resolve("3"),
])
.then((res) => console.log(res)) // 1
.catch((err) => console.error(err));
Promise.any([
Promise.reject("错误 1"),
Promise.reject("错误 2"),
Promise.reject("错误 3"),
])
.then((res) => console.log(res))
.catch((err) => console.error(err));
// AggregateError: All promises were rejected
逻辑操作符和赋值表达
&&=
&&=
可以理解为有值再赋值的意思
let num1 = 1;
let num2 = 2;
num1 &&= num2;
console.log(num1); // 2
let num1 = 0;
let num2 = 2;
num1 &&= num2;
console.log(num1); // 0
等价于
num1 && (num1 = num2);
或者
if (num1) {
num1 = num2;
}
||=
&&=
可以理解为没值再赋值的意思
let num3 = 3;
let num4 = 4;
num3 ||= num4;
console.log(num1); // 3
let num3 = 0;
let num4 = 4;
num3 ||= num4;
console.log(num3); // 4
等价于
num3 || (num3 = num4);
或
if (!num3) {
num3 = num4;
}
??=
??=
跟我们前面说的空值合并运算符 ??
类似,只有在左边的值严格等于 null
或 undefined
时起作用进行赋值。
let num5 = 0;
let num6 = null;
let num7 = undefined;
num5 ??= 10;
console.log(num5); // 0
num6 ??= 10;
console.log(num6); // 10
num7 ??= 10;
console.log(num7); // 10
等价于
num ?? (num = 10);
或
if(num === null || num === undefined) {
num = 10
}
数值分隔符
我们定义number
类型数据的时候,可以使用_
当做分隔符,让数据更美观易懂,并不会影响该数据的值。
const num1 = 1000000000;
const num2 = 1000_000_000;
console.log(num1); // 1000000000
console.log(num2); // 1000000000
WeakRef
WeakRef
允许创建对象的弱引用。弱引用笔者在前面讲weakSet、weakMap
的时候说过了,就是在进行垃圾回收的时候不会考虑该对象是否还在WeakRef
中使用。
我们必须用 new
关键字创建新的 WeakRef
,然后使用deref()
读取引用的对象。
let weakref = new WeakRef({name: 'randy', age: 27})
weakref.deref() // {name: 'randy', age: 27}
weakref.deref().year // 27
总结
笔者用一张图来总结ES7-ES12
所有知识点。
-