久经前端开发沙场,会经历各式各样的需求,处理这些需求时候,会使用各类各样的api和功能,这里集中对setTimeout和Promise的异步功能进行讨论一下。前端
这里就使用Promise
做为例子,来探究一下单独使用它,会有哪些注意点。vue
1.最初的试探 api
执行代码,Promise
的基本使用:浏览器
let fn = () => { console.log(1) let a = new Promise((resolve, reject) => { console.log(2) resolve(3) }) console.log(4) return a } // 执行 fn().then(data => console.log(data))
以上代码,输出结果为:异步
1 // 同步 2 // 同步 4 // 同步 3 // 异步
注意 new Promise()
是同步方法,resolve
才是异步方法。
此外,上面的方法,能够有下面这种写法,效果等同,主要是把Promise
精简了一下:编辑器
let fn = () => { console.log(1) console.log(2) let a = Promise.resolve(3) console.log(4) return a } // 执行 fn().then(data => console.log(data))
由于如今讨论的是Promise
的异步功能,因此下面均使用第二种写法的Promise
函数
2.多个同级Promise
性能
编辑器中,输入如下代码,多个同级的单层的Promise
:code
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') }) Promise.resolve().then(() => { console.log('P-1.2') }) Promise.resolve().then(() => { console.log('P-1.3') }) console.log('同步-0.2')
则会依次输出如下打印,毫无疑问的结果:server
同步-0.1 同步-0.2 P-1.1 P-1.2 P-1.3
3.Promise
套Promise
复杂一下,新增行有注释说明:
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') Promise.resolve().then(() => { // 新加行 console.log('P-2.1') // 新加行 }) // 新加行 }) Promise.resolve().then(() => { console.log('P-1.2') Promise.resolve().then(() => { // 新加行 console.log('P-2.2') // 新加行 }) // 新加行 }) Promise.resolve().then(() => { console.log('P-1.3') Promise.resolve().then(() => { // 新加行 console.log('P-2.3') // 新加行 }) // 新加行 }) console.log('同步-0.2')
输出结果以下:
同步-0.1 同步-0.2 P-1.1 P-1.2 P-1.3 P-2.1 P-2.2 P-2.3
可见,多层Promise
是一层一层执行的。
4.为了最终确认,进行最后一次验证,在第一个Promise
里面多加一层:
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') Promise.resolve().then(() => { console.log('P-2.1') Promise.resolve().then(() => { // 新加行 console.log('P-3.1') // 新加行 }) // 新加行 Promise.resolve().then(() => { // 新加行 console.log('P-3.2') // 新加行 }) // 新加行 }) }) Promise.resolve().then(() => { console.log('P-1.2') Promise.resolve().then(() => { console.log('P-2.2') }) }) Promise.resolve().then(() => { console.log('P-1.3') Promise.resolve().then(() => { console.log('P-2.3') }) }) console.log('同步-0.2')
输出结果以下:
同步-0.1 同步-0.2 P-1.1 P-1.2 P-1.3 P-2.1 P-2.2 P-2.3 P-3.1 P-3.2
确认完毕,的确是一层一层的执行。
并且这里能够告诉你们,setTimeout
和setInterval
在单独使用的时候,和Promise
是同样的,一样是分层执行,这里再也不贴代码了(友情提醒:setInterval
的话,须要第一次执行就把这个定时器清掉,不然就无限执行,卡死页面秒秒钟的事儿),
接下来才是重点,下面将setTimeout
和Promise
进行混合操做。
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') }) setTimeout(() => { console.log('S-1.1') }); Promise.resolve().then(() => { console.log('P-1.2') }) setTimeout(() => { console.log('S-1.2') }); console.log('同步-0.2')
执行结果以下。。。问题暴露出来了:
同步-0.1 同步-0.2 P-1.1 P-1.2 S-1.1 S-1.2
为何,在同级状况下,是Promise
执行完了setTimeout
才会执行?
是人性的泯灭,仍是道德的沦丧?
是由于JavaScript任务类型!
JavaScript的微任务和宏任务
敲黑板,标重点。
JavaScript的任务分为微任务(Microtasks)和宏任务(task);
而Promise
是微任务,setTimeout
是宏任务。
因此上面的代码中,代码执行时会是以下场景:
开始执行当前宏任务代码!遇到了
Promise
?好嘞,把它里面的异步代码,放在当前这个宏任务后面微任务里面,而后继续执行咱的;咦,有个
setTimeout
?是个宏任务,那在当前这个宏任务后面,建立第二个宏任务,而后把这个setTimeout
里面的代码塞进去,咱继续执行;咦,又一个
Promise
?把他塞进后面的微任务里。。。什么?已经有代码了?那有啥关系,继续往里塞,放在已有代码的后面就行,咱继续执行;天啊,又来一个
setTimeout
,如今后面已经有第二个宏任务了对吧?那就建立第三个宏任务吧,后面再遇到的话,继续建立;报告!代码执行到底了,当前这个宏任务执行完毕!
行,看一下咱的小尾巴---咱的微任务里面有代码吗?有的话直接执行;报告,微任务里面,那两个
Promise
的异步代码执行完了!
干的漂亮。。。对了,刚刚微任务里面,有没有新的Promise
微任务?有的话,继续在如今这个微任务后面放!对对,只看执行到的代码,有多少放多少,一下子直接就执行了。。。若是遇到了setTimeout
知道该怎么作吧?继续开宏任务!报告,微任务所有执行完毕!
好!开始执行下一个宏任务!
因此,如今若是执行下面的代码,结果也显而易见吧:
console.log('同步-0.1') Promise.resolve().then(() => { console.log('P-1.1') Promise.resolve().then(() => { // 新加行 console.log('P-2.1') // 新加行 Promise.resolve().then(() => { // 新加行 console.log('P-3.1') // 新加行 }) // 新加行 }) // 新加行 }) setTimeout(() => { console.log('S-1.1') }); Promise.resolve().then(() => { console.log('P-1.2') }) setTimeout(() => { console.log('S-1.2') }); console.log('同步-0.2')
执行结果以下:
同步-0.1 同步-0.2 P-1.1 P-1.2 P-2.1 P-3.1 S-1.1 S-1.2
不管Promise
套用多少层,都会在下一个setTimeout
以前执行。
Dom
操做究竟是同步,仍是异步?这里出现一个说不清道不明的疑问,Dom
操做究竟是同步操做仍是异步操做?
若是是同步操做,那vue
的nextTick
方法是作什么用的?不就是在Dom更新完以后的回调方法吗?
若是是异步操做,那在剧烈操做Dom后面的代码,为何会被阻塞?并且代码看上去,也的确是按顺序执行的?
这里直接说明:js里面的Dom操做代码,是同步执行,但浏览器进行的Dom渲染,是异步操做。
浏览器渲染Dom和执行js,同时只能二选一,渲染一次Dom的时机是,当前宏任务和小尾巴微任务执行完,下一个宏任务开始前
vue
的nextTick
方法,则是使用H5的Api---MutationObserver
,监听浏览器将Dom渲染完成的时机。若浏览器不支持此方法,则会使用
setTimeout
,把nextTick
回调函数的执行时机,做为一个宏任务;上面也说了,浏览器渲染一次Dom,是下一个宏任务开始前,这样使用了
setTimeout
,保证了Dom确实渲染完成。这里也须要稍做提醒,js操做Dom是同步的,但操做Dom,毕竟超出了js自己语言的Api,每操做一次Dom,都须要消耗必定的性能,因此,在适合的状况下,最好先把要修改的Dom的内容,以字符串或者虚拟Dom的形式拼接好,而后操做一次Dom,把组装好的Dom字符串或虚拟Dom,一次性的塞进HTML页面的真实Dom中。