Coder Social home page Coder Social logo

Comments (67)

jimczj avatar jimczj commented on August 22, 2024 27

很棒的文章。纠正一点

我们都知道javaScript是单线程,渲染计算和脚本运行共用同一线程(网络请求会有其他线程),导致脚本运行会阻塞渲染。

渲染计算应该是浏览器GUI渲染线程负责,是由浏览器用c++编写的模块负责的。GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

from blog.

aooy avatar aooy commented on August 22, 2024 12

@webkonglong 文章涉及到规范,所以有些生涩,建议可以先看一些通熟易懂的介绍task和microtask的文章。
我在文中有一个实例,列出了每次循环task队列和microtask的变化,你所说的这个问题要弄清楚,首先得理解这轮event loop的task是哪个任务,其次setTimeout它只是一个task任务源,并不会立即执行,它只是将一个setTimeout任务插进task队列,得排到它,它里面的函数才会执行。Promise.then是microtask任务源,会将任务插进microtask队列。

Promise.resolve().then(function promise1 () {
       console.log('promise1');
    })
setTimeout(function setTimeout1 (){
   console.log('setTimeout1')
}, 0)

以上面的例子来说,这两个api仅仅是将相应的任务插进他们各自的队列中,此次event loop执行的task并不是setTimeout里的任务,setTimeout的任务排在后边了,还没轮到它。microtask队列的任务是会在当轮清空的,所以会看到promise1先于setTimeout1执行。

from blog.

MeCKodo avatar MeCKodo commented on August 22, 2024 12

讲的太好了。。看过讲event loop最舒服的文章
尤其是用了大量的例子去验证浏览器渲染,一直想看这部分,今天终于如愿以偿
感谢作者

from blog.

AsceticBoy avatar AsceticBoy commented on August 22, 2024 8

@WinnieFE 当时我和你有一样的疑惑,写点我现在的理解!也欢迎一起来讨论

首先明确 Task -> MicroTask -> UI Render 顺序是一定的,Vue中对于异步更新的运用主要是维护异步队列dom更新合并,以及nextTick。

而nextTick的实质也是MicroTask,只是它会在执行时立刻追加到异步队列后面,而当你依次执行队列时,UI虽然没渲染,但是DOM其实已经更新了,注意:DOM更新是及时的,但是更新是异步的。

我相信你已经理解了!如果有异议也可以一起讨论下!

from blog.

lmule avatar lmule commented on August 22, 2024 4

我基本肯定第三个例子是错误的
在chrome中,setTimeout1和setTimeout2不是属于一个event loop;但在node中,是属于同一个event loop
因为node是执行完一个setTimeout队列(里面可能会有多个setTimeout callback),才会执行micro task;但chrome是执行完一个setTimeout callback,就会执行micro task

setTimeout(()=> {
    console.log('setTimeout1')
    new Promise((r) => {
        r()
    })
    .then(() => {
        console.log('timeout1-promise1')
    })
}, 0)     

setTimeout(()=> {
    console.log('setTimeout2')
    new Promise((r) => {
        r()
    })
    .then(() => {
        console.log('timeout2-promise1')
    })
}, 0)  

在node和chrome分别执行下这段js,应该就比较明朗了

from blog.

Yatoo2018 avatar Yatoo2018 commented on August 22, 2024 4
setTimeout(() => console.log('timeout'))

Promise.resolve().then(() => {
    console.log('promise1')
    Promise.resolve().then(() => console.log('promise2')).then(() => console.log('aaa'))
    Promise.resolve().then(() => console.log('promise3'))
}).then(() => console.log('promise4'))

console.log('normal')

没太理解,能不能麻烦详细解释一下执行流程,还有为什么这段新代码里面的aaa在最后阶段输出?

我来结合自己的理解讲一下:

                                          // 1 
setTimeout(                               // 1-1
    () => {                               // 2
    console.log('timeout')                // 2-1
})  

Promise.resolve()                         // 1-2
    .then(() => {                         // 3
        console.log('promise1')           // 3-1
        Promise.resolve()                 // 3-2
            .then(() => {                 // 4
                console.log('promise2')   // 4-1
            })
            .then(() => {                 // 5
                console.log('aaa')        // 5-1
            }) 
        Promise.resolve()                 // 3-3
            .then(() => {                 // 6
                console.log('promise3')   // 6-1
            })
    }).then(() => {                       // 7
        console.log('promise4')           // 7-1
    })
console.log('normal')                     // 1-3

