这是我参与 8 月更文挑战的第 11 天,活动详情查看: 8月更文挑战
宏任务、微任务、同步任务、DOM渲染执行顺序
-
程序先程序
Call Stack
里面的内容,待Call Stack
清空时,执行当前的微任务; -
程序找到微任务队列的任务,执行微任务;
-
待微任务执行完毕后,尝试执行DOM渲染;
-
DOM
渲染结束后,触发event loop
,执行宏任务。
名称 | 举例(常用) |
---|---|
宏任务 | script、setTimeout 、setInterval 、setImmediate、Ajax、DOM事件、I/O、UI Rendering |
微任务 | process.nextTick()、Promise、async/await |
单线程和异步
- js是单线程语言,只能同时做一件事
- 浏览器合nodejs已支持js 启动
进程
,如web worker - js和DOM渲染共用同一个线程,因为JS可修改DOM结构
- 遇到等待(网络请求,定时任务)不能卡住
- 需要异步
- 回调callback函数形式
异步使用场景
- 网络请求,如ajax、图片加载
- 定时任务,如setTimeout
同步和异步的区别是什么?
基于JS是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行
回调地狱 callback hell
Promise
解决callback hell,可链式调用
function getData(url){
return new Promise((resolve,reject)=>{
$.ajax({
url,
success(data){
resolve(data)
},
error(err){
reject(err)
}
})
})
}
let url1 = '/data1.json'
let url2 = '/data2.json'
let url3 = '/data3.json'
getData(url1).then(data1=>{
console.log(data1)
return getData(url2) // promise实例
}).then(data2=>{
console.log(data2)
return getData(url3)
}).then(data3=>{
console.log(data3)
}).catch(err=>{
console.error(err)
})
Promise有哪三种状态?如何变化?
- 三种状态:
pending resolved rejected
- pending-> resolved或者pending->rejected
- 变化不可逆
- 状态的表现和变化
- pending状态,不会触发then和catch
- resolved状态,会触发后续的then回调函数
- rejected状态,会触发后续的catch回调函数
- then 和 catch对状态的影响
- then 正常返回 resolved,里面有报错则返回rejected
catch 正常返回 resolved
,里面有报错则返回rejected
//code 1
Promise.resolve().then(()=>{
console.log(1)
}).catch(()=>{
console.log(2)
}).then(()=>{
console.log(3)
})
// 返回 1 3
// code2
Promise.resolve().then(()=>{
console.log(1)
throw new Error('error1')
}).catch(()=>{
console.log(2)
}).then(()=>{
console.log(3)
})
//返回 1 2 3
//code3
Promise.resolve().then(()=>{
console.log(1)
throw new Error('error1')
}).catch(()=>{
console.log(2)
}).catch(()=>{
console.log(3)
})
//返回1 2
手写用Promise加载一张图片
const url = 'https://inews.gtimg.com/newsapp_bt/0/11327712791/641'
function loadImg(src){
return new Promise((resolve,reject)=>{
const img = document.createElement('img')
img.onload = ()=>{
resolve(img)
}
img.onerror = ()=>{
const err = new Error(`图片加载失败${src}`)
reject(err)
}
img.src = src
})
}
loadImg(url).then(img=>{
console.log(img.width)
return img //普通对象
}).then(img=>{
console.log(img.height)
}).catch(ex=>console.error(ex))
event loop事件循环/事件轮询
- js是单线程运行的
- 异步要基于回调来实现
- event loop就是异步回调的实现原理
js是如何执行的?
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步
event loop过程
- 同步代码,一行一行放在Call Stack执行
- 遇到异步,会先‘记录’下,等待时机(定时、网络请求等)
- 时机到了,就移动到Callback Queue
- 如 Call Stack为空(即同步代码执行完),Event Loop开始工作
- 轮询查找Callback Queue,如有则移动到Call Stack执行
- 然后继续轮询查找(永动机一样)
DOM事件和event loop
- js是单线程的
- 异步(setTimeout,ajax等)使用回调,基于event loop
- DOM事件也使用回调,基于event loop,不是异步
async/await
- 异步回调 callback hell
- Promise then catch链式调用,但也是基于回调函数
- async/await是同步语法,彻底消灭回调函数
// loadImg的方法见上方Promise
!(async function(){
//同步的语法
const img1 = await loadImg(src1)
console.log(img1.height,img1.width)
const img2 = await loadImg(src2)
console.log(img2.height,img2.width)
})()
async/await 和 Promise的关系
- async/await是消灭异步回调的终极武器
- 和Promise并不互斥
- 两者相辅相成
执行async函数,返回的Promise对象
await相当于Promise的then
try...catch可捕获异常,代替了Promise的catch
!(async function(){
const p4 = Promise.reject('err1') // rejected状态
const res = await p4 // await -> then
console.log('res',res) //不执行
try{
const res = await p4
consle.log(res)
}catch(ex){
console.log(ex) // try...catch相当于promise catch
}
//err1
})()
异步的本质
- async/await是消灭异步回调的终极武器
- js还是单线程,还是得有异步,还得是基于event loop
- async/await只是一个语法糖
await的后面,都可以看作是 callback里的内容,即异步
async function fn(){
return 100
}
!(async function(){
const a = fn() // Promise
const b= await fn() //100
})()
async function async1(){
console.log('async1 start') // 2 重要
await async2() //undefined
//await的后面,都可以看作是 callback里的内容,即异步
//类似,event loop,setTimeout(cb1)
// setTimeout(function(){console.log('async1 end')})
//Promise.resolve().then(()=> console.log('async1 end') )
console.log('async1 end') //5
}
async function async2(){
console.log('async2') // 3 重要
}
console.log('script start') //1
async1()
console.log('script end') //4
async function async1(){
console.log('async1 start') //2
await async2()
//下面3行都是异步回调 callback的内容
console.log('async1 end') //5
await async3()
//下面1行是异步回调的内容
console.log('async1 end2') //7
}
async function async2(){
console.log('async2') //3
}
async function async3(){
console.log('async3') //6
}
console.log('script start') //1
async1()
console.log('script end') //4 同步代码执行完,event loop执行异步
for ... of异步遍历
- for...in (及forEach for)是常规的同步遍历
- for...of常用于异步的遍历
function muti(num){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(num*num)
},1000)
})
}
const nums = [1,2,3]
nums.forEach(async(i)=>{
const res =await muti(i)
console.log(res) // 一次性输出 1 4 9
})
!(async function(){
for(let i of nums){
const res =await muti(i)
console.log(res) // 每隔1s输出 1 4 9
}
})()
宏任务 macroTash 和微任务 microTask
什么是宏任务?什么是微任务?
- 宏任务:
setTimeout, setInterval, Ajax , Dom事件
- 微任务:
Promise, async/await
微任务执行时机比宏任务要早
event loop和DOM渲染
- js是单线程的,而且和DOM渲染共用一个线程
- js执行的时候,得路一些时机供DOM渲染
执行顺序
- Call Stack空闲
- 执行当前的微任务
- 尝试DOM渲染
- 触发Event loop
微任务为什么比宏任务执行更早
- 每次Call Stack清空(即每次轮询结束),即同步任务执行完
- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
- 然后再去触发下一次Event loop
alert会阻断js执行,也会阻断DOM渲染
微任务和宏任务的区别
- 宏任务:
DOM渲染后触发
, 如setTimeout; 是由浏览器规定的 - 微任务:
DOM渲染前触发
,如Promise;是ES6规定的
async function async1(){
console.log('async1 start') // 2
await async2()
//await后面的都作为回调内容- 微任务
console.log('async1 end') //6
}
async function async2(){
console.log('async2') // 3
}
console.log('script start') //1
setTimeout(function(){ //宏任务
console.log('setTimeout') //8
},0)
async1()
// 初始化Promise时,传入的函数会立即执行
new Promise(function(resolve){
console.log('promise1') // 4
resolve()
}).then(function(){ //微任务
console.log('promise2') // 7
})
console.log('script end') //5
//执行到5,同步代码执行完毕(event loop - call stack被清空)
// 执行微任务
// (尝试触发DOM渲染)
// 触发event loop,执行宏任务