Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js 浏览器的事件循环 event-loop #1

Open
sevenCon opened this issue May 14, 2019 · 0 comments
Open

js 浏览器的事件循环 event-loop #1

sevenCon opened this issue May 14, 2019 · 0 comments

Comments

@sevenCon
Copy link
Owner

sevenCon commented May 14, 2019

js 浏览器的事件循环 event-loop

事件循环

为了协调用户输入, 网络请求, 事件,渲染,和JavaScript脚本的运行, 浏览器引入事件循环,多个同源的窗口共享一个事件循环.

js的执行是单线程的, 如果需要进行异步的任务, 那么它会确定这是一个宏任务还是一个微任务, 如果是微任务则会进入微任务的队列, 如果是宏任务则会进宏任务对应事件源的队列.js的对异步任务的执行过程则是不断的轮询这2个队列, 直到2个队列为空.

event-loop

官方文档事件循环https://html.spec.whatwg.org/multipage/webappapis.html#event-loops

宏任务
  • setTimeout, setInterval
  • MessageChannel
  • setImmediate(ie)
  • UI render
  • requestAnimationFrame
微任务
  • mutationObserver
  • Promise
  • Object.observe(已废弃)
一段代码
    setTimeout(()=>{
        console.log("timeout1");
        
        Promise.resolve().then(()=>{
            console.log("promise1");
        });
    });
    
    Promise.resolve().then(()=>{
        console.log("promise2");
        
        setTimeout(()=>{
            console.log("timeout2");
        });
    });
    
    
    // promise2
    // timeout1
    // promise1
    // timeout2
为什么会是这个顺序?

执行的主要过程是,

  • 首先这段代码开始执行, 就是一个宏任务, 比如script的标签的脚本内容. 宏任务执行完成,
  • montoring process线程发现js的主执行栈为空, 这时检查微任务队列, 发现promise2(注意只有原生浏览器提供Promise方法才是微任务, 一些第三方的实现不一定是), 执行打印'promise2', 一个微任务执行完毕, 微任务队列已经清空.
  • 开始执行UI render

根据HTML 标准,一轮事件循环执行结束之后,下轮事件循环执行之前开始进行UI render。即:

  • macro-task任务执行完毕
  • 清空micro-task任务队列
  • 开始执行UI render。

问题

requestAnimationFrame 和setTimeout 都属于宏任务阵列的函数, 那么为什么requestAnimationFrame总会比setTimeout先执行?
setTimeout(()=>{console.log(1)}, 0);    //  1
requestAnimationFrame(()=>console.log(2)); // 2

// 那为什么大多数时候是1,2, 偶尔会2,1呢; 而且把1,2行的顺序反过来, 
// 代码的结果也是和原来的一样, 只是偶尔会2,1

requestAnimationFrame 的相关知识 https://javascript.ruanyifeng.com/htmlapi/requestanimationframe.html

关于setTimeout 的最少4ms的延迟? 你确定吗?
let t = 0 , timer = null;
function loop(){
    var timestart = Date.now();
    var timeEnd = 0;
    timer = setTimeout(function(){
        if(t++ == 200) return;

        timeEnd = Date.now();
        console.log(timeEnd - timestart, timestart, timeEnd);
        
        loop();
    }, 0);
}
loop();

timeout-mintime-4ms 2
image

为什么有的结果都是1ms ,2ms. 但是多数时候是4ms, 5ms, 6ms, 这是执行的时间只能是一个大概的时间, 并不十分准确.
浏览器的时钟刻度就是4ms, 每隔4ms, 就会从系统处同步时钟, 这时再去检查事件循环队列中,是否有事件可以触发.
同时如果chrome的浏览器有多个标签窗口那么非当前标签js线程会挂起, 但是不会停止ui线程的渲染, 切换到该进程的时候, 会需要一定的唤醒成本.比如刚唤醒的时候执行的setTimeout时钟差不多一个1000ms左右的时钟同步刻度.

requestAnimationFrame总会比setTimeout的先执行
    let i = 0;
    while(i++< 10000){
        loop();
    }
    function loop(){
        setTimeout(function(){
            console.log("timeout");
        }); 
        requestAnimationFrame(function(){
            console.log("request")
        });
    }

image

前面我们说setTimeout的时间检查精度最小是4ms, 以一个60hz的刷新频率的显示器, 那么1000/60 = 16.66ms 的动画会集合到一个渲染周期内, setTimeout 最小是4ms, 怎么也应该会出现setTimeout在前, requestAnimationFrame在后的情况吧? 但是真实打印的情况却是, requestAnimationframe动作先执行, setTimeout后执行, 并且所有的requestAnimationFrame的队列全部完成后, 才开始执行setTimeout,

  • requestAnimationFrame 回调的执行时在setTimeout之前,并且animation的队列清空后才会执行setTimeout, UI render的内容是在每次setTimeout之后执行, requestAnimationFrame的回调只是把任务放到ui Render线程的队列中.(个人理解)

vue nextTick 不是微任务吗? 为啥可以获取到变化后的值

这不是渲染ui进程渲染后的值, 而是在内存中修改后的值, vue的$nextTick有可能是宏任务, 也有可能是微任务, 看浏览器的运行环境支持程度, 目前是先检查是否支持mutationObserver, 然后检查ie的setImmediate,最后才是 我们修改一个指令后,在代码后面天界 this.$nextTick(callback); callback的内容

    vm.a = 1;
    
    // 添加一个微任务, 获取节点信息.
    vm.$nextTick(function(){
        document.getElementById('#a').textContent; 
        // 这时获取的节点信息, 实际是ui线程未渲染前的节点信息, 但是在内存中已经被修改了, 因为vm.a = 1; 触发了对象setter的回调,这里可以理解为同步的修改.
    });
    
    
    // ...
    // 触发属性监听器的回调, 修改dom信息, 添加一个宏任务(UI渲染) 

JavaScript运行过程中的 monitoring process

monitoring process线程是浏览器事件循环模型中的一个辅助线程, 如果是node, 那么这个监听由libv的事件循环机制提供, 用来监听javascript的执行栈是否为空, 如果为空, 那么会去检查微任务列表出栈, 微任务进入主执行栈, 执行, 如果执行的过程产生微任务, 会把微任务放到微任务列表队尾, 直到清空了微任务的列表, 再去执行并宏任务列表.

微任务、宏任务与Event-Loop https://juejin.im/post/5b73d7a6518825610072b42b
从event loop规范探究javaScript异步及浏览器更新渲染时机 aooy/blog#5
官方的事件循环执行模型 https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant