=========================事件循环================== 先看以下的例子: 例1: console.log("a"); setTimeout(() => { console.log("b"); }, 0) console.log("c"); // 以上的结果是 先输出 a, c, b // 然后看下面的例子: console.log("a"); setTimeout(() => { console.log("b"); }, 0) for(let i = 0; i < 1000; i ++){ console.log("c") } // 以上的结果是 a 1000个c 最后是 b 问:为什么总都是b在最后呢? 事件回顾: JS运行的环境称之为宿主环境。 执行栈:call stack , 一个数据结构, 用于存放各种函数的执行环境,每一个函数执行之前, 它的相关信息会加入到执行栈。 函数调用之前, 创建执行环境, 然后加入到执行栈; 函数调用之后,销毁执行环境 JS引擎执行的都是栈的最顶部,执行完后,顶部的执行上下文会销毁(出栈), 调用函数之前一定要先入栈(创建一个对应的上下文),执行完后出栈,销毁对应的上下文 异步函数: 某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称为 异步函数,比如:事件处理函数,setTimout,setTimeItervel等。异步函数的执行时机,会被宿主环境控制。 浏览器宿主环境中包含5个线程: 1. JS引擎: 负责执行栈的最顶部代码 2. GUI线程: 负责渲染页面 3. 事件监听线程: 负责监听各种事件 4. 计时线程: 负责计时, 如setTimeOut, setTimeInterval等 5. 网络线程: 负责网络通信, 如 ajax, axios等 当上面的线程发生某些事情,如果该线程发现,这件事情有处理程序,他会将该处理程序加入 到一个叫做事件队列的内存中。 当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中 的第一个函数加入到执行栈中执行。 JS引擎对事件队列的取出方式,以及与宿主环境的配合,称之为事件循环。 事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。 在浏览器中,事件队列分为两种: 宏队列: macroTask, 计时器结束的回调,事件回调,http回调等绝大部分异步函数进入宏队列 微队列: mutationObserver, Promise产生的回调进入微队列 mutationObserver: 用于监听dom里面属性或者结构发生变化时,dom发生变化。 当执行栈清空时, JS引擎首先会将微队列中的所有任务依次执行结束,如果没有微队列的任务,则执行宏队列里面的任务 =================es6异步处理 Promise================= 事件和回调函数的缺陷: 我们习惯于使用传统的回调或事件处理来解决回调 事件: 某个对象的属性是一个函数,当发生某一事件时,运行该函数 dom.onclick = function(){} 回调: 运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数 dom.addEventLinster("click",function(){}) 本质上,事件和回调并没有本质的区别,只是函数放置的位置不同而且。 该模式主要面临以下问题: 1. 回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套 2. 异步之间的联系: 某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增 异步处理的通用模型 ES 官方参考了大量的异步场景,总结一套异步的通过模型,该模型可以覆盖几乎所有的异步场景,甚至同步场景 值得注意的是,为了兼容旧系统,ES6 并不打算抛弃过去的做法,只是基于该模型推出的一个权限的 API, 使用该APi, 让异步处理更加的简洁优雅 理解该API, 最重要的是,理解他的api 1. ES6 将某一件可能发生异步操作的事情,可以分为两个阶段: unsettled 和 settle unsettled: 未决阶段,表示事情还在进行前提的处理,并没有发生通向结果的那件事; settled: 已决阶段, 事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转; 事情总是从 未决阶段 逐步发展到 已决阶段的。 并且,未决阶段拥有控制何时通向已决阶段的能力 2. ES6 将事情划分为三种状态: pending, resolved rejected pending: 挂起,处于未决阶段,则表示这件事情还在挂起(最终的结果还没有出来) resolved: 已处理, 已决阶段的一种状态,表示整件事情出现了结果,并且是正常逻辑进行下去的结果 rejected: 已拒绝, 已决阶段的一种状态,表示整件事情出现结果,并不是一个正常的结果,错误的结果 既然未决阶段有权决定事情的走向,因此,未决阶段可以决定事情最终的状态! 我们 把事情变为resolved状态的过程叫做:resolve,推向该状态时候,可能还会传递一些数据。 我们 把事情变为rejected状态的过程叫做:rejected,推向该状态时候,可能还会传递一些数据,一些错误的信息。 无论是哪个阶段,过程都是不可逆的 3. 当事情的处理到达已决状态, 不同的状态决定不同的处理 resolved状态: 这是一个正常的已决的状态,后续处理表示未thenable rejected状态:这个一个非正常的已决的状态,后续处理表示未catchable 后续处理可能有多个,因此会形成作业队列,这些后续处理按照顺序,当状态到达后依次执行 Promise API: promise 不是消除回调,而是将回调用两种状态来返回 使用方法: const pro = new Promise((resolve, reject) => { // 未决阶段,也可以理解为等待阶段,异步之前做的事情,代码在这里写 // 通过调用resolve函数将Promise推向已决阶段的resolve状态(成功) // 通过调用reject函数将Promise推向已经阶段的reject状态(失败) // resolve 和rejecte 均可以传递最多一个参数,表示推送状态的数据 }) pro.then(data => { // 这是thenable 函数, 如果当前的Promise已经是resolved状态,该函数会立即执行, // 如果当前是为决状态,则会加入到作业队列,等待到达resolved状态后执行 // data为resolved 的状态数据 }, err => { // 这是catchable 函数, 如果当前的Promise已经是rejected状态,该函数会立即执行 // 如果当前是为决状态,则会加入到作业队列,等待到达rejected状态后执行 // err 为rejected 的状态数据 }) 细节: 1. 未决阶段的函数的代码是同步代码的,会立即执行 2. thenable 和catchable 函数是异步的,就是放到立即执行,但是必须需要等到同步代码执行完后,才会执行,并且是加入的是微队列里面 3. pro.then() 可以单独thenable的函数, 也可单独添加catchable的函数, 如 pro.catch(); 4. 在未决状态中发生错误或者抛出错误,会将错误推向reject,并且会被catchable捕获 5. 一旦状态推向已决,状态不可以改变 6. promise并没有消除回调, 只是让回调变的可控 =======================Promise 串联====================== 当后续的promise需要用到之前promise产生的结果; Promise 无论then方法,还是catch方法, 返回的是一个全新的Promise对象,状态满足下列规则: 1. 返回的Promise对象如果是挂起状态(未决),新的Promise的状态也是挂起状态 2. 如果当前的Promise是已决状态, 会运行后续的函数,并将后续处理函数的结果(返回值) 作为resolved状态数据,会应用奥新的Promise中; 如果后续处理函数发生错误,则把返回值当作 rejected状态数据,应用到新的Promise中(后续的Promise,需要等到前面的promise已决) 如果前面的Promise的后续处理,返回的是一个Promise, 那么后面的Promise的状态和前面的状态信息保持一致 const pro = new Promise((resolve, reject) => { resolve(1); }); const pro1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(3); }, 3000) }); pro.then(result => { console.log("第一个promise的状态") console.log(result) // 1 return pro1; }).then(result => { console.log(result) // 3 下面是undefined,是因为这里没有返回 }).then(result => { console.log(result) //undefined }) // 输出结果 3 和 undefined 在3秒后打印 Promise的其他api 原型成员(实例成员): then: 注册一个后续处理函数,当Promise为resolved状态时运行该函数 catch: 注册一个后续处理函数, 当Promise为rejected状态时运行该函数 finally:[es2018] 注册一个后续处理函数(无参),当Promise为已决时运行该函数 例如: const pro = new Promise((resolve, reject) => { resolve(1) }); pro.finally(() => { console.log("第一次finally的执行"); }) pro.then(res => { console.log(res, "已决resolved得出的结果") }) pro.catch(err => { console.log(err, "已决reject得出结果") }) pro.finally(() => { console.log("已决第二次执行finally") }) // 得出结果如下: // 第一次finally的执行 // 1 已决resolved得出的结果 // 已决第二次执行finally 构造函数成员(静态成员) resolve: 该方法返回一个resolved状态的Promise,传递的数据作为状态数据; 特殊情况:如果传递的数据是promise, 则直接返回传递的Promise对象 例如: const pro = new Promise((resolve, reject) => { // 这里面的代码是同步代码 resolve(1); }) // 等效于 const pro = Promise.resolve(1); 特殊情况: const pro = new Promise((resolve, reject) => { // 这里面的代码是同步代码 resolve(1); }) const pro1 = Promise.resolve(pro); // 等效于 const pro1 = pro; reject: 该方法返回一个rejectd状态的Promise,传递的数据作为状态数据 例如: const pro = new Promise((resolve, reject) => { // 这里面的代码是同步代码 reject(1); }) // 等效于 const pro = Promise.reject(1); all(iterable): 这个方法返回一个新的Promise对象, 该promise对象在所有promise数组中 所有的promise都已决resolved的时候触发成功方法, 一旦有任何一个promise里面的已决rejected状态 一行,会把错误立即返回; 例如: function getRandom(min, max) { return Math.floor(math.getRandom() * ((max - min) + min)) } const proms = []; for (let i = 0; i < 10; i++) { proms.push(new Promise((resolve, rejecct) => { setTimeout(() => { resolve(i) }, getRandom(1000, 5000)); })) } // 等待所有promise完成 Promise.all(proms).then(res => { console.log("所有promise 已决resolved状态后执行") }) race: 有一个成功那就成功,有一个失败那就失败,返回的是一个Promise