JS如何执行
从前到后,一行一行执行
如果某一行执行报错,则停止下面代码的执行
先执行完同步代码,再执行异步
event loop(事件循环/事件轮询)图示流程
JS是单线程 运行的
异步 要基于回调 来实现
event loop 就是异步回调的实现原理
图示流程 :1 2 3 4 5 6 7 console .log ('Hi' )setTimeout (function cb1 ( ) { console .log ('cb1' ) }, 5000 ) console .log ('Bye' )
DOM事件和event loop的关系
首先要明确DOM事件不是异步!
JS单线程,异步(定时器,ajax等)都使用回调,基于 event loop
DOM 事件也使用回调 ,基于 event loop
他们都是到了某一时刻才触发的,定时器是到了时间执行回调,ajax是返回请求时执行回调,DOM事件则是触发事件时执行回调,他们都基于 event loop
click是立马就执行的,但是绑定的事件函数要等触发以后才执行,虽然不是异步,但也有一个事件循环的过程,触发后的事件函数才被放入 回调队列 中,等待执行栈中清空后进行事件循环。
1 2 3 4 5 6 7 8 9 10 11 <button id ="btn1" > 提交</button > <script > console .log ('Hi' )$('#btn1' ).click (function (e ) { console .log ('button clicked' ) }) console .log ('Bye' )</script >
Promise 三种状态
pending resolved rejected
pending-> resolved 或pending-> rejected
变化不可逆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 const p1 = new Promise ((resolve, reject ) => {}) console .log ('p1' ,p1)const p2 = new Promise ((resolve, reject ) => { setTimeout (() => { resolve () }) }) console .log ('p2' ,p2)setTimeout (() => console .log ('p2_setTimeout' ,p2))const p3 = new Promise ((resolve, reject ) => { setTimeout (() => { reject () }) }) console .log ('p3' ,p3)setTimeout (() => console .log ('p3_setTimeout' ,p3))
1 2 3 4 5 const p = Promise .resolve (100 )Promise .reject ('some error' )
状态和 then catch
pengding状态,不会触发then和catch
resolved状态,会触发后续的then回调函数
rejected状态,会触发后续的catch回调函数 (哪怕中间隔着then,也不执行中间的then,直接执行后面的catch)
then和catch如何影响状态的变化
注意:then和catch返回新的Promise实例,状态默认都为resolved!! 如手动在then()/catch()中 返回Promise 或 抛出Error(抛出错误),则状态可变。
then正常返回resolved ,里面有 抛出错误 则返回rejected
catch正常返回resolved ,里面有 抛出错误 则返回rejected
注意:rejected状态下的then不被调用,所以也不会得到then返回的新的Promise,而catch就算隔着then也会被调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const p = Promise .resolve ().then (() => { return 100 ; }) setTimeout (() => console .log ('p' , p)) const p1 = Promise .resolve ().then (() => { throw new Error ('then error' ) }) setTimeout (() => console .log ('p1' , p1)) p1.then (() => { console .log ('rejected状态下的then()不被调用' ) }).catch (() => { console .log ('rejected状态下隔着then()的catch()仍然被调用' ) }).then (() => { console .log ('catch()返回的是resolved状态的Promise,此时的then()会被调用' ) }) const p2 = Promise .reject ('some error' )setTimeout (() => console .log ('p2' , p2)) const p3 = p2.catch ((err ) => { console .log ('err' , err) }) setTimeout (() => console .log ('p3' , p3)) p3.then (() => { console .log ('resolved状态下then被触发' ) }) const p4 = p2.catch ((err ) => { throw new Error ('catch error' ) }) setTimeout (() => console .log ('p4' , p4))
async/await语法
背景:
异步回调 会造成callback hell(回调地狱 )
Promise then catch链式调用 可解决回调地狱,但也是基于回调函数
async/await是同步语法,彻底消灭回调函数
async/await是用同步的方式编写异步 。
例子 如下,Promise的例子可参考“JS异步与单线程、Date、Math”中”手写用Promise加载2张图片” 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 function loadImg (src ) { const promise = new Promise ((resolve, reject ) => { const img = document .createElement ('img' ) img.onload = () => { resolve (img) } img.onerror = () => { reject (new Error (`图片加载失败 ${src} ` )) } img.src = src }) return promise } async function loadImg1 ( ) { const src1 = 'http://www.imooc.com/static/img/index/logo_new.png' const img1 = await loadImg (src1) return img1 } async function loadImg2 ( ) { const src2 = 'https://avatars3.githubusercontent.com/u/9583120' const img2 = await loadImg (src2) return img2 } (async function ( ) { try { const img1 = await loadImg1 () console .log (img1.width ,img1.height ) const img2 = await loadImg2 () console .log (img2) } catch (ex) { console .error (ex) } })()
async/await和Promise的关系
async/await是消灭异步回调 的终极武器,但和Promise并不互斥,反而,两者相辅相成
执行async函数 ,返回的是Promise对象 (如果函数内没返回 Promise ,则自动封装一下)
await相当于Promise的then
注意:对rejected状态的Promise使用await时不会执行,会报错,此时可使用try..catch包裹await来捕获异常。
try..catch可捕获异常 ,代替了Promise的catch
1 2 3 4 5 6 7 8 9 10 async function fn2 ( ) { return new Promise (() => {}) } console .log ( fn2 () )async function fn1 ( ) { return 100 } console .log ( fn1 () )
await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
await 后续跟非 Promise 对象:会直接返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 (async function ( ) { const p1 = new Promise (() => {}) await p1 console .log ('p1' ) })() (async function ( ) { const p2 = Promise .resolve (100 ) const res = await p2 console .log (res) })() (async function ( ) { const res = await 100 console .log (res) })() (async function ( ) { const p3 = Promise .reject ('some err' ) const res = await p3 console .log (res) })()
async/await与异步的本质,执行顺序
执行顺序:async是同步的,调用就立马执行,await这行代码也同步,但其后的代码会被放入 异步微任务队列 中!
本质: async/await是消灭异步回调 的终极武器,实现起来是同步的方式,但本质上还是异步 (具体看下方例子,从await后的代码被放在微任务中可看出本质是异步)
JS还是单线程,还得是有异步,还得是基于event loop
async/await只是一个语法糖 ,async/await本质还是异步,异步的本质还是回调函数,所以await所在那行代码后的代码都放在异步微队列中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 async function async1 () { console .log ('async1 start' ) await async2 () console .log ('async1 end' ) } async function async2 () { console .log ('async2' ) } console .log ('script start' ) async1 ()console .log ('script end' )
ES6 for-of的应用场景(与async/await)
可参考ES6扩展 对象扩展 和MDN for-of
for-in以及forEach、for是常规的同步遍历
for-of常用于异步遍历 ,例子如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 function multi (num ) { return new Promise ((resolve ) => { setTimeout (() => { resolve (num * num) }, 1000 ) }) } async function test2 () { const nums = [1 , 2 , 3 ]; for (let x of nums) { const res = await multi (x) console .log (res) } } test2 ()
ES9 for await of
可参考MDN
与上面的例子区分开
注意: 上面的例子循环的是同步可迭代对象nums
,只是 循环体中用了异步操作multi()
与for-of的区别:
针对同步可迭代对象
没有区别:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function multi (num ) { return new Promise ((resolve ) => { setTimeout (() => { resolve (num * num) }, 1000 ) }) } async function test2 () { const nums = [1 , 2 , 3 ]; for (let x of nums) { const res = await multi (x) console .log (res) } } test2 () async function test3 () { const nums = [1 , 2 , 3 ]; for await (let x of nums ) { const res = await multi (x) console .log (res) } } test3 ()
针对异步可迭代对象
区别在于for await of
会自动给循环的值执行一个await
,而for-of
不会,所以虽然都会同步执行,但执行效果可能有差异
就像下面例子test4()
,执行的是Promise对象,没有async-await,直接就同步返回了
注意: 以下例子在f12中执行时可能存在逐步输出变同时输出的情况,这是因为JS引擎在执行异步代码时会利用事件循环机制,允许同时处理多个任务造成的,实际不是这样1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function multi (num ) { return new Promise ((resolve ) => { setTimeout (() => { resolve (num * num) }, 1000 ) }) } async function test4 () { const asyncNums = [multi (1 ), multi (2 ), multi (3 )]; for (let x of nums) { console .log (res) } } test4 () async function test5 () { const asyncNums = [multi (1 ), multi (2 ), multi (3 )]; for await (let x of nums ) { console .log (res) } } test5 ()
宏任务和微任务
宏任务 :script setTimeout setInterval ajax DOM 事件
微任务 :Promise.then()(对于前端来说)async/await
注意:Promise的函数体是定义时就立即执行的,then()是微任务
宏任务比微任务执行的更早,执行一个宏任务就要清空所有微任务后再执行一个宏任务。注意js主体是一个宏任务,所以js后紧接着的是微任务
1 2 3 4 5 6 7 8 9 console .log (100 )setTimeout (() => { console .log (200 ) }) Promise .resolve ().then (() => { console .log (300 ) }) console .log (400 )
event-loop和DOM渲染的关系
JS是单线程的,且和DOM渲染共用一个线程,所以JS执行时需要留一些时机供DOM渲染
每一次 call stack 结束(即同步任务执行完 ),都会给一次 DOM 渲染的机会 (不一定非得渲染,就是给一次 DOM 渲染的机会!!!),DOM结构如有改变则重新渲染 。然后再触发下一次 event loop 。
例子:可以看出js执行和DOM渲染共用一个线程,js执行完才到DOM渲染 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const $p1 = $('<p>一段文字</p>' )const $p2 = $('<p>一段文字</p>' )const $p3 = $('<p>一段文字</p>' )$('#container' ) .append ($p1) .append ($p2) .append ($p3) console .log ('length' , $('#container' ).children ().length )alert ('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染' )setTimeout (function ( ) { alert ('setTimeout 是在下一次 Call Stack ,就能看到 DOM 渲染出来的结果了' ) })
宏任务和微任务的执行顺序的原因
为什么 异步任务 中微任务比宏任务执行更早
宏任务 :DOM 渲染后 再触发,如script setTimeout setInterval ajax DOM 事件
微任务 :DOM 渲染前 会触发,如Promise.then()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const $p1 = $('<p>一段文字</p>' )const $p2 = $('<p>一段文字</p>' )const $p3 = $('<p>一段文字</p>' )$('#container' ) .append ($p1) .append ($p2) .append ($p3) setTimeout (() => { const length = $('#container' ).children ().length console .log ('length2' ,length) alert (`macro task ${length} ` ) })
event-loop图解宏任务和微任务与DOM渲染的执行顺序
微任务 :微任务是ES6语法规定的。 ES 语法标准之内,JS 引擎来统一处理 。即,不用浏览器有任何处理,即可一次性处理完,更快更及时。
宏任务 :宏任务是浏览器规定的。 ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。 处理起来就比较慢,所以在DOM渲染后才触发宏任务。
题目 描述event loop机制
第一遍可以只到宏任务微任务
第二遍深入可加上和宏任务微任务与DOM渲染的关系
不要混着讲,怕乱
宏任务与微任务的区别
宏任务 :script setTimeout setInterval ajax DOM 事件
除script,其他都是浏览器引擎来执行,因为宏任务都是Web API,不属于ES语法
微任务 :Promise.then()(对于前端来说)async/await
js引擎来执行
注意:Promise的函数体是定义时就立即执行的,then()是微任务
宏任务比微任务执行的更早,js引擎处理速度比浏览器引擎快,所以 异步任务中微任务执行时机先于宏任务
微任务 在DOM渲染前 触发,宏任务在DOM渲染后 触发
Promise的三种状态,如何变化
pending,resolved,rejected
pending–>resolved 或 pending–>rejected
变化不可逆
Promise catch 连接 then catch执行与否的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Promise .resolve ().then (() => { console .log (1 ) }).catch (() => { console .log (2 ) }).then (() => { console .log (3 ) }) Promise .resolve ().then (() => { console .log (1 ) throw new Error ('erro1' ) }).catch (() => { console .log (2 ) }).then (() => { console .log (3 ) }) Promise .resolve ().then (() => { console .log (1 ) throw new Error ('erro1' ) }).catch (() => { console .log (2 ) }).catch (() => { console .log (3 ) })
async/await 语法问题 1 2 3 4 5 6 7 async function fn ( ) { return 100 } (async function ( ) { const a = fn () const b = await fn () })()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (async function ( ) { console .log ('start' ) const a = await 100 console .log ('a' , a) const b = await Promise .resolve (200 ) console .log ('b' , b) const c = await Promise .reject (300 ) console .log ('c' , c) console .log ('end' ) })()
Promise 和 setTimeout 顺序
同步任务先于异步任务
宏任务(setTimeout)先于微任务(Promise.then)
但是同步任务也属于宏任务,所以 执行宏任务(同步任务)=》清空微任务(Promise.then)=》执行宏任务(setTimeout) 1 2 3 4 5 6 7 8 9 console .log (100 )setTimeout (() => { console .log (200 ) }) Promise .resolve ().then (() => { console .log (300 ) }) console .log (400 )
【重点】async/await 执行顺序问题
注意:
async()调用时函数体立即执行,await当行代码为同步,await后是微任务。
Promise 的函数体(即new Promise(){…})会立刻执行。
then()是微任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 async function async1 () { console .log ('async1 start' ) await async2 () console .log ('async1 end' ) } async function async2 () { console .log ('async2' ) } console .log ('script start' ) setTimeout (function ( ) { console .log ('setTimeout' ) }, 0 ) async1 () new Promise (function (resolve ) { console .log ('promise1' ) resolve () }).then (function ( ) { console .log ('promise2' ) }) console .log ('script end' )
宏任务(同步,async)=》清空微任务(await后,then)=》宏任务(setTimeout)
script start async1 start async2 promise1 script end (同步代码执行完毕) async1 end promise2 setTimeout