文章目录
- 6-1. 对象解构
- 什么是解构
- 在解构中使用默认值
- 非同名属性解构
- 7-2. 共享符号
- 7-3. 知名(公共、具名)符号
- [回顾]事件循环
- 8-1. 事件和回调函数的缺陷
- 8-2. 异步处理的通用模型
- 8-3. Promise 的基本使用
- 8-4. Promise 的串联
- 8-5. Promise 的其他 api
- 原型成员 (实例成员)
- 构造函数成员 (静态成员)
- 8-7. Promise 的其他 api
- async 和 await
- async
- await
6-1. 对象解构
什么是解构
使用 ES6 的一种语法规则,将一个对象或数组的某个属性提取到某个变量中
解构不会对被解构的目标造成任何影响
在解构中使用默认值
{
同名变量 = 默认值;
}
非同名属性解构
{
属性名: 变量名;
}
7-1. 普通符号
符号是 ES6 新增的一个数据类型,它通过使用函数 Symbol(符号描述) 来创建
符号设计的初衷,是为了给对象设置私有属性
私有属性:只能在对象内部使用,外面无法使用
符号具有以下特点:
- 没有字面量
使用 typeof 得到的类型是 symbol
每次调用 Symbol 函数得到的符号永远不相等,无论符号名是否相同
符号可以作为对象的属性名存在,这种属性称之为符号属性
开发者可以通过精心的设计,让这些属性无法通过常规方式被外界访问
符号属性是不能枚举的,因此在 for-in 循环中无法读取到符号属性,Object.keys 方法也无法读取到符号属性
Object.getOwnPropertyNames 尽管可以得到所有无法枚举的属性,但是仍然无法读取到符号属性
ES6 新增 Object.getOwnPropertySymbols 方法,可以读取符号
符号无法被隐式转换,因此不能被用于数学运算、字符串拼接或其他隐式转换的场景,但符号可以显式的转换为字符串,通过 String 构造函数进行转换即可,console.log 之所以可以输出符号,是它在内部进行了显式转换
7-2. 共享符号
根据某个符号名称(符号描述)能够得到同一个符号
Symbol.for("符号名/符号描述"); //获取共享符号
7-3. 知名(公共、具名)符号
知名符号是一些具有特殊含义的共享符号,通过 Symbol 的静态属性得到
ES6 延续了 ES5 的思想:减少魔法,暴露内部实现!
因此,ES6 用知名符号暴露了某些场景的内部实现
- Symbol.hasInstance
该符号用于定义构造函数的静态成员,它将影响 instanceof 的判定
obj instanceof A;
//等效于
A[Symbol.hasInstance](obj); // Function.prototype[Symbol.hasInstance]
- [扩展] Symbol.isConcatSpreadable
该知名符号会影响数组的 concat 方法
- [扩展] Symbol.toPrimitive
该知名符号会影响类型转换的结果
- [扩展] Symbol.toStringTag
该知名符号会影响 Object.prototype.toString 的返回值
- 其他知名符号
[回顾]事件循环
JS 运行的环境称之为宿主环境。
执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。
JS 引擎永远执行的是执行栈的最顶部。
异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。
浏览器宿主环境中包含 5 个线程:
- JS 引擎:负责执行执行栈的最顶部代码
- GUI 线程:负责渲染页面
- 事件监听线程:负责监听各种事件
- 计时线程:负责计时
- 网络线程:负责网络通信
当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当 JS 引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。
JS 引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。
事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:
- 宏任务(队列):macroTask,计时器结束的回调、事件回调、http 回调等等绝大部分异步函数进入宏队列
- 微任务(队列):MutationObserver,Promise 产生的回调进入微队列
MutationObserver 用于监听某个 DOM 对象的变化
当执行栈清空时,JS 引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。
8-1. 事件和回调函数的缺陷
我们习惯于使用传统的回调或事件处理来解决异步问题
事件:某个对象的属性是一个函数,当发生某一件事时,运行该函数
dom.onclick = function () {};
回调:运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数。
dom.addEventListener("click", function () {});
本质上,事件和回调并没有本质的区别,只是把函数放置的位置不同而已。
一直以来,该模式都运作良好。
直到前端工程越来越复杂…
目前,该模式主要面临以下两个问题:
- 回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套
- 异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增
8-2. 异步处理的通用模型
ES 官方参考了大量的异步场景,总结出了一套异步的通用模型,该模型可以覆盖几乎所有的异步场景,甚至是同步场景。
值得注意的是,为了兼容旧系统,ES6 并不打算抛弃掉过去的做法,只是基于该模型推出一个全新的 API,使用该 API,会让异步处理更加的简洁优雅。
理解该 API,最重要的,是理解它的异步模型
- ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettled 和 settled
- unsettled: 未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
- settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转
事情总是从 未决阶段 逐步发展到 已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力。
- ES6 将事情划分为三种状态: pending、resolved、rejected
- pending: 挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没出来)
- resolved:已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
- rejected:已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误
既然未决阶段有权力决定事情的走向,因此,未决阶段可以决定事情最终的状态!
我们将 把事情变为 resolved 状态的过程叫做:resolve,推向该状态时,可能会传递一些数据
我们将 把事情变为 rejected 状态的过程叫做:reject,推向该状态时,同样可能会传递一些数据,通常为错误信息
始终记住,无论是阶段,还是状态,是不可逆的!
- 当事情达到已决阶段后,通常需要进行后续处理,不同的已决状态,决定了不同的后续处理。
- resolved 状态:这是一个正常的已决状态,后续处理表示为 thenable
- rejected 状态:这是一个非正常的已决状态,后续处理表示为 catchable
后续处理可能有多个,因此会形成作业队列,这些后续处理会按照顺序,当状态到达后依次执行
- 整件事称之为 Promise
理解上面的概念,对学习 Promise 至关重要!
8-3. Promise 的基本使用
const pro = new Promise((resolve, reject) => {
// 未决阶段的处理
// 通过调用resolve函数将Promise推向已决阶段的resolved状态
// 通过调用reject函数将Promise推向已决阶段的rejected状态
// resolve和reject均可以传递最多一个参数,表示推向状态的数据
});
pro.then(
(data) => {
//这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
//如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
//data为状态数据
},
(err) => {
//这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
//如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
//err为状态数据
}
);
细节
- 未决阶段的处理函数是同步的,会立即执行
- thenable 和 catchable 函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
- pro.then 可以只添加 thenable 函数,pro.catch 可以单独添加 catchable 函数
- 在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向 rejected,并会被 catchable 捕获
- 一旦状态推向了已决阶段,无法再对状态做任何更改
- Promise 并没有消除回调,只是让回调变得可控
8-4. Promise 的串联
当后续的 Promise 需要用到之前的 Promise 的处理结果时,需要 Promise 的串联
Promise 对象中,无论是 then 方法还是 catch 方法,它们都具有返回值,返回的是一个全新的 Promise 对象,它的状态满足下面的规则:
- 如果当前的 Promise 是未决的,得到的新的 Promise 是挂起状态
- 如果当前的 Promise 是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为 resolved 状态数据,应用到新的 Promise 中;如果后续处理函数发生错误,则把返回值作为 rejected 状态数据,应用到新的 Promise 中。
后续的 Promise 一定会等到前面的 Promise 有了后续处理结果后,才会变成已决状态
如果前面的 Promise 的后续处理,返回的是一个 Promise,则返回的新的 Promise 状态和后续处理返回的 Promise 状态保持一致。
8-5. Promise 的其他 api
原型成员 (实例成员)
- then:注册一个后续处理函数,当 Promise 为 resolved 状态时运行该函数
- catch:注册一个后续处理函数,当 Promise 为 rejected 状态时运行该函数
- finally:[ES2018]注册一个后续处理函数(无参),当 Promise 为已决时运行该函数
构造函数成员 (静态成员)
-
resolve(数据):该方法返回一个 resolved 状态的 Promise,传递的数据作为状态数据
- 特殊情况:如果传递的数据是 Promise,则直接返回传递的 Promise 对象
-
reject(数据):该方法返回一个 rejected 状态的 Promise,传递的数据作为状态数据
-
all(iterable):这个方法返回一个新的 promise 对象,该 promise 对象在 iterable 参数对象里所有的 promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 promise 对象失败则立即触发该 promise 对象的失败。这个新的 promise 对象在触发成功状态以后,会把一个包含 iterable 里所有 promise 返回值的数组作为成功回调的返回值,顺序跟 iterable 的顺序保持一致;如果这个新的 promise 对象触发了失败状态,它会把 iterable 里第一个触发失败的 promise 对象的错误信息作为它的失败错误信息。Promise.all 方法常被用于处理多个 promise 对象的状态集合。
-
race(iterable):当 iterable 参数里的任意一个子 promise 被成功或失败后,父 promise 马上也会用子 promise 的成功返回值或失败详情作为参数调用父 promise 绑定的相应句柄,并返回该 promise 对象
8-7. Promise 的其他 api
async 和 await
async 和 await 是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。
async
目的是简化在函数的返回值中对 Promise 的创建
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
async function test() {
console.log(1);
return 2;
}
//等效于
function test() {
return new Promise((resolve, reject) => {
console.log(1);
resolve(2);
});
}
await
await 关键字必须出现在 async 函数中!!!!
await 用在某个表达式之前,如果表达式是一个 Promise,则得到的是 thenable 中的状态数据。
async function test1() {
console.log(1);
return 2;
}
async function test2() {
const result = await test1();
console.log(result);
}
test2();
等效于
function test1() {
return new Promise((resolve, reject) => {
console.log(1);
resolve(2);
});
}
function test2() {
return new Promise((resolve, reject) => {
test1().then((data) => {
const result = data;
console.log(result);
resolve();
});
});
}
test2();
如果 await 的表达式不是 Promise,则会将其使用 Promise.resolve 包装后按照规则运行