目录
面试之宏任务、微任务、async、await原理
事件循环(消息队列)
- 事件循环,说到这里,不得不说的是,浏览器的进程与线程,由于浏览器的一个tab代表一个进程(加载静态页面)
- 一个进程里面包含多个线程,又gui线程,js引擎线程,定时器线程,httpp线程
- 主要的是 再浏览器的线程执行之中,js引擎为单线程的,于是会造成的是划分为几种执行机制,首先是同步任务,还有异步任务,异步任务里面分(宏任务、微任务)
- 根据触发的机制,把对应要执行的事件,达到条件后,一一排序到 消息队列之中 等待js引擎的执行
- 优先执行同步,然后微任务,最后是宏任务,执行消息队列之中的事件,再一次去查询是否还有未执行的事件,这就是所谓的事件循环
消息队列之宏任务、微任务
-
1:JS引擎执行任务的时候是单线程的,也就是说一条流水线下来执行
-
2:优先执行同步任务,异步任务放置在消息队列之中
-
3:消息队列分为两种,宏队列(宏任务),微队列(维任务)
-
4:微队列优先于宏队列执行,
-
注意点1:即使两个宏队列里面,其中有一个微队列,那么就先执行宏列队后,把宏队列的微队列取出执行后,最后指向宏队列的下一个宏队列
-
注意点2:
- 宏队列(dom的回调函数,还有ajax,还有定时器函数)
- 微队列(promise回调函数,还有mutation回调函数)
消息队列示例1
console.log('1')
setTimeout(() => {
console.log('2')
})
Promise.resolve().then(() => {
console.log('3')
})
console.log('4')
// 结果是: 1 4 3 2
// 分析:
// log为同步任务优先执行 1 - 4
// setTimeout为宏任务 Promise为微任务,微任务先执行,则先3 最后 2
消息队列示例2
const home = this.$refs.home
let li = document.createElement('li')
li.innerHTML = '我是添加的li'
home.insertBefore(li, home.children[0])
console.log('1')
setTimeout(() => {
console.log('2')
alert('阻断js执行 宏')
})
Promise.resolve().then(() => {
console.log('3')
alert('阻断js执行 微')
})
console.log('4')
// 结果是: 1 4 3(阻断js执行 微) dom渲染(创建dom节点 li 我是添加的li) 2
// 微任务 > dom渲染 > 宏任务
// 分析:
// log为同步任务优先执行 1 - 4
// setTimeout为宏任务 Promise为微任务,微任务先执行,则先3 之后alret一个 (阻断js执行 微)
// dom渲染出 li标签 我是添加的li 最后执行宏任务 2
消息队列3
console.log('1')
async function async1() {
await async2()
console.log('async1')
}
async function async2() {
console.log('async2')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('2')
// 结果
// // 先执行第一轮:同步任务 使用await语法之后 转化为同步任务;new Promise里面的函数为同步任务(Promise)
// // 第一轮执行的同步任务结果 1 async2 Promise 2 async1
// 第一轮先执行同步任务
// 1 -> await async2() 中的async2()是同步任务,优先打印出async2,
// 之后的代码则由于await处理后,加入微任务消息队列 -> 然后再执行;new Promise里面的函数为同步任务(Promise)
// 最后执行最后的同步任务 2
// 同步任务的执行结果:1 async2 Promise 2
// 再执行微任务:async1 promise1 然后下一个.then()存储再下一个微任务之中
// 没有其他的微任务则继续执行下一个消息队列的微任务 promise2
// 最后执行宏任务 setTimeout
// 最后结果:1 async2 Promise 2 async1 promise1 promise2 setTimeout
消息队列4
setTimeout(() => {
console.log('0')
}, 0)
new Promise(resolve => {
console.log('1')
resolve()
})
.then(() => {
console.log('2')
new Promise(resolve => {
console.log('3')
resolve()
})
.then(() => {
console.log('4')
})
.then(() => {
console.log('5')
})
})
.then(() => {
console.log('6')
})
new Promise(resolve => {
console.log('7')
resolve()
}).then(() => {
console.log('8')
})
// 执行同步任务
// 1 -> 7 微任务消息队列[2,8]
// 执行微任务:
// 微任务之中 同步任务 2 -> 同步任务 3 -> 下一个消息队列[8,4]
// 先执行8 -> 下一个消息队列[4]
// 由于微任务5是不确定,只有等微任务4执行后才确定,因此 -> 下一个消息队列[4,6]
// 执行微任务4之后,里面的微任务5才确定,放在下一轮消息队列之中[6,5]
// 之后执行 6 - 5
// 最后执行宏任务0
// 结果 1 7 2 3 8 4 6 5 0
async与await的原理
- async 、await实际上就是对 generator 封装的一个语法糖
多个异步嵌套
function queryData() {
return new Promise(function (resolve) {
resolve(111)
})
}
queryData()
.then(res => {
console.log('第一个异步res', res)
return Promise.resolve(222)
})
.then(res => {
console.log('第二个异步res', res)
})
.catch(err => {
console.log('error', err)
})
// 如果有多个异步的话,需要使用多个then来处理异步之间可能存在的同步关系
es6 之 使用yield 的方式
// 模拟一个请求
function request() {
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: '我是data数据'
})
}, 1000)
})
}
//用yield 获取request的值
function* genGetData() {
yield request()
}
const g = genGetData()
const { value } = g.next()
// console.log('g', g, 'value', value, 'done', done)
// 间隔1s后打印 {data:"我是data数据"}
value.then(res => {
console.log('data', res)
})
// 输出结果:data {data: '我是data数据'}
es7 之 async 与 await的简写
// 使用async 和 await的写法
async function asyncGetData() {
const result = await request()
const result2 = await request()
console.log('result', result, 'result2', result2)
}
asyncGetData()
事件机制
-
事件分为两种事件:事件冒泡、事件捕获
-
事件一个触发流程:
- 当前目标阶段 - 事件捕获 - 事件冒泡
-
事件冒泡
- 当前目标阶段 - 逐层向上冒泡,会触发与当前目标阶段相同的事件,直至document
- 阻止事件冒泡
- e.stopPropagation()
- return false
- cancelBubble = true
- 再vue里面 添加 .stop事件修饰符
-
事件捕获
- 从document逐层向下捕获事件,直到当前目标阶段为止
- 阻住默认事件
- event. preventDefault()
- vue里面 添加 .prevent事件修饰符
-
冒泡事件之事件委托
- 就是原本 一个ul父容器包裹着 许多li子容器,想要对子容器添加事件,但是事件多,这时候就可以事件挂载再父容器之中,进行事件委托
<ul id="ul"> <li>1</li> <li>2</li> <li>3</li> </ul> let ul = document.querySelector('#ul') ul.addEventListener('click', (event) => { console.log(event.target); })
-
addEventListener 的事件捕获
dom.addEventListener('click',function(){ console.log("button被点击") },true)
-
点击冒泡