实际上JavaScript是ECMAScript的扩展语言,ECMAScript只提供了最基本的语法。2015年开始ES保持每年一个版本的迭代。ECMAScript2015也叫es6,开发者喜欢用es6泛指所有新标准
1、let与块级作用域
块级作用域,凡是带{}的都是一个作用域
1、在内部定义的变量a,在外部依旧可以访问
if(true){
var a = '123'
}
console.log(a) // 123
2、作用域内部重新定义外部变量,则不会对外部变量进行修改,循环体有两层嵌套的作用域,
if(true){
let a = '123'
console.log(a) //可以访问到
}
console.log(a) // 报错 访问不到
for (var i = 0;i<3;i++) {
for(var i = 0;i<3;i++){
console.log(i) // 1,2,3 因为内层循环结束后i是3影响外面的i 所以外层退出循环,只执行一次,解决办法var改成let
}
}
2、const
用来声明常量,相比于let增加了只读属性。变量声明之后不可以被更改。
1、const生成的常量不允许被修改
const a = 'test'
a = 'test2' //不允许被修改
2、const生成的对象不允许修改指向的内存地址,但是可以修改内部属性
const obj = {}
obj.name = '123' //内部属性允许修改
console.log(obj)
obj = {} //指向的内存地址不允许修改,报错
项目中尽量不用var,避免变量提升以及垃圾作用域,主用const(默认全部使用const),配合使用let(如果值需要修改,则使用let)
手写实现一个const功能
var __const = function __const (data, value) {
window.data = value // 把要定义的data挂载到window下,并赋值value
Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
enumerable: false,
configurable: false,
get: function () {
return value
},
set: function (data) {
if (data !== value) { // 当要对当前属性进行赋值时,则抛出错误!
throw new TypeError('Assignment to constant variable.')
} else {
return value
}
}
})
}
__const('a', 10)
console.log(a)
delete a
console.log(a)
for (let item in window) { // 因为const定义的属性在global下也是不存在的,所以用到了enumerable: false来模拟这一功能
if (item === 'a') { // 因为不可枚举,所以不执行
console.log(window[item])
}
}
a = 20 // 报错
3、解构赋值
ES6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这个过程被称之为解构
//完全解构
const arr = [1,2,3]
const [arr1,arr2,arr3,arr4] = arr
const [arr1,arr2,arr3,arr4 = '123'] = arr //设置默认值,如果取不到值,则会使用默认值
console.log(arr1,arr2,arr3) //1 2 3
console.log(arr4)//undefined,就像是访问数组中不存在的下标
const str = 'foo/boo/too'
const [a,b,c] = str.split("/")
log(a,b,c) // foo boo too
不完全解构,数组中的部分值对应到相应的变量,不取模式匹配的值需要使用逗号隔开
//不完全解构
const arr = [1,2,3]
const[,,arr4] = arr
console.log(arr4) // 3
对象的解构,没有的值也会打印undefined,也可设置默认值
//对象解构
const obj = { name:'test', age:18}
const {name} = obj
console.log(name)
可设置别名
const {name:myName} = obj //myName,匹配完name以后将值赋值给myName,
console.log(myName) // test
4、字符串模板
const name = 'tom'
const str = `name is ${name}`
console.log(str) // name is tom
const str1 = `运算:${1+1}`
console.log(str1) // 2
字符串的扩展方法startWith、endsWith、includes
const message = 'my name is tom'
console.log(
message.startsWith('my'), //true
message.endsWith('123'), //false
message.includes('is') //true
)
5、默认参数
function foo(str,name = 123){ //直接在name参数后添加默认值123,注意:设置默认值的参数一定要放到最后
console.log(str,name)
}
foo('str')
foo('str','hello')
6、剩余参数
function foo(...arg){
console.log(arg) // [1,2,3,4]
}
foo(1,2,3,4)
7、展开数组
在这里插入代码片const arr = [1,2,3,4]
console.log(...arr) //1 2 3 4
8、箭头函数
出现的作用除了让函数的书写变得很简洁,可读性很好外;最大的优点是解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题
const add1 = (n1,n2) => n1+n2
console.log(add1(3,4)) // 7
9、箭头函数和this对象
this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this指向的是window;当函数被作为某个对象的方法调用时,this就等于那个对象
函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。箭头函数外面的 this 是什么,箭头函数内的 this 还是什么。
10、对象字面量的增强
const bar = 'bar'
const obj = {
name:123,
bar, //等同于:bar:'bar'
myLog(){
console.log(this) //指向当前对象obj
},
[1+1+'str']:'12' //等同于 str2:‘12’,[]中可计算,计算的结果 作为属性名
}
11、Object.assign()
const target = {a:456,c:789}
const source1 = {a:123,b:456}
const result = Object.assign(target,source1) //有返回值,返回值与第一个参数target相同
console.log(target) //{ a: 123, c: 789, b: 456 }
console.log(result === target) // true
console.log(result === source1) // false
12、Object.is()
比较两个值是否完全一致
console.log(NaN===NaN)//false
console.log(+0===-0) //true
console.log(Object.is(NaN,NaN)) //true
console.log(Object.is(+0,-0)) //false
13、Proxy
const person = { name:'test',age:12} //定义
const personProxy = new Proxy(person,{
get(target,property){
return property in target ? target[property]:'default' //返回值
},
set(target,property,value){
if(property === 'age'){
if(!Number.isInteger(value)){
throw new TypeError("age not a num") //可以对传递的数据进行校验
}
}
target[property] = value
console.log(target,property,value)
}
})
console.log(personProxy.name) //test
console.log(personProxy.qq) //default
personProxy.name='hello'
personProxy.age='12'
Proxy相对于Object.defineProperty有什么优势呢
- 优势1:Proxy 可以监视读写以外的操作 比如:删除
const person = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person, {
deleteProperty (target, property) {
console.log('delete', property) // age
delete target[property]
}
})
delete personProxy.age
console.log(person)
// person = {
// name: 'zce',
// }
- 优势2:Proxy 可以很方便的监视数组操作
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value) //
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
- 优势3:Proxy 不需要侵入对象
// defineProperty方式
const person = {}
Object.defineProperty(person, 'name', {
get () {
console.log('name 被访问')
return person._name
},
set (value) {
console.log('name 被设置')
person._name = value
}
})
Object.defineProperty(person, 'age', {
get () {
console.log('age 被访问')
return person._age
},
set (value) {
console.log('age 被设置')
person._age = value
}
})
person.name = 'jack'
console.log(person.name)
//Proxy 方式更为合理
const person2 = {
name: 'zce',
age: 20
}
const personProxy = new Proxy(person2, {
get (target, property) {
console.log('get', property)
return target[property]
},
set (target, property, value) {
console.log('set',target, property, value)
target[property] = value
}
})
personProxy.name = 'jack'
console.log(personProxy.name)
14、Reflect
静态类,通过类型.方法名调用,Reflect成员方法就是Proxy处理对象的默认实现
const myObj = {
name:'Tom',
age:18
}
console.log(
Reflect.has(myObj,'name'), // true
Reflect.deleteProperty(myObj,'name'), // true
Reflect.ownKeys(myObj) // ['age']
)
15、构造函数
//es5
function Person (name) {
this.name = name
}
Person.prototype.say = function () {
console.log(`hi, my name is ${this.name}`)
}
// es6
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
const p = new Person('tom')
p.say()
16、静态方法
static logMethond(){
console.log('我是静态方法')
}
Person.logMethods() // ‘我是静态方法’
17、类的继承
// extends 继承
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
class Student extends Person {
constructor (name, number) {
super(name) // 父类构造函数
this.number = number
}
hello () {
super.say() // 调用父类成员
console.log(`my school number is ${this.number}`)
}
}
const s = new Student('jack', '100')
s.hello()
18、set
Set是一种数据结构,用来存放不重复的数据,常用来去重
- 创建与添加元素,add()方法返回一个set对象,所以可以链式调用add
const set = new Set()
set.add(1).add(2).add(3).add(4) //添加元素
- 遍历
//第一种方式
set.forEach(element => {
console.log(element)
});
//第二种方式
for(let i of set){
console.log(i)
}
- 获取长度,size
console.log(set.size)
- 判断是否含有某个元素,has
console.log(set.has(1)) // true
```js
5. **删除某个元素,delete**
```js
set.delete(1)
- 清除集合中的数据,clear
set.clear()
- set与array的转化
//将set转化为数组
let arr = [...set]
let arr1 = Array.from(set)
//将数组转化为set
let arr = [1,2,3,4]
let set1 = new Set(arr)
console.log(set1)
18、WeakSet
WeakSet只能存放对象类型,不能存放基础类型;WeakSet对元素引用是弱引用,没有其他引用的会被回收,比如当 对象 = null。这也意味着WeakSet中没有存储当前对象的列表。 正因为这样,WeakSet 是不可枚举的
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
WeakSet 的成员只能是对象,而不能是其他类型的值
let weak = new WeakSet();
weak.add(1);//Uncaught TypeError: Invalid value used in weak
正如它的名字weak,它是对象的弱引用,垃圾回收机制不考虑,所以不会造成内存的泄露,同时是无法遍历的,因为你不知道什么时候对象的引用消失了,而且没有size属性,不知道长度,可以试试以下代码在浏览器的输出
let weak = new WeakSet();
weak.add({});
weak.add([]);
let timeId = setInterval(() => {
console.log(weak);
}, 1000);
19、Map
Map是一种数据结构
let m = new Map() //创建实例
m.set("name","tom") //存储值
console.log(m.get("name"))//获取name
console.log(m.has('name'))//是否有name键
m.clear()//清空整个map
m.forEach((value,key)=>{ //遍历
console.log(key+":"+value)
})
可迭代
19、WeakMap
弱映射,是一种增强的键值对存储机制。(weak描述的是JavaScript垃圾回收程序对待“若映射”中键的方式)
因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象就好被当做垃圾回收。然后这个键值对就从弱映射中消失了,然后形成一个空映射
const wm = new WeakMap()
const container = {
key : {}
}
wm.set(container.key,"val")
function removeReference(){
container.key = null
}
使用场景:访问计数器
需要注意的地方:
(1)不可迭代枚举,因为我们不知道何时垃圾回收器会移除这个对象。
(2)weakmap对象是不可枚举的,无法获取大小
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
20、Symbol
- Symbol不是一个构造函数,不能使用new来创建
const symbol = Symbol()
console.log(symbol) //Symbol()
- Symbol可以传递一个字符串参数,参数作用是对symbol类型的描述,便于区分这个symbol是哪一个,
const s1 = Symbol('foo')
const s2 = Symbol('bar')
console.log(s1,s2) // Symbol(foo) Symbol(bar)
- Symbol类型的值具有唯一性,是一个独一无二的值,每一个 Symbol 的值都不相等。相同参数 Symbol() 返回的值不相等
const s1 = Symbol('foo')
const s2 = Symbol('foo')
console.log(s1===s2) // false
- Symbol私有变量
const name = Symbol()
const obj = {
[name]:'123',
sayHi(){
console.log(this[name])
}
}
console.log(obj.name) //不能直接访问
obj.sayHi() //只能调用其它非私有的方法
- for in 获取不到symbol值
for(let key in obj){
console.log(key)
} //sayHi
- keys也获取不到值
console.log(Object.keys(obj)) // ['sayHi']
- 补充
复用symbol值,因为symbol产生的值不是唯一的,这里提供了一个symbol.for方法
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
// 会把字符串类型的值 转换成 boolean类型值
console.log(Symbol.for(true) === Symbol.for('true'))
// 如果我们想自定义标签[object object]
//const obj = {}
//console.log(obj.toString()) //[object object]
// 如果我们想自定义标签
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString()) //[object XObject]
通过getOwnPropertySymbols获取symbol属性名
const obj = {
[Symbol('str')]: 'symbol value',
foo: 'normal value'
}
console.log(Object.getOwnPropertySymbols(obj))
//[ Symbol('str') ]
获取Symbol属性方法
console.log(Object.getOwnPropertySymbols(obj))
21、for…of循环
(1).遍历数组 和forEach对比
//数组遍历
const arr = [1,2,3,4,5]
for (const item of arr) {
console.log(item) // 1,2,3,4,5
if(item > 2) {
break; // 可以终止循环
}
}
arr.forEach() // 不能跳出循环
(2)遍历set集合
const set = new Set([1,2,3,4,5])
for (const item of set) {
console.log(item)//1,2,3,4,5
}
(3)遍历Map集合
let map = new Map()
map.set("name","Tom")
map.set("age",12)
for (const [key,value] of map) {
console.log(key+":"+value)
}
(4)for of 内部实现原理 数组集合原型对象上都有Symbol.iterator方法,调用iterator方法得到一个迭代器,从而遍历内部所有的数据。所以只有实现了Iterator接口才可以进行for…of的遍历
const arr = [1,2,3]
const iterator = arr[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
(5) ES2015可迭代接口实现
const obj = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function() {
let index = 0
const self = this
return {
next: function() {
const result = {
value: self.store[index],
done: index >= self.store.length
}
index++
return result
}
}
}
}
for (const item of obj) {
console.log(item) // foo、bar、baz
}
(6) 用下面例子 看看可迭代接口作用
现在有一个对象
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
}
我们想遍历对象里面 生活类、学习类内容,通常会这样,逐个数组遍历
for (const item of todos.life) {
console.log(item)
}
for (const item of todos.learn) {
console.log(item)
}
for (const item of todos.work) {
console.log(item)
}
我们也可以提供统一遍历访问接口
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
// 提供统一遍历访问接口
each: function (callback) {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
callback(item)
}
},
}
todos.each(function (item) {
console.log(item)
})
提供迭代器(ES2015 统一遍历访问接口)
// 迭代器设计模式
// 场景:你我协同开发一个任务清单应用
// 我的代码 ===============================
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
// 提供迭代器(ES2015 统一遍历访问接口)
[Symbol.iterator]: function () {
const all = [...this.life, ...this.learn, ...this.work]
let index = 0
return {
next: function () {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
for (const item of todos) {
console.log(item)
}
这样你不用关心对象的内部结构 是怎样的!对外提供统一接口
22、生成器函数
function * foo () {
console.log('1111')
yield 100
console.log('2222')
yield 200
console.log('3333')
yield 300
}
const generator = foo()
console.log(generator.next()) // 第一次调用,函数体开始执行,遇到第一个 yield 暂停
console.log(generator.next()) // 第二次调用,从暂停位置继续,直到遇到下一个 yield 再次暂停
console.log(generator.next()) // 。。。
console.log(generator.next()) // 第四次调用,已经没有需要执行的内容了,所以直接得到 undefined
用生成器函数实现iterator方法
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
[Symbol.iterator]: function * () {
const all = [...this.life, ...this.learn, ...this.work]
for (const item of all) {
yield item
}
}
}
for (const item of todos) {
console.log(item)
}
22、迭代器和异步迭代器
什么是同步迭代器呢?
例子:
var obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
var iterator = obj[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}
这里的 iterator 就是同步迭代器了,每调用一次 next 方法,就返回一个 { value: xx, done: xx } 形式的对象。
什么是异步迭代器呢?
再举个例子:
var obj = {
async *[Symbol.asyncIterator]() {
yield 1;
yield 2;
yield 3;
}
}
var asyncIterator = obj[Symbol.asyncIterator]()
asyncIterator.next().then(data => console.log(data)) // {value: 1, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 2, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: 3, done: false}
asyncIterator.next().then(data => console.log(data)) // {value: undefined, done: true}
同步迭代器数据如果即用即给,处理顺序等于遍历顺序。
var obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for (let item of obj) {
console.log(item) // 1 -> 2 -> 3。处理顺序等于遍历顺序
}
再来看看异步迭代器
var obj = {
async *[Symbol.asyncIterator]() {
yield new Promise(resolve => setTimeout(() => resolve(1), 5000));
yield new Promise(resolve => setTimeout(() => resolve(2), 3000));
yield new Promise(resolve => setTimeout(() => resolve(3), 500));
}
}
console.log(Date.now())
for await (let item of obj) {
console.log(Date.now(), item)
}
// 1579256590699
// 1579256595700 1 // 1579256595700 - 1579256590699 = 5001
// 1579256598702 2 // 1579256598702 - 1579256590699 = 8003
// 1579256599203 3 // 1579256599203 - 1579256590699 = 8504
for-await-of 除了能用在异步可迭代对象上,还能用在同步可迭代对象上。
var obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for await(let item of obj) {
console.log(item) // 1 -> 2 -> 3
}
22、生成器和迭代器区别
JavaScript中的生成器(Generator
)和迭代器(Iterator
)是两种不同的概念和用法。
生成器是一种特殊的函数,可以通过yield语句来暂停函数的执行,并且可以通过next()方法来恢复函数的执行。生成器函数可以返回一个可迭代对象,该对象可以用于循环遍历生成器函数中的yield语句返回的值。生成器函数可以用于惰性地生成一系列值,而不需要一次性生成所有值。生成器可以在需要的时候逐步生成值,这样可以节省内存和提高性能。
迭代器是一种对象,它定义了一个next()方法,用于返回序列中的下一个值。迭代器可以用于遍历一个序列,例如数组或者字符串。当调用next()方法时,迭代器会返回一个包含value和done属性的对象。value属性表示序列中的下一个值,done属性表示是否已经遍历完序列。迭代器可以用于实现自定义的可迭代对象,使其可以通过for…of循环来遍历。
总结来说,生成器是一种特殊的函数
,它可以通过yield语句来暂停和恢复函数的执行,并且可以生成一个可迭代对象。而迭代器是一种对象
,它定义了一个next()方法,用于遍历一个序列中的值。生成器可以用于惰性地生成一系列值,而迭代器可以用于遍历一个序列。
23、indexOf和includes方法的区别
indexOf方法查找不到NAN这样的数值,includes可以
const arr = ['foo', 1, NaN, false]
// 找到返回元素下标
console.log(arr.indexOf('foo')) // 1
// 找不到返回 -1
console.log(arr.indexOf('bar')) // -1
// 无法找到数组中的 NaN
console.log(arr.indexOf(NaN)) // -1 无法找到NAN
// 直接返回是否存在指定元素
console.log(arr.includes('foo')) // true
// 能够查找 NaN
console.log(arr.includes(NaN)) // true
24、指数运算符
// console.log(Math.pow(2, 10))
console.log(2 ** 10)
25、ECMAScript 2017
(1)Object.values(),返回一个对象所有value的数组
const obj = {
name:'tom',
age:12
}
console.log(Object.values(obj) // [ 'tom', 12 ]
(2)Object.entries(),返回属性值对应的数组
console.log(Object.entries(obj)) // [ [ 'name', 'tom' ], [ 'age', 12 ] ]
for (const [key, value] of Object.entries(obj)) {
console.log(key, value)
}
const books = {
html: 5,
css: 16,
javascript: 128
}
for (const [name, count] of Object.entries(books)) {
console.log(name, count)
}
//html 5
//css 16
//javascript 128
for (const [name, count] of Object.entries(books)) {
console.log(`${name.padEnd(16, '-')}|${count.toString().padStart(3, '0')}`)
}
//html------------|005
//css-------------|016
//javascript------|128
(3)Object.assign()的弊端,只能复制属性值,方法不能被赋值,通过输出值可以看出,输出的fullName不是一个函数,而是一个固定的字符串
const p = {
firstName:'Lei',
lastName : 'Wang',
get fullName(){
return this.firstName+" "+this.lastName
}
}
console.log(p.fullName) // Lei Wang
let p2 = {}
Object.assign(p2,p)
console.log(p2) // { firstName: 'Lei', lastName: 'Wang', fullName: 'Lei Wang' }
(4)使用 Object.getOwnPropertyDescriptors与Object.defineProperties
const descriptor = Object.getOwnPropertyDescriptors(p)
console.log(descriptor)
const copyP = Object.defineProperties({},descriptor)
console.log(copyP) // { firstName: 'Lei', lastName: 'Wang', fullName: [Getter] }
copyP.firstName = 'qiuqiu'
console.log(copyP.fullName)
26、for await of
异步迭代器(for-await-of):循环等待每个Promise对象变为resolved状态才进入下一步。
我们知道 for…of 是同步运行的,看如下代码
function TimeOut(time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test() {
let arr = [TimeOut(2000), TimeOut(1000), TimeOut(3000)]
for (let item of arr) {
console.log(Date.now(), item.then(console.log))
}
}
test()
上面打印结果如下图
上述代码证实了 for of 方法不能遍历异步迭代器,得到的结果并不是我们所期待的,于是 for await of 就粉墨登场啦!
ES9 中可以用 for…await…of 的语法来操作
function TimeOut(time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test() {
let arr = [TimeOut(2000), TimeOut(1000), TimeOut(3000)]
for await (let item of arr) {
console.log(Date.now(), item)
}
}
test() // 1560092345730 2000// 1560092345730 1000// 1560092346336 3000复制代码
for await of 环等待每个Promise对象变为resolved状态才进入下一步。所有打印的结果为 2000,1000,3000
27、flat()
ES10
新增了flat()方法用于将嵌套的数组扁平化,并且还引入了flatMap()方法,它结合了map()和flat(),可以一次性对数组进行映射和扁平化操作。
按照一个可指定的深度递归遍历数组:
默认扁平化第一层
const arr1 = [0, 1, 2, [3, 4]];
document.getElementById("demo1").innerHTML = arr1.flat();
const arr2 = [0, 1, 2, [[[3, 4]]]];
document.getElementById("demo2").innerHTML = arr2.flat(2);
28、BigInt
ES2011
BigInt:引入了新的基本数据类型BigInt,用于表示任意精度的整数。BigInt可以表示比JavaScript中Number类型范围更大的整数,以及更高的精度。
29、Promise.allSettled()
ES11
首先通过 promise 对象实现 Ajax (如下) ,后面 在代码中 会使用到
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "text";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
Promise.allSettled()
有时候,我们希望等到一组异步操作都结束了,不管每一个操作是成功还是失败,再进行下一步操作。但是,现有的 Promise 方法很难实现这个要求。
Promise.all() 方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。 Promise.all()方法
const urls = [url_1, url_2, url_3];
const requests = urls.map(x => fetch(x));
try {
await Promise.all(requests);
console.log('所有请求都成功。');
} catch {
console.log('至少一个请求失败,其他请求可能还没结束。');
}
上面示例中,Promise.all()可以确定所有请求都成功了,但是只要有一个请求失败,它就会报错,而不管另外的请求是否结束。
为了解决这个问题,ES2020 引入了 Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了 ”fulfilled“和 ”rejected“两种情况。
Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是 rejected),返回的 Promise 对象才会发生状态变更。
const promises = [
getJSON('https://jsonplaceholder.typicode.com/users/1'),
getJSON('https://jsonplaceholder.typicode.com/uses/2'),
getJSON('https://jsonplaceholder.typicode.com/users/3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();
上面示例中,数组 promises包含了三个请求,只有等到这三个请求都结束了
(不管请求成功还是失败),removeLoadingIndicator()才会执行。
该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是 fulfilled,不会变成rejected。状态变成 fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
上面代码中,Promise.allSettled()的返回值 allSettledPromise,状态只可能变成 fulfilled。它的回调函数接收到的参数是数组 results。该数组的每个成员都是一个对象,对应传入 Promise.allSettled()的数组里面的两个 Promise 对象。
results的每个成员是一个对象,对象的格式是固定的,对应异步操作的结果。
// 异步操作成功时
{status: 'fulfilled', value: value}
// 异步操作失败时
{status: 'rejected', reason: reason}
成员对象的 status属性的值只可能是字符串 fulfilled或字符串 rejected,用来区分异步操作是成功还是失败。如果是成功(fulfilled),对象会有 value属性,如果是失败(rejected),会有 reason属性,对应两种状态时前面异步操作的返回值。
下面是返回值的用法例子。
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// 过滤出失败的请求,并输出原因
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);
30、Promise.any()
Promise.any():新增了Promise.any()方法,用于接收一个Promise数组,并在其中至少有一个Promise成功解决时返回一个新的Promise。与Promise.race()不同,Promise.any()会忽略失败的Promise,只要有一个成功即可。