同步和异步的区别
- 同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
- 异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容
异步编程的实现方式
-
回调函数
- 优点:简单、容易理解
- 缺点:不利于维护,代码耦合高
-
事件监听(采用时间驱动模式,取决于某个事件是否发生):
- 优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
- 缺点:事件驱动型,流程不够清晰
-
发布/订阅(观察者模式)
- 类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅
-
Promise对象
-
优点:可以利用then方法,进行链式写法;可以书写错误时的回调函数;
-
缺点:编写和理解,相对比较难
-
Promise 的关键点在于callback 的两个参数,一个是 resovle,一个是 reject。还有就是 Promise 的链式调用(Promise.then(),每一个 then 都是一个责任人)
-
可以把 Promise看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化
-
简单来说它就是一个容器,里面保存着某个未来才会结束的事件(通常是异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
-
待定(pending):初始状态,既没有被完成,也没有被拒绝。
-
已完成(fulfilled):操作成功完成。
-
已拒绝(rejected):操作失败。
-
Promise.all(iterable) 将多个请求合并到一起
-
简化版的Promise
-
创建
myPromise 实例时,用户会传入一个 constructor 回调,这个回调会立即执行。
resolve 或 reject 会根据 constructor 回调的逻辑被调用,改变 Promise 的状态。
用户通过 then 方法传入回调函数
- `constructor(resolve, reject)` 是用户定义的控制逻辑,用于告诉 `Promise` 什么时候成功或失败。
- `onFullfilled(self.value)` 是在 `Promise` 成功时,执行用户定义的成功回调。
-
Generator函数
- 优点:函数体内外的数据交换、错误处理机制
- 缺点:流程管理不方便
-
async函数
- 优点:内置执行器、更好的语义、更广的适用性、返回的是Promise、结构清晰。
- 缺点:错误处理机制
什么是单线程,和异步的关系
-
单线程 - 只有一个线程,只能做一件事
-
原因 - 避免 DOM 渲染的冲突
- 浏览器需要渲染 DOM
- JS 可以修改 DOM 结构
- JS 执行的时候,浏览器 DOM 渲染会暂停
- 两段 JS 也不能同时执行(都修改 DOM 就冲突了)
- webworker 支持多线程,但是不能访问 DOM
-
解决方案 - 异步
事件循环EventLoop
-
js是单线程的,主要的任务是处理用户的交互
-
用户的交互无非就是响应DOM的增删改,使用事件队列的形式,一次事件循环只处理一个事件响应,使得脚本执行相对连续,所以有了事件队列,用来储存待执行的事件
-
那么事件队列的事件从哪里被push进来的呢?
-
那就是另外一个线程叫事件触发线程做的事情了,他的作用主要是在定时触发器线程、异步HTTP请求线程满足特定条件下的回调函数push到事件队列中,等待js引擎空闲的时候去执行
-
当然js引擎执行过程中有优先级之分,首先js引擎在一次事件循环中,会先执行js线程的主任务,然后会去查找是否有微任务microtask(promise),如果有那就优先执行微任务,如果没有,在去查找宏任务macrotask(setTimeout、setInterval)进行执行
-
微任务
- process.nextTick
Promise构造函数中的代码是同步的,then是微任务 等价于 await xx 之后的代码- Object.observe
- MutationObserver
-
宏任务
- script
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
一次 Event loop 顺序
- 执行同步代码,这属于宏任务
- 执行栈为空,查询是否有微任务需要执行
- 执行所有微任务
- 必要的话渲染 UI
- 然后开始下一轮 Event loop,执行宏任务中的异步代码
setTimeout(function () { console.log("1"); }, 0);
async function async1() {
console.log("2");
const data = await async2();
console.log("3");
return data;
}
async function async2() {
return new Promise((resolve) => {
console.log("4");
resolve("async2的结果");
}).then((data) => {
console.log("5");
return data;
});
}
async1().then((data) => {
console.log("6");
console.log(data);
});
new Promise(function (resolve) {
console.log("7");
// resolve(); // 调用 resolve,状态变为 fulfilled
}).then(function () {
console.log("8");
});
### **事件循环中的任务顺序**
-
同步任务(立即执行):
console.log("2")(在async1中)console.log("4")(在async2中)console.log("7")(在Promise构造函数中)
-
微任务队列(按添加顺序执行):
console.log("5")(async2的then)console.log("3")(async1中await后的代码)console.log("6")和console.log("async2的结果")(async1.then的回调)
-
宏任务队列(下一轮事件循环执行):
console.log("1")(setTimeout回调)
结果: 2 4 7 5 3 6 async2的结果 1
注意:由于没有调用 resolve,then 中的回调永远不会执行,因此不会打印 8。
因为 resolve 没有被调用,Promise 的状态永远是 pending,不会变成 fulfilled 或 rejected。
.then 方法, 只有当 Promise 状态变为 fulfilled 时,then 中的回调才会执行
事件的各个阶段
- 默认是冒泡
- 1:捕获阶段 ---> 2:目标阶段 ---> 3:冒泡阶段
- document ---> target ----> document
- 由此,addEventListener的第三个参数设置为true和false的区别已经非常清晰了
- true表示该元素在事件的“捕获阶段”(由外往内传递时)响应事件
- false表示该元素在事件的“冒泡阶段”(由内向外传递时)响应事件
- 阻止冒泡:在W3c中,使用stopPropagation()方法
- 阻止捕获:阻止事件的默认行为,使用preventDefault()方法