摘要:为了最终确认,进行最后一次验证,在第一个里面多加一层同步新加行新加行新加行新加行新加行新加行同步输出结果如下同步同步确认完毕,的确是一层一层的执行。而是微任务,是宏任务。
久经前端开发沙场,会经历各式各样的需求,处理这些需求时候,会使用各种各样的api和功能,这里集中对setTimeout和Promise的异步功能进行讨论一下。
多带带使用的执行模式这里就使用Promise作为例子,来探究一下多带带使用它,会有哪些注意点。
1.最初的试探
执行代码,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:
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")
则会依次输出以下打印,毫无疑问的结果:
同步-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);
宏任务是主流,当js开始被执行的时候,就是开启一个宏任务,在宏任务中执行一条一条的指令;
宏任务可以同时有多个,但会按顺序一个一个执行;
每一个宏任务,后面都可以跟一个微任务队列,如果微任务队列中有指令或方法,那么就会执行;如果没有,则开始执行下一个宏任务,直到所有的宏任务执行完为止,微任务相当于宏任务的小尾巴;
为什么有了宏任务,还会有微任务存在?因为宏任务太占用性能,当需要一些较早就准备好的方法,排在最后才执行的时候,又不想新增一个宏任务,那么就可以把这些方法,一个一个的放在微任务队列里面,在这个宏任务中的代码执行完后,就会执行微任务队列。
而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中。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/108882.html
摘要:事件触发线程主要负责将准备好的事件交给引擎线程执行。进程浏览器渲染进程浏览器内核,主要负责页面的渲染执行以及事件的循环。第二轮循环结束。 将自己读到的比较好的文章分享出来,大家互相学习,各位大佬有好的文章也可以留个链接互相学习,万分感谢! 线程与进程 关于线程与进程的关系可以用下面的图进行说明: showImg(https://segmentfault.com/img/bVbjSZt?...
js运行机制-事件循环EventLoop 先来看看一段js代码: console.log(script begin) setTimeout(() => { console.log(setTimeout) },0) new Promise((resolve) => { console.log(promise begin) for(let i = 0; i < 1000; i...
摘要:是怎么执行的一开始先简单聊了聊基本的数据结构,它和我们现在说的事件环有什么关系么当然有,首先要明确的一点是,代码的执行全都在栈里,不论是同步代码还是异步代码,这个一定要清楚。 栈和队列 在计算机内存中存取数据,基本的数据结构分为栈和队列。 栈(Stack)是一种后进先出的数据结构,注意,有时候也管栈叫做堆栈,但是堆又是另一种复杂的数据结构,它和栈完全是两码事。栈的特点是操作只在一端进行...
阅读 2748·2021-10-11 11:08
阅读 1468·2021-09-30 09:48
阅读 1019·2021-09-22 15:29
阅读 1012·2019-08-30 15:54
阅读 959·2019-08-29 15:19
阅读 514·2019-08-29 13:12
阅读 3137·2019-08-26 13:53
阅读 926·2019-08-26 13:28