ES7新特性(2016)
Array.prototype.includes
Includes
方法用来检测数组中是否包含某个元素,返回布尔类型值。
includes
函数与 indexOf
函数很相似,下面两个表达式是等价的:
arr.includes(x)
arr.indexOf(x) >= 0
在ES7之前的做法是使用indexOf()
验证数组中是否存在某个元素,这时需要根据返回值是否为-1来判断;在ES7之后使用includes()
验证数组中是否存在某个元素,这样更加直观简单:
// includes indexOf
const mingzhu = ['西游记','红楼梦','三国演义','水浒传'];
//判断
console.log(mingzhu.includes('西游记')); // true
console.log(mingzhu.includes('金瓶梅')); // false
指数操作符
在 ES7
中引入指数运算符「**」
,用来实现幂运算,功能与 Math.pow
结果相同
// **
console.log(2 ** 10); // 1024
console.log(Math.pow(2, 10)); // 1024
ES8新特性(2017)
async 和 await
async
和 await
两种语法结合可以让异步代码像同步代码一样
async 函数
async
函数的返回值为promise
对象,promise
对象的结果由async
函数执行的返回值决定 例子1
//async 函数
async function fn(){
// 返回一个字符串
return '测试';
}
const result = fn();
//调用 then 方法
result.then(value => {
console.log(value);// 测试
}, reason => {
console.warn(reason);
})
async
函数 return
的结果是一个字符串, 但返回的结果是一个成功 Promise
对象;
例子2
//async 函数
async function fn(){
//抛出错误, 返回的结果是一个失败的 Promise
throw new Error('出错啦!');
}
const result = fn();
//调用 then 方法
result.then(value => {
console.log(value);
}, reason => {
console.warn(reason);// Error: 出错啦!
})
例子3
//async 函数
async function fn(){
//返回的结果如果是一个 Promise 对象
return new Promise((resolve, reject)=>{
resolve('成功的数据');
});
}
const result = fn();
//调用 then 方法
result.then(value => {
console.log(value);// 成功的数据
}, reason => {
console.warn(reason);
})
例子4
//async 函数
async function fn(){
//返回的结果如果是一个 Promise 对象
return new Promise((resolve, reject)=>{
reject("失败的错误");
});
}
const result = fn();
//调用 then 方法
result.then(value => {
console.log(value);
}, reason => {
console.warn(reason);// 失败的错误
})
await 表达式
await
必须写在async
函数中await
右侧的表达式一般为promise
对象await
返回的是promise
成功的值await
的promise
失败了, 就会抛出异常, 需要通过try...catch
捕获处理
//创建 promise 对象
const p = new Promise((resolve, reject) => {
// resolve("用户数据");
reject("失败啦!");
})
// await 要放在 async 函数中.
async function main() {
try {
let result = await p;
console.log(result);
} catch (e) {
console.log(e);// 失败啦
}
}
//调用函数
main();
async与await封装AJAX请求
// 发送 AJAX 请求, 返回的结果是 Promise 对象
function sendAJAX(url) {
return new Promise((resolve, reject) => {
//1. 创建对象
const x = new XMLHttpRequest();
//2. 初始化
x.open('GET', url);
//3. 发送
x.send();
//4. 事件绑定
x.onreadystatechange = function () {
if (x.readyState === 4) {
if (x.status >= 200 && x.status < 300) {
//成功啦
resolve(x.response);
}else{
//如果失败
reject(x.status);
}
}
}
})
}
//promise then 方法测试
// sendAJAX("https://api.apiopen.top/getJoke").then(value=>{
// console.log(value);
// }, reason=>{})
// async 与 await 测试 axios
async function main(){
//发送 AJAX 请求
let result = await sendAJAX("https://api.apiopen.top/getJoke");
//再次测试
let tianqi = await sendAJAX('https://www.tianqiapi.com/api/?version=v1&city=%E5%8C%97%E4%BA%AC&appid=23941491&appsecret=TXoD5e8P')
console.log("result",result)
console.log("tianqi",tianqi);
}
main();
Object.values 和 Object.entries
Object.values()
方法返回一个给定对象的所有可枚举属性值的数组Object.entries()
方法返回一个给定对象自身可遍历属性 [key,value] 的数组
//声明对象
const school = {
name:"学习",
cities:['北京','上海','深圳'],
xueke: ['前端','Java','大数据','运维']
};
//获取对象所有的键
console.log(Object.keys(school));// ["name", "cities", "xueke"]
//获取对象所有的值
console.log(Object.values(school));// ["学习", ['北京','上海','深圳'], ['前端','Java','大数据','运维']]
//entries
console.log(Object.entries(school));// [["name", "学习"],["cities", ['北京','上海','深圳']],["xueke", ['前端','Java','大数据','运维']]]
//创建 Map
const m = new Map(Object.entries(school));
console.log(m.get('cities'));// ["北京", "上海", "深圳"]
String padding
在ES8
中String
新增了两个实例函数String.prototype.padStart
和String.prototype.padEnd
,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
String.padStart(targetLength,[padString])
- targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
- padString:(可选)填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为空格(" ")。
console.log('0.0'.padStart(4,'10'))
console.log('0.0'.padStart(5,'10'))
console.log('0.0'.padStart(7,'10'))
console.log('0.0'.padStart(20))
String.padEnd(targetLength,padString])
- targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度,则返回当前字符串本身。
- padString:(可选) 填充字符串。如果字符串太长,使填充后的字符串长度超过了目标长度,则只保留最左侧的部分,其他部分会被截断,此参数的缺省值为空格(" ")。
console.log('0.0'.padEnd(4,'0')) //0.00
console.log('0.0'.padEnd(10,'0'))//0.00000000
Object.getOwnPropertyDescriptors
该方法返回指定对象所有自身属性的描述对象,如果没有任何自身属性,则返回空对象。
//声明对象
const school = {
name:"学习",
cities:['北京','上海','深圳'],
xueke: ['前端','Java','大数据','运维']
};
//对象属性的描述对象
console.log(Object.getOwnPropertyDescriptors(school));
//{
// cities: {value: Array(3), writable: true, enumerable: true, configurable: true}
// name: {value: "学习", writable: true, enumerable: true, configurable: true}
// xueke: {value: Array(4), writable: true, enumerable: true, configurable: true}
//}
函数参数列表和调用中的尾逗号(Trailing commas)
尾逗号在函数定义中只是一个纯粹语法变化,在ES5
中,将会非法语法,在函数参数后面应该是没有逗号的:
var f = function(a,b,c,d) {
// ...
console.log(d)// this
}
f(1,2,3,'this')
在ES8
中,这种尾逗号是没有问题的:
var f = function(a,b,c,d,) {
// ...
console.log(d)// this
}
f(1,2,3,'this')
尾逗号主要有用在使用多行参数风格(典型的是那些很长的参数名),开发者终于可以忘记逗号放在第一位这种奇怪的写法。自从逗号bugs
主要原因就是使用他们。而现在你可以到处使用逗号,甚至最后参数都可以。
SharedArrayBuffer对象
SharedArrayBuffer
对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer
对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer
不同的是,SharedArrayBuffer
不能被分离。
请注意,作为对Spectre的响应,所有主流浏览器均默认于2018年1月5日禁用SharedArrayBuffer。 Chrome在启用了网站隔离功能的平台上的v67中重新启用了该功能,以防止出现Spectre风格的漏洞。 语法: new SharedArrayBuffer(length)
参数length指所创建的数组缓冲区的大小,以字节(byte)为单位。
- 需要new运算符构造
// 创建一个1024字节的缓冲
let buffer = new SharedArrayBuffer(1024);
console.log(buffer.byteLength); // 1024
Atomics对象
Atomics
对象提供了一组静态方法对 SharedArrayBuffer
和 ArrayBuffer
对象进行原子操作。
这些原子操作属于 Atomics
模块。与一般的全局对象不同,Atomics
不是构造函数,因此不能使用 new
操作符调用,也不能将其当作函数直接调用。Atomics
的所有属性和方法都是静态的(与 Math
对象一样)。
多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。
Atomics.add(typedArray, index, value)
将指定位置上的数组元素与给定的值相加,并返回相加前该元素的值。 参数:
- typedArray是一个整型 的类型数组。例如Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,或者Uint32Array。indext, 数组中的位置,该位置数值会被加总并更新。value增加的数字。
- 返回值: 返回给定位置的旧值
- 如果typedArray不是符合的格式,就会报错
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
console.log(ta[0]); // 0
ta[0] = 5;
console.log(ta[0]) // 5
Atomics.add(ta, 0, 12); //将下标为0的值加12, 返回ta[0]
console.log(ta[0]); // 17
console.log(Atomics.load(ta, 0)); // 17 ✅ // 12 ❌
Atomics.load(typedArray, index)
返回数组中指定元素的值。
Atomics.and(typedArray, index, value)
将指定位置上的数组元素与给定的值相加,并返回与操作前该元素的值。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;
console.log(Atomics.and(ta, 0, 1)); // 5
console.log(Atomics.load(ta, 0)); // 1
Atomics.compareExchange(typedArray, index, expectedValue, replacementValue)
如果数组中指定的元素与给定的值相等,则将其更新为新的值,并返回该元素原先的值。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;
// 给定的值与指定元素值相等时
console.log(Atomics.compareExchange(ta, 0, 5, 12));// 5
console.log(Atomics.load(ta, 0))// 12
// 给定的值与指定元素值不相等时
console.log(Atomics.compareExchange(ta, 0, 6, 12));// 5
console.log(Atomics.load(ta, 0))// 5
Atomics.exchange(typedArray, index, value)
将数组中指定的元素更新为给定的值,并返回该元素更新前的值。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;
console.log(Atomics.exchange(ta, 0, 12));// 6
console.log(Atomics.load(ta, 0));// 12
Atomics.isLockFree(size)
可以用来检测当前系统是否支持硬件级的原子操作。对于指定大小的数组,如果当前系统支持硬件级的原子操作,则返回 true;否则就意味着对于该数组,Atomics 对象中的各原子操作都只能用锁来实现。此函数面向的是技术专家。 换种说法:表示
Atomics
对象是否可以处理某个size
的内存锁定。如果返回false
,应用程序就需要自己来实现锁定。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;
console.log(Atomics.isLockFree(1));// true
console.log(Atomics.isLockFree(2));// true
console.log(Atomics.isLockFree(3));// false
console.log(Atomics.isLockFree(4));// true
Atomics.notify(typedArray, index, count)
唤醒等待队列中正在数组指定位置的元素上等待的线程。返回值为成功唤醒的线程数量。 参数:
- sharedArray:共享内存的视图数组。
- index:视图数据的位置(从0开始)。
- count:需要唤醒的 Worker 线程的数量,默认为Infinity。
Atomics.or(typedArray, index, value)
将指定位置上的数组元素与给定的值相或,并返回或操作前该元素的值。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
console.log(Atomics.or(ta, 0, 1));// 0
console.log(Atomics.load(ta, 0)); // 1
Atomics.store(typedArray, index, value)
将数组中指定的元素设置为给定的值,并返回该值。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;
console.log(Atomics.store(ta, 0, 12));// 12
Atomics.sub(typedArray, index, value)
将指定位置上的数组元素与给定的值相减,并返回相减前该元素的值。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;
console.log(Atomics.sub(ta, 0, 2));// 5
console.log(Atomics.load(ta, 0));// 3
Atomics.wait(typedArray, index, value, timeout)
检测数组中某个指定位置上的值是否仍然是给定值,是则保持挂起直到被唤醒或超时。返回值为 "ok"、"not-equal" 或 "time-out"。调用时,如果当前线程不允许阻塞,则会抛出异常(大多数浏览器都不允许在主线程中调用 wait())。 参数:
- sharedArray:共享内存的视图数组。
- index:视图数据的位置(从0开始)。
- value:该位置的预期值。一旦实际值等于预期值,就进入休眠。
- timeout:整数,表示过了这个时间以后,就自动唤醒,单位毫秒。该参数可选,默认值是Infinity,即无限期的休眠,只有通过Atomics.notify()方法才能唤醒。 Atomics.xor(typedArray, index, value)
将指定位置上的数组元素与给定的值相异或,并返回异或操作前该元素的值。
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0] = 5;
console.log(Atomics.xor(ta, 0, 1));// 5
console.log(Atomics.load(ta, 0));// 4
ES9新特性(2018)
异步迭代器(for await...of)
for...of
循环用于遍历同步的 Iterator
接口。2⃣而新引入的for await...of
循环,则是用于遍历异步的 Iterator
接口
手写一个异步迭代器
let createAsyncIterable = {
[Symbol.asyncIterator]:() => {
const items = [`a`, `s`, `y`, `n`, `c`];
return {
next: () => Promise.resolve({
done: items.length === 0,
value: items.shift()
})
}
}
};
我们可以使用如下代码进行遍历:
for await (const item of createAsyncIterable) {
console.log(item);
}
如果遇到了 Uncaught SyntaxError: Unexpected reserved word
错误,那是因为 for-await-of
只能在 async
函数或者 async
生成器里面使用。
修改一下:
(async function () {
for await (const item of createAsyncIterable) {
console.log(item); // a s y n c
}
})();
根据上面的代码,我们可以知道,异步迭代器返回结果是一个Promise
对象,如果next
方法返回的 Promise
对象被reject
,for await...of
就会报错,要用try...catch
捕捉。
let createAsyncIterable = {
[Symbol.asyncIterator]:() => {
const items = [`a`, `s`, `y`, `n`, `c`];
return {
next:() => Promise.reject({
err:"出错啦"
})
}
}
};
(async function () {
try{
for await (const item of createAsyncIterable) {
console.log(item);
}
}catch(e){
console.log(e);// {err: "出错啦"}
}
})()
Promise.prototype.finally()
finally()
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
const Gen = (time) => {
return new Promise((resolve, reject) => {
setTimeout(function () {
if (time < 500) {
reject(time)
} else {
resolve(time)
}
}, time)
})
}
Gen(Math.random() * 1000)
.then(val => console.log('resolve', val))
.catch(err => console.log('reject', err))
.finally(() => { // 不管走resolve 还是reject 都要走这里
console.log('finish')
})
finally
方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise
状态到底是fulfilled
还是rejected
。这表明,finally
方法里面的操作,应该是与状态无关的,不依赖于 Promise
的执行结果。
使用finally的好处就是避免同样的语句需要在成功和失败两种情况中各写一次的情况。
Rest/Spread 属性
Rest
参数与 spread
扩展运算符在 ES6
中已经引入,不过 ES6
中只针对于数组,
在 ES9
中为对象提供了像数组一样的 rest
参数和扩展运算符;
//rest 参数
function connect({host, port, ...user}){
console.log(host);// 127.0.0.1
console.log(port); // 3306
console.log(user);// {username: 'root',password: 'root',type: 'master'}
}
connect({
host: '127.0.0.1',
port: 3306,
username: 'root',
password: 'root',
type: 'master'
});
//对象合并
const skillOne = {
q: '天音波'
}
const skillTwo = {
w: '金钟罩'
}
const mangseng = {...skillOne, ...skillTwo};
console.log(mangseng) // {q: "天音波", w: "金钟罩"}
正则表达式命名捕获分组
ES9
允许命名捕获分组使用符号『?<name>』
,这样获取捕获结果可读性更强
//声明一个字符串
let str = '<a href="https://www.baidu.com/">百度一下</a>';
//提取 url 与 『标签文本』
const reg = /<a href="(.*)">(.*)<\/a>/;
//执行
const result = reg.exec(str);
console.log("result",result);
console.log("result[1]",result[1]);
console.log("result[2]",result[2]);
接下来对上面的代码进行分组
let str = '<a href="https://www.baidu.com/">百度一下</a>';
//分组命名
const reg = /<a href="(?<url>.*)">(?<text>.*)<\/a>/;
const result = reg.exec(str);
console.log(result.groups.url);// https://www.baidu.com/
console.log(result.groups.text);// 百度一下
正则表达式反向断言
ES9
支持反向断言,通过对匹配结果前面的内容进行判断,对匹配进行筛选。
//声明字符串
let str = 'JS5211314你知道么555啦啦啦';
//正向断言
const reg = /\d+(?=啦)/;
const result = reg.exec(str);// ["555"]
//反向断言
const reg = /(?<=么)\d+/;
const result = reg.exec(str);// ["555"]
正则表达式 dotAll 模式
正则表达式中点.
匹配除回车外的任何单字符,标记『s』
改变这种行为,允许行
终止符出现
let str = `
<ul>
<li>
<a>肖生克的救赎</a>
<p>上映日期: 1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期: 1994-07-06</p>
</li>
</ul>`;
//声明正则
// const reg = /<li>\s+<a>(.*?)<\/a>\s+<p>(.*?)<\/p>/;
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;
//执行匹配
// const result = reg.exec(str);
let result;
let data = [];
while(result = reg.exec(str)){
data.push({title: result[1], time: result[2]});
}
//输出结果
console.log(data);
正则表达式 Unicode 转义
到目前为止,在正则表达式中本地访问 Unicode
字符属性是不被允许的。ES2018
添加了 Unicode
属性转义——形式为\p{...}
和\P{...}
,在正则表达式中使用标记 u (unicode)
设置,在\p
块儿内,可以以键值对的方式设置需要匹配的属性而非具体内容。例如:
/^\p{White_Space}+$/u.test(' ') // 空格
// true
/^\p{Script=Greek}+$/u.test('μετά') // 希腊字母
// true
/^\p{Script=Latin}+$/u.test('Grüße') // 匹配拉丁字母
// true
/^\p{Surrogate}+$/u.test('\u{D83D}') // 匹配单独的替代字符
// true
此特性可以避免使用特定 Unicode 区间来进行内容类型判断,提升可读性和可维护性。
非转义序列的模板字符串
ES2018
移除对 ECMAScript
在带标签的模版字符串中转义序列的语法限制。
之前,\u
开始一个 unicode
转义,\x
开始一个十六进制转义,\
后跟一个数字开始一个八进制转义。这使得创建特定的字符串变得不可能,例如Windows文件路径 C:\uuu\xxx\111
。
ES10新特性(2019)
更加友好的 JSON.stringify
根据标准,JSON 数据必须是 UTF-8 编码。但是,现在的JSON.stringify()方法有可能返回不符合 UTF-8 标准的字符串。
具体来说,UTF-8 标准规定,0xD800到0xDFFF之间的码点,不能单独使用,必须配对使用。比如,\uD834\uDF06是两个码点,但是必须放在一起配对使用,代表字符?。这是为了表示码点大于0xFFFF的字符的一种变通方法。单独使用\uD834和\uDFO6这两个码点是不合法的,或者颠倒顺序也不行,因为\uDF06\uD834并没有对应的字符。
JSON.stringify()的问题在于,它可能返回0xD800到0xDFFF之间的单个码点。
JSON.stringify('\u{D834}') // "\u{D834}"
为了确保返回的是合法的 UTF-8 字符,ES2019 改变了JSON.stringify()的行为。如果遇到0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理。
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
console.log(JSON.stringify('𝌆'))// "𝌆"
console.log(JSON.stringify('\uD834\uDF06'))// "𝌆"
Object.fromEntries
//Map
const m = new Map();
m.set('name','ATGUIGU');
const result = Object.fromEntries(m);
console.log(result); // {name: "ATGUIGU"}
//Object.entries ES8
const arr = Object.entries({
name: "测试"
})
console.log(arr);// ["测试"]
trimStart 和 trimEnd
// trim
let str = ' iloveyou ';
console.log(str);// iloveyou
console.log(str.trimStart());//iloveyou 去除字符串头部的空格
console.log(str.trimEnd());// iloveyou 去除字符串尾部的空格
Array.prototype.flat 与 flatMap
//flat 平
//将多维数组转化为低维数组
const arr1 = [1,2,3,4,[5,6]];
const arr2 = [1,2,3,4,[5,6,[7,8,9]]];
//参数为深度 是一个数字
console.log(arr1.flat(2)); // [1,2,3,4,5,6]
console.log(arr2.flat(2)); // [1,2,3,4,5,6,7,8,9]
//flatMap
const arr = [1,2,3,4];
const result = arr.flatMap(item => [item * 10]);
console.log(result); // [10, 20, 30, 40]
Symbol.prototype.description
//创建 Symbol
let s = Symbol('测试');
console.log(s.description);// 测试
BigInt:任意精度整数
BigInt
是第七种 原始类型。
BigInt
是一个任意精度的整数。这意味着变量现在可以 表示²⁵³
数字,而不仅仅是9007199254740992
。
const b = 1n; // 追加 n 以创建 BigInt
在过去,不支持大于 9007199254740992
的整数值。如果超过,该值将锁定为 MAX_SAFE_INTEGER + 1
:
const limit = Number.MAX_SAFE_INTEGER;
⇨ 9007199254740991
limit + 1;
⇨ 9007199254740992
limit + 2;
⇨ 9007199254740992 <--- MAX_SAFE_INTEGER + 1 exceeded
const larger = 9007199254740991n;
⇨ 9007199254740991n
const integer = BigInt(9007199254740991); // initialize with number
⇨ 9007199254740991n
const same = BigInt("9007199254740991"); // initialize with "string"
⇨ 9007199254740991n
typeof
typeof 10;
⇨ 'number'
typeof 10n;
⇨ 'bigint'
等于运算符可用于两种类型之间比较
10n === BigInt(10);
⇨ true
10n == 10;
⇨ true
数学运算符只能在自己的类型中工作
200n / 10n
⇨ 20n
200n / 20
⇨ Uncaught TypeError:
Cannot mix BigInt and other types, use explicit conversions <
-运算符可以操作, + 不可用
-100n
⇨ -100n
+100n
⇨ Uncaught TypeError:
Cannot convert a BigInt value to a number
ES11新特性(2020)
String.prototype.matchAll
matchAll()
方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';
const array = [...str.matchAll(regexp)];
console.log(array[0]);
// Array ["test1", "e", "st1", "1"]
console.log(array[1]);
// Array ["test2", "e", "st2", "2"]
语法:str.matchAll(regexp)
参数:regexp
正则表达式对象。如果所传参数不是一个正则表达式对象,则会隐式地使用 new RegExp(obj)
将其转换为一个 RegExp
。
RegExp必须是设置了全局模式g的形式,否则会抛出异常TypeError。
返回值:一个迭代器(不可重用,结果耗尽需要再次调用方法,获取一个新的迭代器)。
在 matchAll
出现之前,通过在循环中调用regexp.exec
来获取所有匹配项信息(regexp
需使用/g标志):
const regexp = RegExp('foo*','g');
const str = 'table football, foosball';
while ((matches = regexp.exec(str)) !== null) {
console.log(`Found ${matches[0]}. Next starts at ${regexp.lastIndex}.`);
// Found foo. Next starts at 9.
// Found foo. Next starts at 19.
}
let str = `<ul>
<li>
<a>肖生克的救赎</a>
<p>上映日期: 1994-09-10</p>
</li>
<li>
<a>阿甘正传</a>
<p>上映日期: 1994-07-06</p>
</li>
</ul>`;
//声明正则
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/sg
//调用方法
const result = str.matchAll(reg);
// for(let v of result){
// console.log(v);
// }
const arr = [...result];
console.log(arr);
类的私有属性
class Person{
//公有属性
name;
//私有属性
#age;
#weight;
//构造方法
constructor(name, age, weight){
this.name = name;
this.#age = age;
this.#weight = weight;
}
intro(){
console.log(this.name);
console.log(this.#age);
console.log(this.#weight);
}
}
//实例化
const girl = new Person('晓红', 18, '45kg');
console.log(girl.name);
console.log(girl.#age);
console.log(girl.#weight);
我们创建一个Person
类,并且已初始化,接下来输出该类对应的属性,输出后发现报错。
接下来我们把上面代码中的三个输出项注释,调用类里面的intro()
方法,发现可以正常输出。
//实例化
const girl = new Person('晓红', 18, '45kg');
girl.intro();
这说明类定义的私有属性,不可在类的外面调用,只能在该类的内部进行使用。
Promise.allSettled
//声明两个promise对象
const p1 = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('商品数据 - 1');
},1000)
});
const p2 = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('商品数据 - 2');
// reject('出错啦!');
},1000)
});
//调用 allsettled 方法
const result = Promise.allSettled([p1, p2]);
const res = Promise.all([p1, p2]);
console.log(result);
console.log(res);
再将上面两个promise
对象中的一个返回不成功时(修改p2
),我们再观察一下输出结果:
//声明两个promise对象
const p1 = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('商品数据 - 1');
},1000)
});
const p2 = new Promise((resolve, reject)=>{
setTimeout(()=>{
//resolve('商品数据 - 2');
reject('出错啦!');
},1000)
});
//调用 allsettled 方法
const result = Promise.allSettled([p1, p2]);
const res = Promise.all([p1, p2]);
console.log(result);
console.log(res);
根据上面的例子,声明两个promise
对象都是resolve
时输出,以及其中有reject
时的输出对比,用Promise.allSettled()
和Promise.all()
接收这两个promise
对象,对比这两个函数的输出结果,我们可以得出如下结论:
对于包装实例本身
相同点:Promise.all()
与Promise.allSettled()
方法用于将多个 Promise 实例,包装成一个新的 Promise
实例。
不同点:
Promise.allSettled()
不管参数中的promise
是fulfilled
还是rejected
,都会等参数中的实例都返回结果,包装实例才会结束。Promise.all()
中的参数的状态都变成fulfilled
,返回的结果状态才会变成fulfilled
;如果参数之中有一个被rejected
,返回结果的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给结果的回调函数。
返回的数据结构
Promise.allSettled()
:回调接收的结果与入参时的promise
实例一一对应,且结果的每一项都是一个对象,告诉你结果和值,对象内都有一个属性叫“status
”,用来明确知道对应的这个promise
实例的状态(fulfilled
或rejected
),fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应状态的返回值。
适用场景:promise.allSettled()
不论接受入参的promise
本身的状态,会返回所有promise
的结果,但这一点Promise.all
做不到,如果你需要知道所有入参的异步操作的所有结果,或者需要知道这些异步操作是否全部结束,应该使用promise.allSettled()
。如多张图片选择后一起上传时需判断多张图片的异步上传结果,成功了几张,失败了几张,这时我们就可以选择用promise.allSettled()
。
Promise.all()
:只有当所有入参的promise
实例都是fulfilled
状态,才会在Promise.all().then()
方法中结果,返回结果也是与入参一一对应,结果中只包含实际的resolve
的结果,不包含类似allSettled
的status
和value
属性。
可选链操作符
// ?.
function main(config){
// const dbHost = config && config.db && config.db.host; 等价于下面的代码
const dbHost = config?.db?.host;
console.log(dbHost);//4-可选链操作符.html:15 192.168.1.100
}
main({
db: {
host:'192.168.1.100',
username: 'root'
},
cache: {
host: '192.168.1.200',
username:'admin'
}
})
动态 import 导入
需要的时候才引入js
// demo.js
export default hello(){
console.log("hello world")
}
// 在需要的地方动态引入
btn.onclick = function(){
import('./demo.js').then(module=>{
module.hello()
})
}
//或者如下动态引入
<script src="./demo.js" type="module"></script>
globalThis 对象
在哪里使用都是全局对象。
JavaScript
语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。
-
浏览器里面,顶层对象是
window
,但Node
和Web Worker
没有window
。 -
浏览器和
Web Worker
里面,self
也指向顶层对象,但是Node
没有self
。 -
Node
里面,顶层对象是global
,但其他环境都不支持。 同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this
变量,但是有局限性。 -
全局环境中,
this会
返回顶层对象。但是,Node.js
模块中this
返回的是当前模块,ES6
模块中this
返回的是undefined
。 -
函数里面的
this
,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this
会指向顶层对象。但是,严格模式下,这时this
会返回undefined
。 -
不管是严格模式,还是普通模式,
new Function('return this')()
,总是会返回全局对象。但是,如果浏览器用了CSP(Content Security Policy,内容安全策略)
,那么eval、new Function
这些方法都可能无法使用。 综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
ES2020
在语言标准的层面,引入globalThis
作为顶层对象。也就是说,任何环境下,globalThis
都是存在的,都可以从它拿到顶层对象,指向全局环境下的this
。
ES12新特性(2021)
String.prototype.replaceAll(searchValue, replaceValue)
看到replaceAll
这个词,相比很容易联想到replace
。在JavaScript
中,replace
方法只能是替换字符串中匹配到的第一个实例字符,而不能进行全局多项匹配替换,唯一的办法是通过正则表达式进行相关规则匹配替换。
而replaceAll
则是返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉,替换规则可以是字符串或者正则表达式。
let string = 'I like 前端,I like 前端公虾米'
//使用replace
let replaceStr = string.replace('like','love')
console.log(replaceStr) // 'I love 前端,I like前端公虾米'
//replace使用正则匹配所有
console.log(string.replace(/like/g,'love')) // 'I love 前端,I love 前端公虾米'
//使用replaceAll
let replaceAllStr = string.replaceAll('like','love')
console.log(replaceAllStr) // 'I love 前端,I love 前端公虾米'
需要注意的是,
replaceAll
在使用正则表达式的时候,如果非全局匹配(/g),则replaceAll()
会抛出一个异常
let string = 'I like 前端,I like 前端公虾米'
console.log(string.replaceAll(/like/,'love')) //TypeError
Promise.any
当
Promise
列表中的任意一个promise
成功resolve
则返回第一个resolve
的结果状态; 如果所有的promise
均reject
,则抛出异常表示所有请求失败。
Promise.any([
new Promise((resolve, reject) => setTimeout(reject, 500, '哎呀,我被拒绝了')),
new Promise((resolve, reject) => setTimeout(resolve, 1000, '哎呀,她接受我了')),
new Promise((resolve, reject) => setTimeout(resolve, 2000, '哎呀,她也接受我了')),
])
.then(value => console.log(`输出结果: ${value}`))
.catch (err => console.log(err))
//输出
//输出结果:哎呀,她接受我了
再来看下另一种情况
Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2'),
Promise.reject('Error 3')
])
.then(value => console.log(`请求结果: ${value}`))
.catch (err => console.log(err))
//输出
AggregateError: All promises were rejected
Promise.any
与Promise.race
十分容易混淆,务必注意区分,Promise.race
一旦某个promise
触发了resolve
或者reject
,就直接返回了该状态结果,并不在乎其成功或者失败。
WeakRefs
使用
WeakRefs
的Class
类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)
当我们通过(const
、let
、var
)创建一个变量时,垃圾收集器GC将永远不会从内存中删除该变量,只要它的引用仍然存在可访问。WeakRef
对象包含对对象的弱引用。对对象的弱引用是不会阻止垃圾收集器GC恢复该对象的引用,则GC可以在任何时候删除它。
WeakRefs
在很多情况下都很有用,比如使用Map
对象来实现具有很多需要大量内存的键值缓存,在这种情况下最方便的就是尽快释放键值对占用的内存。
目前,可以通过WeakMap()
或者WeakSet()
来使用WeakRefs
举个栗子
我想要跟踪特定的对象调用某一特定方法的次数,超过1000条则做对应提示
let map = new Map()
function doSomething(obj){
...
}
function useObject(obj){
doSomething(obj)
let called = map.get(obj) || 0
called ++
if(called>1000){
console.log('当前调用次数已经超过1000次了,over')
}
map.set(obj, called)
}
如上虽然可以实现我们的功能,但是会发生内存溢出,因为传递给doSomething
函数的每个对象都永久保存在map
中,并且不会被GC回收,因此我们可以使用WeakMap
let wmap = new WeakMap()
function doSomething(obj){
...
}
function useObject(obj){
doSomething(obj)
let called = wmap.get(obj) || 0
called ++
if(called>1000){
console.log('当前调用次数已经超过1000次了,over')
}
wmap.set(obj, called)
}
因为是弱引用,所以WeakMap
、WeakSet
的键值对是不可枚举的
WeakSet
和WeakMap
相似,但是每个对象在WeakSet
中的每个对象只可能出现一次,WeakSet
中所有对象都是唯一的
let ws = new WeakSet()
let foo = {}
let bar = {}
ws.add(foo)
ws.add(bar)
ws.has(foo) //true
ws.has(bar) //true
ws.delete(foo) //删除foo对象
ws.has(foo) //false 已删除
ws.has(bar) //仍存在
WeakSet
与Set
相比有以下两个区别
WeakSet
只能是对象集合,而不能是任何类型的任意值;
WeakSet
弱引用,集合中对象引用为弱引用,如果没有其他对WeakSet
对象的引用,则会被GC回收,最后,WeakRef
实例有一个方法deref
,返回引用的原始对象,如果原始对象被回收,则返回undefined
const cache = new Map();
const setValue = (key, obj) => {
cache.set(key, new WeakRef(obj));
};
const getValue = (key) => {
const ref = cache.get(key);
if (ref) {
return ref.deref();
}
};
const fibonacciCached = (number) => {
const cached = getValue(number);
if (cached) return cached;
const sum = calculateFibonacci(number);
setValue(number, sum);
return sum;
};
对于缓存远程数据来说,这可能不是一个好主意,因为远程数据可能会不可预测地从内存中删除。在这种情况下,最好使用LRU之类的缓存。
逻辑运算符和赋值表达式
逻辑运算符和赋值表达式,新特性结合了逻辑运算符(&&,||,??)和赋值表达式而JavaScript已存在的复合赋值运算符有:
-
操作运算符:+=、 -= 、*= 、/= 、%= 、**=
-
位操作运算符:&=、 ^=、 |=、
-
按位运算符:<<=、 >>=、 >>>=
现有的的运算符,其工作方式都可以如此来理解
表达式:a op= b
等同于:a = a op b
逻辑运算符和其他的复合赋值运算符工作方式不同
表达式:a op= b
等同于:a = a op (a = b)
a ||= b
//等价于
a = a || (a = b)
a &&= b
//等价于
a = a && (a = b)
a ??= b
//等价于
a = a ?? (a = b)
为什么不再是跟以前的运算公式a = a op b
一样呢,而是采用a = a op (a = b)
。因为后者当且仅当a
的值为false
的时候才计算赋值,只有在必要的时候才执行分配,而前者的表达式总是执行赋值操作。
??=
可用来补充/初始化缺失的属性
const pages = [
{
title:'主会场',
path:'/'
},
{
path:'/other'
},
...
]
for (const page of pages){
page.title ??= '默认标题'
}
console.table(pages)
//(index) title path
//0 "主会场" "/"
//1 "默认标题" "/other"
小结: &&=:当LHS值存在时,将RHS变量赋值给LHS ||=:当LHS值不存在时,将RHS变量赋值给LHS ??= :当LHS值为null或者undefined时,将RHS变量赋值给LHS
数字分隔符
数字分隔符,可以在数字之间创建可视化分隔符,通过_下划线来分割数字,使数字更具可读性
const money = 1_000_000_000
//等价于
const money = 1000000000
const totalFee = 1000.12_34
//等价于
const totalFee = 1000.1234
该新特性同样支持在八进制数中使用
const number = 0o123_456
//等价于
const number = 0o123456