• JavaScript:event loop详解


    之前已经有两篇随笔提到了event loop,一篇是事件机制,一篇是tasks和microtasks,但是里面的event loop都是文字描述,很难说细,逻辑也只是简单的提了一遍。其实之前也是通过阮一峰老师的一篇网络日志:再谈event loop,然后写了点自己的想法。但是总感觉里面一些细节没有提到,像微任务队列这种。后来通过查看了一些国外的文档,尤其是一些谷歌Chrome开发人员的技术文档,并且结合了whatwg的一些HTML标准,这才有了点较全面的认识,这里把它记录下来。

    这里先来看一段代码,这是HTML规范里提到的:

    eventLoop = {
        taskQueues: {
            events: [], // UI events from native GUI framework
            parser: [], // HTML parser
            callbacks: [], // setTimeout, requestIdleTask
            resources: [], // image loading
            domManipulation: []
        },
    
        microtaskQueue: [
        ],
    
        nextTask: function() {
            // Spec says:
            // "Select the oldest task on one of the event loop's task queues"
            // Which gives browser implementers lots of freedom
            // Queues can have different priorities, etc.
            for (let q of taskQueues)
                if (q.length > 0)
                    return q.shift();
            return null;
        },
    
        executeMicrotasks: function() {
            if (scriptExecuting)
                return;
            let microtasks = this.microtaskQueue;
            this.microtaskQueue = [];
            for (let t of microtasks)
                t.execute();
        },
    
        needsRendering: function() {
            return vSyncTime() && (needsDomRerender() || hasEventLoopEventsToDispatch());
        },
    
        render: function() {
            dispatchPendingUIEvents();
            resizeSteps();
            scrollSteps();
            mediaQuerySteps();
            cssAnimationSteps();
            fullscreenRenderingSteps();
    
            animationFrameCallbackSteps();
    
            intersectionObserverSteps();
    
            while (resizeObserverSteps()) {
                updateStyle();
                updateLayout();
            }
            paint();
        }
    }
    
    while(true) {
        task = eventLoop.nextTask();
        if (task) {
            task.execute();
        }
        eventLoop.executeMicrotasks();
        if (eventLoop.needsRendering())
            eventLoop.render();
    }

    事件循环并没有多说关于什么时候dispatch event:

    1,每一个queue(队列)中的事件都是按顺序执行;

    2,事件可以直接dispatch,绕过task queues(任务队列)

    2,微任务是在一个task执行完成后立即执行;

    3,渲染部分循环是在vSync上执行,并且按以下顺序传递事件:

    1️⃣分派待处理的UI事件,2️⃣resize事件,3️⃣scroll滚动事件,4️⃣mediaquery监听者(css @media),5️⃣CSSAnimation事件,6️⃣Observers,7️⃣requestAnimationFrame

    下面来详细说一下

    UI events有两类:

    1,Discrete,俗称离散事件,就是那些不连续的,比如(mousedown,mouseup,touchstart,touchend)等

    2,Continuous,俗称连续事件,就是连续的,比如(mousemove,mousewheel,touchmove,wheel)等

    两种事件的注意点不同:

    1,连续事件:一个UI event task queue中,相匹配的连续事件(比如持续更新position属性,或者持续改变大小),可能会合并,不管那些合并的事件是否被dispatch了,因为有的还没有被dispatch,或者排在了队列的后面。

    2,离散事件:如果从硬件接收到了离散事件,就必须尽快dispatch,如果此时队列中有连续事件,就必须立即运行所有的连续事件,以防止离散事件的延迟。也就是说,触发离散事件的时候,连续事件必定已经全部dispatch完毕。

    不同的浏览器对事件循环的顺序是不同的,我还是习惯以谷歌为准,因为谷歌浏览器是最接近规范的

    下面列举一些谷歌dispatch event是通过哪些方法来dispatch的:

    1,DOMWindowEventQueue

    由计时器触发

    示例事件:window.storage,window.hashchange,document.selectionchange

    2,ScriptedAnimationController

    由Frame调用的BeginMainFrame函数触发,同时还管理requestAnimationFrame请求

    示例事件:Animation.finish,Animation.cancel,CSSAnimation.animationstart,CSSAnimation.animationiteration(CSSAnimation)

    3,Custom dispatch

    触发器不同:OS events操作系统事件,timers定时器,文档/元素生命周期事件

    Custom dispatch event 不通过队列,他们直接被触发(这里我猜想可能就是‘任务队列’的隐藏概念,他们不会排在普通队列中,而是单独去了另一个特殊的队列执行,这里就是‘任务队列’)

    4,Microtask队列

    微任务通常由EndOfTaskRunner.didProcessTask()触发,任务由TaskQueueManager运行,每当task完成时,微任务队列就会执行

    示例事件:image.onerror,image.onload

    微任务也包括Promise callbacks,这里注意,Promise的回调才是真正的微任务,之前说的可能不严谨,执行promise的时候本身还是正常task

    5,主线程事件队列

    连续事件会被合并处理。

    关于Timer有几个方法:

    requestIdleCallback

    这个方法只有浏览器空闲的时候才会有内部的timer触发

    requestAnimationFrame

    由ScriptedAnimationController触发,这个方法挺重要的,主要是用来解决setTimeout和setInterval无法完成的动画效果。

    Timers:setTimeout,setInterval

    由运行在TaskQueue primitive上的WebTaskRunner触发的。

    Observers

    观察者分为两种,MutationObserver和IntersectionObserver。

    MutationObserver:属于微任务,主要是负责监听DOM节点内的变化,前一篇随笔里有提到;

    IntersectionObserver:属于轮询,可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。具体用法不多介绍,不过它可以用来实现懒加载、无限滚动,监听元素是否在视窗中,或者已经出现了多少内容。

    Promises

    执行完成后,回调会放到微任务队列中去。

    以上就是event loop的全部相关内容。

    ========= 这条是分割线 ========

     2018年2月14日情人节

     其实结合上一篇‘再谈tasks和microtasks’

    上面提到微任务是在一个task执行完成之后才会执行,而前一篇里提到当JS栈清空时微任务才会执行,但是存在一个task中JS栈会清空的情况(例如那个click冒泡事件),而一个task并未执行完成。这里面就会有一个矛盾点,微任务并没有在task执行完成后执行,而是中途就执行了。

    针对此问题,我私信了Google Chrome的developer,Jake Archibald,感谢他不吝指教:

    那其实很明确了,微任务是否执行完全取决于JS stack是否为空,而task是否执行完成,这并不重要。一次任务中可能会有多个事件监听器,每次执行完一次事件监听,JS stack就会空一次,这时候就会执行微任务了。

    =================================== 

    2018.02.23 下午补充:

    nodejs里也有事件循环的改变,它是nodejs自身的执行模型:

    它是一个类似于while(true)的循环,每次循环都会去询问是否有事件需要处理,如果有,取出一个事件,查看该事件是否有对应的回调函数,如果有,执行函数然后进入下一个循环。如果不再有事件处理,则退出进程。

    如何得知是否有事件需要处理呢?问谁?

    观察者。每一个事件对应一个观察者,有文件I/O观察者,网络I/O观察者等等,观察者将事件进行了分类。

    事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求是事件的生产者,为Node提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。

    在Windows下,这个循环基于IOCP创建,而在*nix下则基于多线程创建。

    end

  • 相关阅读:
    AGC算法
    Cordic算法
    git Remote: HTTP Basic: Access denied Git failed with a fatal error.
    mysql 定义用户变量
    Docker 报错处理
    IIS,Docker 部署.Net Core
    SpringBoot向后台传参的若干种方式
    修改Mysql 数据库以及表字符集
    安装Docker
    获取北京时间
  • 原文地址:https://www.cnblogs.com/yanchenyu/p/8446119.html
Copyright © 2020-2023  润新知