Promise
-
Promise 是异步编程的一种解决方案,Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
-
Promise构造函数接受一个函数作为参数,该函数两个参数分别是resolve和reject,他们是两个函数
- resolve函数作用:把Promise对象的状态从未完成变成成功(pending=>resolved)
- reject函数作用:把promise对象的状态从未完成变成失败(pending=>rejected)
-
Promise.prototype.then
- then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
- then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
getJSON("/posts.json").then(function(json) { return json.post; }).then(function(post) { // ... });
-
Promise.prototype.catch()
- Promise.prototype.catch()方法是.
then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。 - 如果Promise实例对象抛出错误的话,也会走到catch方法的回调。如果.then()的处理出现异常也会走到catch回调方法中
- Promise.prototype.catch()方法是.
-
Promise.prototype.finally
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ); };
-
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。- Promise.all()方法接受一个具有 Iterator 接口的对象作为参数
const p = Promise.all([p1, p2, p3]); p的状态由p1、p2、p3决定,分成两种情况。 (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
-
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。const p = Promise.race([p1, p2, p3]); 上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。 下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve。
-
Promise.allSettled()
用来确定一组异步操作是否都结束了(不管成功或失败)。// Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。 const promises = [ fetch('/api-1'), fetch('/api-2'), fetch('/api-3'), ]; await Promise.allSettled(promises); removeLoadingIndicator(); // 该方法返回的新的 Promise 实例,一旦发生状态变更,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,它的回调函数会接收到一个数组作为参数,该数组的每个成员对应前面数组的每个 Promise 对象。
-
Promise.any()
只要有一个变成fulfilled,包装实例就会变成fulfilled状态或者所有如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。 -
Promise.resolve()
将现有对象转为 Promise 对象, Promise.resolve()方法就起到这个作用。分为四种情况
(1). 参数是一个 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
(2). 参数是一个thenable对象,(thenable对象
指的是具有then方法的对象)// Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。 let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function (value) { console.log(value); // 42 });
(3). 参数不是具有then()方法的对象,或根本就不是对象
// 如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。 const p = Promise.resolve('Hello'); p.then(function (s) { console.log(s) }); // Hello
(4). 不带有任何参数
// Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。 const p = Promise.resolve(); p.then(function () { // ... });
-
Promise.reject()
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。const p = Promise.reject('出错了'); // 等同于 const p = new Promise((resolve, reject) => reject('出错了')) p.then(null, function (s) { console.log(s) }); // 出错了
Iterator 和 for…of 循环
- 为了使不同数据结构的数据能统一操作,提供了遍历器(Iterator)就是这样一种机制。
- 它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
- Iterator 的作用有三个:一是
为各种数据结构,提供一个统一的、简便的访问接口
;二是使得数据结构的成员能够按某种次序排列
;三是 ES6 创造了一种新的遍历命令for...of
循环,Iterator 接口主要供for...of
消费。 - 原生具备Iterator 接口的数据(可用for of遍历)有:Array,arguments,set,map,string,typedArray,nodeList
- Iterator 的遍历过程是这样的。
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。 (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。 (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。 (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。 每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。
对象(Object)
之所以没有默认部署 Iterator 接口
,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。//声明一个对象 // object是不具有Iterator 接口的。但是我们可以手动添加一个Iterator 接口 // 下面的方法就是给obj添加一个Iterator 接口。让obj通过for of遍历 时,遍历的只是里面stus数组的数据 const obj = { name:"name", stus:["zhangsan","lisi","wangwu"], [Symbol.iterator](){ let index = 0; let _this = this; return{ next:function(){ if(index<_this.stus.length){ const result = {value:_this.stus[index],done:false}; index++ return result } else { return {value:undefined,done:true} } } } } }
调用 Iterator 接口的场合
- 解构赋值,对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
- 扩展运算符,也会调用默认的 Iterator 接口。
- yield*
let generator = function* () { yield 1; yield* [2,3,4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next() // { value: 2, done: false } iterator.next() // { value: 3, done: false } iterator.next() // { value: 4, done: false } iterator.next() // { value: 5, done: false } iterator.next() // { value: undefined, done: true } // next方法也是可以传参数的。next传递的参数时作为上一次yield 的返回结果 let generator = function* () { let one = yield 1; // 这个one就是第二次next('参数')的传递的参数 yield* [2,3,4]; yield 5; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next(2) // { value: 2, done: false } //上面的one就是next中的参数2
- 其他场合
for...of Array.from() Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])) Promise.all() Promise.race()
for of
与其他遍历语法的比较已数组为例for循环
(这种写法比较麻烦)
for (var index = 0; index < myArray.length; index++) { console.log(myArray[index]); }
forEach
无法中途跳出forEach循环,break命令或return命令都不能奏效
myArray.forEach(function (value) { console.log(value); });
for in
for (var index in myArray) { console.log(myArray[index]); } // 遍历得到的是索引 // 还会遍历手动添加的其他键,甚至包括原型链上的键。 // for...in循环会以任意顺序遍历键名。(不能保证顺序)
for of
没有for in
的缺点,且可以与break
、continue
和return
配合使用。
Generator 函数的语法
Generator 函数是 ES6 提供的一种异步编程解决方案
- 它的具体执行步骤如下:
- 执行 Generator 函数会返回一个遍历器对象(这个遍历器对象就是
Iterator
)可以依次遍历 Generator 函数内部的每一个状态。 - 得到这个遍历器对象后,下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。
- Generator 函数配合内部的yield语法使用
- yield表达式是暂停执行的标记,当使用next方法Ⅹ就是恢复执行
- 执行 Generator 函数会返回一个遍历器对象(这个遍历器对象就是
yield
-
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
-
遍历器对象的next方法的运行逻辑如下。
1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value属性值。(2)下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
表达式。(3)如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。(4)如果该函数没有
return
语句,则返回的对象的value
属性值为undefined。需要注意的是,
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
与 Iterator 接口的关系
- 任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
- 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
next()方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
for…of
- for…of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。
function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5 // 上面代码当执行到return 6的时候,next()的返回值为{value:6,done:true},此时for..of会中止.且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中。
- Generator.prototype.return
Generator 函数返回的遍历器对象,还有一个return()方法,可以返回给定的值,并且终结遍历 Generator 函数。 function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return('foo') // { value: "foo", done: true } g.next() // { value: undefined, done: true }
- Generator的一个使用
// 有三个异步操作。1秒后输出111,2秒后输入222 3秒后输入333.每一个都要在上一个执行后才执行下一个操作 // 通过回调地狱的方式写代码的话会有层层嵌套问题。可以使用Generator函数 function one(){ setTimeout(()=>{ console.log(111) iterator.next() },1000) } function two(){ setTimeout(()=>{ console.log(222) iterator.next() },2000) } function three(){ setTimeout(()=>{ console.log(333) },3000) } function * gen(){ yield one() yield two() yield three() } // 调用 let iterator = gen() // 传递参数的iterator 函数 function getUser(){ setTimeout(()=>{ let data = '用户数据' // next中的参数就是上一次yield的返回值 iterator.next(data) },3000) } function getOrder(user){ console.log(user) setTimeout(()=>{ let data = '订单数据' iterator.next(data) },3000) } function getGood(order){ console.log(order) setTimeout(()=>{ let data = '商品数据' },3000) } function * gen(){ const user = yield getUser() const order= yield getOrder(user) const good= yield getGood(order) } let iterator = gen()
async await
- 含义: async 函数是什么?一句话,它就是 Generator 函数的语法糖。
- 语法:
const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
- async函数对 Generator 函数的改进,体现在以下四点。
- 内置执行器。
async
函数的执行,与普通函数一模一样,只要一行。这完全不像Generator
函数,需要调用next
方法,或者用co模块,才能真正执行,得到最后结果。 - 更好的语义。
- 更广的适用性.
yield
命令后面只能是Thunk
函数或Promise
对象,而async
函数的await
命令后面,可以是Promise
对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即resolved
的Promise
对象)。 - 返回值是
Promise
。Generator
函数的返回值是Iterator
对象
- 内置执行器。
async
函数的实现原理(async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。)
class的基本语法
- ES6引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
class Point {
constructor(x, y) {
//this关键字则代表实例对象
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
- constructor()默认返回实例对象(即this)
- 通过class创建实例对象的时候必须new
- class中的getter和setter取值函数(getter)和存值函数(setter)
class MyClass { constructor() { // ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: '+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter'
- 通过 static 注册静态方法以及静态属性
class Foo { static classMethod() { // this.aaa = 'xxx' // 如果静态方法里面使用this,那这个this指的是类而不是实例 return 'hello'; } static name = 'xxx' } // 静态方法以及静态属性通过类调用,而不是实例
- 通过
#
注册私有方法以及属性(提案), 私有属性只能在类中使用,在外部使用就会报错class IncreasingCounter { #count = 0; get value() { console.log('Getting the current value!'); return this.#count; } increment() { this.#count++; } }
- 静态块(ES2022新增了静态块这一提案)
// 静态块内部可以使用this替代当前类 class C { static x = ...; static y; static z; static { try { const obj = doSomethingWith(this.x); this.y = obj.y; this.z = obj.z; } catch { this.y = ...; this.z = ...; } } }
class的继承
- class可以通过extend关键字实现继承,让子类继承父类的属性和方法(es5通过原型链继承)
class ColorPoint extends Point { }
- 子类必须在constructor函数中调用super()
class Point { /* ... */ } // super作用就是使用通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法 // super执行之前使用this会报错,因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。 class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
- Object.getPrototypeOf()用来从子类上获取父类
- super关键字
- super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。(作为函数时只能用在子类的构造函数中)
class A {} class B extends A { constructor() { super(); } } super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)。
- 第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A { p() { return 2; } } class B extends A { constructor() { super(); // 这里只能通过super调用父类上的静态属性以及方法,不是使用父类实例上的方法以及属性 console.log(super.p()); // 2 } } let b = new B();
- 类的 prototype 属性和__proto__属性
- 子类的__proto__属性,表示构造函数的继承,总是指向父类。
- 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
- 实例的 proto 属性
- 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
var p1 = new Point(2, 3); var p2 = new ColorPoint(2, 3, 'red'); p2.__proto__ === p1.__proto__ // false p2.__proto__.__proto__ === p1.__proto__ // true
- 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。