image

首先我为了区分每一个块,对你的代码进行了不会影响执行的格式调整,如果这个调整会导致与你执行的结果存在差异,请指出来。

我们再假设没有任何其他的js代码,将你的代码块放在一个script标签中,script里的代码被浏览器列为第一个task,放入task队列,并触发启动eventloop。

那么eventloop第一次循环的时候,执行栈为空,任务队列如下描述:

{task: [1], 其他队列:[] }

从task队列中找到最老的一个task,task队列中只有一个也就是script的task, 将这个task推入执行栈并执行这个task中的内容(也就是1这个代码块,1这个代码块中包含1-1,1-2,1-3三条同步代码;
我们看看这三条同步代码依次执行的详细过程(注意这三条语句肯定是在1那个task中执行完的)

  • 1-1的同步语句是一个js的异步api,他将一个定时任务2代码块放入了 task,代码块1-1执行完毕
  • 1-2是一个Promise.resolve()函数调用,并将promise1的状态改为fullfiled,then异步api将3代码放入 microtask
  • 1-3是一个同步语句console,执行之,打印normal

这时候1task执行完毕,并出栈,执行栈再次为空并告诉eventloop,你可以继续给我代码执行了,eventloop就进入下一个循环点,接下来,我们先看看异步队列里面的任务

{task:[2], microtask: [3]}

(按照node的说法,eventloop会有多个循环执行点)eventloop进入到能够执行microtask的循环点,发现microtask队列中有3这个microtask,
将3这个microtask中的内容推入执行栈并执行3中的内容(包含3-1,3-2这俩条同步语句);(同样要注意3-1,3-2, 3-3也是在代码块3中执行完毕的)

  • 3-1 console了promise1
  • 3-2 Promise.resolve()函数调用,将 promise2的状态置为fullfilled,then异步api将4这个回调放入microtask异步队列
  • 3-3 Promise.resolve()函数调用,将 promise3的状态置为fullfilled,then异步api将6这个回调放入microtask异步队列

这时候3这个microtask执行完毕,3代码块整个都出栈了, 然而promise1的状态为fullfilled,所以当3同步块执行完毕后,由于promise1的状态为fullfilled,7这个then异步api得以执行将7这个代码块放入microtask队列队尾

执行栈再次为空并告诉eventloop,你可以继续给我代码执行了,这时候eventloop不是到下一个循环点,
而是要继续检查microtask队列是否为空,为空就到下一个执行点,如果不是就继续执行microtask中的任务,直到microtask为空为止;那我们再看看各个队列的情况

{task:[2], microtask: [4, 6, 7]}

由于是队列(当然node中或者浏览器的实现中可能更复杂的数据结构,但是和队列相似),所以先进先出,那4先入队,4先执行

于是eventloop将4这个microtask推入执行栈并执行4中的内容(包含4-1)

  • 4-1 只有一条语句块执行之,于是打印promise2;由于promise2的状态为fullfilled状态,于是then异步api得以执行,将5代码块放入microtask队列队尾中,这时候promise2还未结束,还有then回调在microtask中。
    4 执行完毕

接着6同样的入栈执行

  • 6-1 打印promise3, promise3执行完毕。

6执行完毕

接着7这个microtask要执行了

  • 7-1 打印 promise4

接着5这个microtask要执行了

  • 5-1 打印aaa

终于3这个then搞定了,

执行完后,eventloop会到一个循环结束点,示意eventloop循环结束。

我们再看看各个队列中

[task:[2],microtask:[]]

eventloop发现异步队列里面还有一个task,进行第二次循环,然后执行最老的一个task2, 然后打印timeout

from blog.

FTAndy avatar FTAndy commented on August 22, 2024 3

microtask 是个好东西

from blog.

dreamdevil00 avatar dreamdevil00 commented on August 22, 2024 2

疑问 or Bug

关于 文中 一句 script里的代码被列为一个task,放入task队列。
不知道这句是根据什么推测来的, 难道 script 也是一个 task 源?

说下个人的理解,不当之处请指正

HTML-Standard 中 8.1.3.4 一节 (calling-scripts)[https://html.spec.whatwg.org/#calling-scripts]
根据第九步 clean up after running script
当执行栈为空时, perform a microtask checkpoint,

我认为步骤应该是这样:
tick1:
1、 queue a microtask promise1
task queue [], microtask queue [promise1]
2、 queue a task setTimeout1
task queue [setTimeout1], microtask queue [promise1]
3、 queue a task setTimeout2
task queue [setTimeout1, setTimeout2], microtask queue [promise1]

此刻 execution context stack 为空 则
perform a microtask checkpoint
执行所有 microtask queue 里的microtask , 也就是 执行 microtask promise1, 执行完毕后 queue 是这样的
task queue [setTimeout1, setTimeout2], microtask queue []

tick2:
执行 task queue 中 oldest task, 也就是 setTimeout1, 同时 queue a microtask promise2, 执行完毕后
task queue [setTimeout2], microtask queue [promise2]
接着 perform a microtask checkpoint, 执行 microtask queue 中的所有microtask , 此时 也就是要执行 promise2 , 执行完毕后
task queue [setTimeout2], microtask queue []

tick3:
执行 task queue 中 oldest task, 也就是 setTimeout2
接着perform a microtask checkpoint 此时 microtask queue 为空。 执行完毕后

task queue [] microtask queue []

from blog.

WinnieFE avatar WinnieFE commented on August 22, 2024 2

十分感谢分享!有个疑问想请教下,应用一节提到 “可以看到如果task队列如果有大量的任务等待执行时,将dom的变动作为microtasks而不是task能更快的将变化呈现给用户。” 这里是将dom的变动操作放在microtasks中执行,那么如果要获得最新的DOM是在哪步执行呢? Vuejs的nextTick回调函数里说是可以获得最新DOM,可是UI rendering是在microtasks之后执行,那么是怎么获取到最新DOM的呢? 这点实在困惑

from blog.

aooy avatar aooy commented on August 22, 2024 2

@caiyongmin node里的event loop可以说和浏览器的event loop完全不是一个东西,node没有微宏任务的概念,规范只是约束了浏览器厂商。不要混淆nodejs和浏览器中的event loop这里有一篇文章可以看看,setTimeout1、setTimeout2是在timers阶段运行的,该阶段结束后才会执行promise2

from blog.

lmule avatar lmule commented on August 22, 2024 2

楼主,我刚试了一下第三个例子,好像是渲染了两次:
image
我用的chrome 67.0.3396.99
而且你的截图好像只截了setTimeout1,没有setTimeout2?

from blog.

wingtao avatar wingtao commented on August 22, 2024 2

楼主写的非常棒。有点小疑惑望不吝赐教,如果希望在每轮event loop都即时呈现变动,可以使用requestAnimationFrame这句话的基础是每轮event loop都会检查执行requestAnimationFrame吧,并且有requestAnimationFrame回调会就一定会执行渲染。但实际应该是渲染前才会调用requestAnimationFrame吧,使用requestAnimationFrame并不能保证每轮都能渲染,只能保证渲染前一定调用了requestAnimationFrame的回调,求指教

from blog.

yilikun avatar yilikun commented on August 22, 2024 1

学习了 十分感谢

from blog.

wweggplant avatar wweggplant commented on August 22, 2024 1

@agileago 个人认为关键是then方法调用会创建一个新的promise,而这里的promise2和promise3已经标记为success了,aaa的新产生的promiseA要等promise2的第一个then结束后才会被标记为success,所以promise2和promise3会比aaa先执行

from blog.

ayou33 avatar ayou33 commented on August 22, 2024

用户代理可以理解为浏览器实现吗

from blog.

aooy avatar aooy commented on August 22, 2024

@hubu 我感觉可以这么理解

from blog.

PerkinJ avatar PerkinJ commented on August 22, 2024

感觉理解浏览器运行机制又深刻了一些,谢谢你的好文!

from blog.

webkonglong avatar webkonglong commented on August 22, 2024

运行microtask的条件是 上下文执行盏为空的时候,也就是在运行task之后,更新渲染之前。

settimeout属于task,promise属于microtask 照这样 settimeout 0秒要早promise执行了,我整体这么理解下来 有些懵,求解答 不知道可否加个微信或者qq,等你闲了 我请教你

from blog.

zhanba avatar zhanba commented on August 22, 2024

好文!想问一下浏览器为何要区分task和microtask呢?

from blog.

aooy avatar aooy commented on August 22, 2024

@zhanba 感觉选择eventloop这个异步模型,自然就得分出两种task才合理,一种是在当轮eventloop执行的,一种是往后某轮才执行的。如果没有microtask就没法在当轮eventloop里添加异步操作了,有点像人的左右手吧,少了一个就不完整了。

from blog.

rylan0119 avatar rylan0119 commented on August 22, 2024

请问将任务放到队列的操作是在哪里完成的?

from blog.

aooy avatar aooy commented on August 22, 2024

@jimczj 学习了,谢谢指正。

from blog.

aooy avatar aooy commented on August 22, 2024

@dreamdevil00 个人理解,<script>标签的解析和执行是在parse HTML这个阶段,是这个阶段的一部分,也就是最初构建DOM trees的时候。
在规范的8.1.4 Event loops中的8.1.4.1 Definitions里有这样的话:

An event loop has one or more task queues. A task queue is an ordered list of tasks, which are algorithms that are responsible for such work as:

Parsing
The HTML parser tokenizing one or more bytes, and then processing any resulting tokens, is typically a task.

Callbacks
Calling a callback is often done by a dedicated task.

提到了 HTML parser是一个典型的task,当我们解析和执行完<script>标签里的内容,后续执行的回调函数才会分到指定的task中执行。

可以做个测试,在文档中间插入一个执行的脚本,使用Developer tools里的performance(原timeline)看到结果是parser HTML -> 执行脚本 -> parser HTML ..... , 执行脚本是会中断文档解析的,因为脚本可能会修改dom trees,所以最初的文档解析和脚本执行应该是一个连续的过程,所以<script>标签的代码也是一个task。

from blog.

dreamdevil00 avatar dreamdevil00 commented on August 22, 2024

@aooy 恩恩 有道理 这里有个问题想请教下 call stack 和 execution context stack 是一回事么
在网上看视频 Philip Roberts- Help I'm stuck in an event-loop.-Mobile

他是拿 call stack 来讲的 event loop
这个call stack 和 execution context stack 感觉有相似之处啊 是一回事么

from blog.

fulvaz avatar fulvaz commented on August 22, 2024

楼主看这个

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

from blog.

dreamdevil00 avatar dreamdevil00 commented on August 22, 2024

@fulvaz 我看过啊 这个我懂了。 我的问题是 call stack 和 execution context stack 的区别啊

from blog.

Aeolos1994 avatar Aeolos1994 commented on August 22, 2024

很棒的文章 我最近也在写这个原理 学习了很多之前比较模糊的点

from blog.

sponia-joker avatar sponia-joker commented on August 22, 2024

请教博主一个问题,烦请指教!当我反复查看html规范时候

An event loop has one or more task queues. A task queue is an ordered list of tasks, which are algorithms that are responsible for such work as:

  1. Events
    Dispatching an Event object at a particular EventTarget object is often done by a dedicated task.
    2.Parsing
    The HTML parser tokenizing one or more bytes, and then processing any resulting tokens, is typically a task.
    ....

规范里面说到event loop有多个task queues.一个task queue就是任务的列表,它们负责以下工作。
看里很久有两个疑惑,恳请解答
1.dispatching an event 不是浏览器有专门的线程监听各种事件吗?然后相应的event handler 会进入task queues.为什么规范里说:是任务队列Dispatching an Event。因为任务队列里的任务都会被JavaScript engines执行,也就是说是JavaScript engines在触发事件?
2.html解析不是通过渲染引擎来解析的吗,怎么这里又说是JavaScript engines解析?在一次event loop中,如果页面需要更新。这时候会停止JavaScript引擎,启动渲染引擎来更新页面吗?
谢谢了
@aooy

from blog.

caiyongmin avatar caiyongmin commented on August 22, 2024
Promise.resolve().then(function promise1 () {
  console.log('promise1');
})
setTimeout(function setTimeout1 (){
console.log('setTimeout1')
Promise.resolve().then(function  promise2 () {
  console.log('promise2');
})
}, 0)

setTimeout(function setTimeout2 (){
console.log('setTimeout2')
}, 0)

这个例子在 node.js 是运行的不一样的结果

promise1
setTimeout1
setTimeout2
promise2

➜  ~ node -v
v6.11.3

from blog.

sunzhaoye avatar sunzhaoye commented on August 22, 2024

�这个翻译的是W3c的标准吧,不是whatwg的标准,我最近也从规范入手理解Event loop呢

from blog.

jadestrong avatar jadestrong commented on August 22, 2024

@WinnieFE 在Vue中更新一个注册到data中的属性,此时引用该值的地方,比如temlate中是不会立即执行的,所以直接获取对应的DOM节点得到的也还是原来的值,这是因为在更新该属性之后,Vue需要依次去执行注册到该属性上的监听函数去更新对应的应用,只有这一系列监听函数执行完成之后,DOM才是最新的,而我们手动注册的$nextTick方法会在这之后执行,所以可以获得更新后的DOM, 以上是个人理解,如果有误,请指正一下

from blog.

ringcrl avatar ringcrl commented on August 22, 2024

楼主,我刚试了一下第三个例子,好像是渲染了两次:
image
我用的chrome 67.0.3396.99
而且你的截图好像只截了setTimeout1,没有setTimeout2?

是的,我测试的时候也是两次

from blog.

agileago avatar agileago commented on August 22, 2024
setTimeout(() => console.log('timeout'))

Promise.resolve().then(() => {
    console.log('promise1')
    Promise.resolve().then(() => console.log('promise2'))
    Promise.resolve().then(() => console.log('promise3'))
}).then(() => console.log('promise4'))

console.log('normal')

楼主这个代码的运行结果为什么会是 1,2, 3,4而不是1,4,2,3,难道在执行microTask时产生的microTask优先级更高?

from blog.

lmule avatar lmule commented on August 22, 2024
setTimeout(() => console.log('timeout'))

Promise.resolve().then(() => {
    console.log('promise1')
    Promise.resolve().then(() => console.log('promise2'))
    Promise.resolve().then(() => console.log('promise3'))
}).then(() => console.log('promise4'))

console.log('normal')

楼主这个代码的运行结果为什么会是 1,2, 3,4而不是1,4,2,3,难道在执行microTask时产生的microTask优先级更高?

不是因为优先级高,而是因为执行2、3的时候,4还没加到microTask中

from blog.

agileago avatar agileago commented on August 22, 2024
setTimeout(() => console.log('timeout'))

Promise.resolve().then(() => {
    console.log('promise1')
    Promise.resolve().then(() => console.log('promise2')).then(() => console.log('aaa'))
    Promise.resolve().then(() => console.log('promise3'))
}).then(() => console.log('promise4'))

console.log('normal')

@lmule 没太理解,能不能麻烦详细解释一下执行流程,还有为什么这段新代码里面的aaa在最后阶段输出?

from blog.

agileago avatar agileago commented on August 22, 2024

@Yatoo2018 理解了,谢谢分析

from blog.

yuuk avatar yuuk commented on August 22, 2024

既然异步的任务分两种,一种task,另一种microtask,那同步任务又叫啥task呢?而且不管同步还是异步的task都会进入到调用栈,那同步的task是从哪进入调用栈的呢?小白求解释~~~

from blog.

huixisheng avatar huixisheng commented on August 22, 2024

当用户代理安排一个任务,必须将该任务增加到相应的event loop的一个tsak => [task] 队列中。

文中有个小错误

from blog.

xiehongyang avatar xiehongyang commented on August 22, 2024

con.textContent = 0;

例子2里面小错误,不是textContext

from blog.

imageslr avatar imageslr commented on August 22, 2024

博主您好,我有一个疑问。当JS访问某些属性的时候会触发“立即回流/重绘”,比如offsetWidth等。

那么我想请问一下,当我的代码中访问offsetWidth时,会触发“立即回流/重绘”时,但是这段代码还没有执行完毕,那这不就相当于我当前的task还没有执行完毕、就去执行UI rendering了吗?

var node = document.getElementById('id')
node.offsetWidth // => 浏览器在这里中断,进行回流重绘,然后再返回来继续运行后续代码?
// 后续代码

或者是我对回流/重绘理解有误吗?回流/重绘≠UI rendering?

谢谢您解答!

from blog.

Tvinsh avatar Tvinsh commented on August 22, 2024

‘’一个event loop有一个或者多个task队列‘’ 这句话不是很理解,一个loop不是只包含一个task队列和一个微任务队列吗

from blog.

imageslr avatar imageslr commented on August 22, 2024

@Tvinsh 浏览器确实是这样的,只有一个宏队列。但是在NodeJS中,不同的macrotask对应着不同的宏队列:

  1. Timers Queue:setTimeout() 和 setInterval() 的回调
  2. IO Callbacks Queue:用户输入的回调,如键盘、鼠标事件
  3. Check Queue:setImmediate的回调
  4. Close Callbacks Queue:一些准备关闭的回调函数,如:socket.on('close', ...)

取出下一个宏任务的时候,会从上一个宏任务所在队列开始往后检查是否有下一个任务。也就是说,如果当前宏队列还有任务,那么取出一个执行;如果当前宏队列没有任务,会执行下一个宏队列的任务(而不是回到第一个Timers Queue中)。

同样,浏览器中只有一个微队列,而node中有两个:

  1. Next Tick Queue:是放置process.nextTick(callback)的回调任务的
  2. Other Micro Queue:放置其他microtask,比如Promise等

两个微队列的任务也是按队列顺序依次执行。

要注意NodeJS与浏览器的执行顺序的不同,NodeJS的执行阶段是这样的:

  1. 先执行全局Script代码
  2. 执行完同步代码调用栈清空后,先从微任务队列Next Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask Queue中依次取出所有的任务放入调用栈中执行
  3. 然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个)
  4. 6个阶段执行完毕后,再开始执行微任务,以此构成事件循环

from blog.

pei-han avatar pei-han commented on August 22, 2024

每个线程都有自己的event loop。
那么event loop是线程中的循环结构吗?感谢
@aooy

from blog.

silviawxy avatar silviawxy commented on August 22, 2024

楼主,我刚试了一下第三个例子,好像是渲染了两次:
image
我用的chrome 67.0.3396.99
而且你的截图好像只截了setTimeout1,没有setTimeout2?

我试的也是一次

from blog.

silviawxy avatar silviawxy commented on August 22, 2024

楼主,我刚试了一下第三个例子,好像是渲染了两次:
image
我用的chrome 67.0.3396.99
而且你的截图好像只截了setTimeout1,没有setTimeout2?

我试的也是一次
理论上应该是两次啊

from blog.

lmule avatar lmule commented on August 22, 2024

楼主,我刚试了一下第三个例子,好像是渲染了两次:
image
我用的chrome 67.0.3396.99
而且你的截图好像只截了setTimeout1,没有setTimeout2?

是的,我测试的时候也是两次

wtf 你引用的是我去年发的,我刚试了下最新的chrome 80.0.3987.116,又变成了一次,难道楼主未卜先知。。。

from blog.

easonwanger avatar easonwanger commented on August 22, 2024

取出下一个宏任务的时候,会从上一个宏任务所在队列开始往后检查是否有下一个任务。也就是说,如果当前宏队列还有任务,那么取出一个执行;如果当前宏队列没有任务,会执行下一个宏队列的任务(而不是回到第一个Timers Queue中)。

@imageslr您好,执行完一个宏任务之后不应该是执行该宏任务后面的微任务么?你看下面就是promise在second timeout之前,不知道有没有正确理解你的意思,请指教
`setTimeout(
() => {
setTimeout(() => {
console.log('timeout');
}, 0);

Promise.resolve().then(()=>{
console.log('promise')
});

process.nextTick(()=>{
console.log('tick');
})
setImmediate(() => {
console.log('immediate');
});
},0)

setTimeout(()=>{
console.log('second timeout')
})`

输出:
tick
promise
second timeout
immediate
timeout

from blog.

luckymore avatar luckymore commented on August 22, 2024

postMessage是task,那它是在,setTimeout1之后插入的,为啥会在 setTimeout1 之前执行呢?

setTimeout(function setTimeout1(){
        console.log('setTimeout1')
}, 0)
var channel = new MessageChannel();
channel.port1.onmessage = function onmessage1 (){
    console.log('postMessage')
    Promise.resolve().then(function promise1 (){
        console.log('promise1')
    })
};
channel.port2.postMessage(0);
setTimeout(function setTimeout2(){
        console.log('setTimeout2')
}, 0)
console.log('sync')

from blog.

AlexZhong22c avatar AlexZhong22c commented on August 22, 2024

所有同源的browsing contexts可以共用event loop,这样它们之间就可以相互通信。

这句话是有前提的吧,否则效果太吓人了。这句话出自哪里?

from blog.

LeeeeeeM avatar LeeeeeeM commented on August 22, 2024
在一轮event loop中多次修改同一dom,只有最后一次会进行绘制。

楼主,小结第一条是不是描述有误,应该是可能会绘制,并且只绘制一次。
如果想每一次event loop都进行绘制,那么就要在渲染之前使用requestAnimationFrame对DOM进行修改。

from blog.

Jack-Works avatar Jack-Works commented on August 22, 2024

指正 task queue 不是队列,而是 set

规范:
Task queues are sets, not queues, because step one of the event loop processing model grabs the first runnable task from the chosen queue, instead of dequeuing the first task

from blog.

crazcdll avatar crazcdll commented on August 22, 2024

关于第一题,我发现 只修改 textContent 或者 innerHTML 的话貌似不会触发 LayoutPaint,只会触发 Schedule Style Recalculation

append 新的元素后,会触发 LayoutPaint

Chrome 版本 83.0.4103.97(正式版本) (64 位)

<div id='con'>this is con</div>
<script>
var t = 0;
    var con = document.getElementById('con');
    con.onclick = function () {
      setTimeout(function setTimeout1() {
        // 只有这行只会触发 Schedule Style Recalculation
        con.textContent = t;
       // 加上下面的代码后,会触发 Layout 和 Paint
        const child = document.createElement('div');
        child.innerHTML = 'I am child';

        con.appendChild(child);
      }, 0)
    };
</script>

相关截图

修改 textContent 和 appendChild
修改 textContent 和 appendChild

修改 textContent 或者 innerHTML
修改 textContent 或者 innerHTML

只有 appendChild
只有 appendChild

from blog.

steady-join avatar steady-join commented on August 22, 2024

postMessage是task,那它是在,setTimeout1之后插入的,为啥会在 setTimeout1 之前执行呢?

setTimeout(function setTimeout1(){
        console.log('setTimeout1')
}, 0)
var channel = new MessageChannel();
channel.port1.onmessage = function onmessage1 (){
    console.log('postMessage')
    Promise.resolve().then(function promise1 (){
        console.log('promise1')
    })
};
channel.port2.postMessage(0);
setTimeout(function setTimeout2(){
        console.log('setTimeout2')
}, 0)
console.log('sync')

估计浏览器实现不一样吧。。测了下safari(13.0.2),输出和想法一致。。当然,firefox在非特殊场景中也是一样。具体(https://stackoverflow.com/questions/62421016/event-execution-sequence-and-rendering)

from blog.

97Yates05 avatar 97Yates05 commented on August 22, 2024

好不容易搞清楚了宏任务和微任务,现在又被ui渲染困住了,请问如果宏任务或微任务执行超过16.7ms,那是会等结束后立即执行渲染吗?还有requestAnimationFrame是在一轮循环内执行,还是一定在每次渲染前执行。加上ui渲染后好迷惑啊

from blog.

steady-join avatar steady-join commented on August 22, 2024

@97Yates05 并不是每一轮的event loop都伴随着渲染,浏览器会基于一些“规则”,比如这次渲染不会带来视觉上的改变会跳过这次渲染,详情见(https://html.spec.whatwg.org/multipage/webappapis.html#event-loops),而RAF是在渲染前执行的,当然在不同的chrome版本测试可能是有问题的,比如version 80,存在这个bug。

from blog.

ZhaZhengRefn avatar ZhaZhengRefn commented on August 22, 2024

相逢恨晚,文章写得非常好,感谢作者

from blog.

lmlife2016 avatar lmlife2016 commented on August 22, 2024

好文!想问一下浏览器为何要区分task和microtask呢?

像setTimeout这种task是浏览器本身提供的(没有ecma层面的规范);promise这种microtask是ecma语言层面的规范,由浏览器来实现。
所以可以说promise出现之前,js的异步靠浏览器提供的api,promise出现后js有了语言层面的异步操作

from blog.

xiaoxiaoqian1217 avatar xiaoxiaoqian1217 commented on August 22, 2024

同感同感,这是我看得最舒服也学到最多的文章了

from blog.

steady-join avatar steady-join commented on August 22, 2024

from blog.

yanyang1116 avatar yanyang1116 commented on August 22, 2024

postMessage是task,那它是在,setTimeout1之后插入的,为啥会在 setTimeout1 之前执行呢?

setTimeout(function setTimeout1(){
        console.log('setTimeout1')
}, 0)
var channel = new MessageChannel();
channel.port1.onmessage = function onmessage1 (){
    console.log('postMessage')
    Promise.resolve().then(function promise1 (){
        console.log('promise1')
    })
};
channel.port2.postMessage(0);
setTimeout(function setTimeout2(){
        console.log('setTimeout2')
}, 0)
console.log('sync')

因为,settimeout 默认有 1ms 延时。虽然先进入 task queue。
但是任务执行去取 task 的时候,由于这个延时,会先取出后面的 postmessage task

from blog.

SK-Runner avatar SK-Runner commented on August 22, 2024

我这里有一段代码,可以麻烦您讲解一下执行流程吗,多谢多谢?
`async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}

async function async2() {
console.log('async2')
return new Promise(function (resolve, reject) {
console.log('promise2')
reject('promise2 reject');
}).then(function (res) {
console.log(res)
}).catch(err => {
console.log(err)
});
}

console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)

async1();

new Promise(function (resolve) {
console.log('promise1')
resolve();
}).then(function () {
console.log('promise1 resolve')
})

console.log('script end')`

from blog.

steady-join avatar steady-join commented on August 22, 2024

from blog.

xiaojueji avatar xiaojueji commented on August 22, 2024

@WinnieFE 当时我和你有一样的疑惑,写点我现在的理解!也欢迎一起来讨论

首先明确 Task -> MicroTask -> UI Render 顺序是一定的,Vue中对于异步更新的运用主要是维护异步队列dom更新合并,以及nextTick。

而nextTick的实质也是MicroTask,只是它会在执行时立刻追加到异步队列后面,而当你依次执行队列时,UI虽然没渲染,但是DOM其实已经更新了,注意:DOM更新是及时的,但是更新是异步的。

我相信你已经理解了!如果有异议也可以一起讨论下!

其实就是vue的nextTick可以拿到还没有绘制出来的dom,拿到的dom除了还没绘制之外已经可以进行任何api操作了

from blog.

hexiaolong24 avatar hexiaolong24 commented on August 22, 2024

楼主写的非常棒。有点小疑惑望不吝赐教,如果希望在每轮event loop都即时呈现变动,可以使用requestAnimationFrame这句话的基础是每轮event loop都会检查执行requestAnimationFrame吧,并且有requestAnimationFrame回调会就一定会执行渲染。但实际应该是渲染前才会调用requestAnimationFrame吧,使用requestAnimationFrame并不能保证每轮都能渲染,只能保证渲染前一定调用了requestAnimationFrame的回调,求指教

 个人认为是这样的

from blog.

Smallnuo avatar Smallnuo commented on August 22, 2024

好文!想问一下浏览器为何要区分task和microtask呢?
比如你去一个饭店吃饭,如果只能到店排队的话那么你只能到店后从最后开始排队,但是如果提供可以取号排队,你可以在需要去饭店的前半个小时就开始排队,等你到店就差不多吃上饭了~上面是一个比较形象的例子,但是也可以看出task的局限性就是无法处理高优先级的任务,microtask 提供了一种方式可以处理高优先级的任务。作者文章中也做了比较好的解释。

from blog.

JuctTr avatar JuctTr commented on August 22, 2024

worker也是一种用户代理,我理解能够提供一个完整的上下文环境,有着自己的事件循环处理模型的一个终端,都可以称之为一种用户代理

from blog.

L0st1 avatar L0st1 commented on August 22, 2024

作者你好,关于第九个例子,我在运行时的结果和示例不一样
如下:

sync
setTimeout1
postMessage
promise1
setTimeout2

setTimeout并不会因为4ms的规定而晚于postMessage执行

调换postMessage与setTimeout1的顺序

    var channel = new MessageChannel();
    channel.port1.onmessage = function onmessage1() {
      console.log('postMessage')
      Promise.resolve().then(function promise1() {
        console.log('promise1')
      })
    };
    setTimeout(function setTimeout1() {
      console.log('setTimeout1')
    }, 0)
    channel.port2.postMessage(0);
    setTimeout(function setTimeout2() {
      console.log('setTimeout2')
    }, 0)
    console.log('sync')

打印如下:

sync
setTimeout1
postMessage
promise1
setTimeout2

表明setTimeout1会早于postMessage执行

但如果为setTimeout指定1ms的延迟会发现它晚于postMessage执行,这种情况可能是什么原因导致的

上述结果的浏览器环境
mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/120.0.0.0 safari/537.36 edg/120.0.0.0

在更早版本的Chrome浏览器中,会得到和示例一样的结果,好像和浏览器有关
旧版本浏览器
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36

    setTimeout(function setTimeout1() {
      console.log('setTimeout0')
    }, 0)
    console.log("1st log")
    var channel = new MessageChannel();
    channel.port1.onmessage = function onmessage1() {
      console.log('postMessage')
      Promise.resolve().then(function promise1() {
        console.log('promise1')
      })
    };
    console.log("2nd log")
    setTimeout(function setTimeout1() {
      console.log('setTimeout1')
    }, 0)
    channel.port2.postMessage(0);
    setTimeout(function setTimeout2() {
      console.log('setTimeout2')
    }, 0)
1st log
2nd log
postMessage
promise1
setTimeout0
setTimeout1
setTimeout2

from blog.

Related Issues (7)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.