浏览器事件循环理解

何为进程

进程(Process) 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基。每个程序都需要自己的专属的内存空间,可以把这款空间简单的理解为进程;每个应用至少有一个进程,进程之间相互独立,即使通信也需要双方同意;

何为线程

线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

浏览器中的线程和进程

我们可以带着一个老生常谈的问题去思考:**为什么Javascript是单线程?**要更好地理解这个问题,我们需要首先了解浏览器的工作原理;浏览器是一个多进程多线程的应用程序;浏览器内部为了避免相互影响,会开启多进程,每个进程负责一个模块。这里核心了解的就是大致三个进程:浏览器进程、网络进程、渲染进程。同时每个进程中又会启动多个线程来处理中不同的任务;

  1. 浏览器进程

主要负责界面的交互、用户的交互、子进程的管理(其他进程的启动)

  1. 网络进程

负责加载网络资源

  1. 渲染进程

渲染进程启动后,会开启一个渲染主线程,用来执行HTML、CSS、JS;默认情况下是一个标签页一个渲染进程,以保证不同的标签页之间互补影响
由此可以理解到为什么Javascript是单线程的了,因为JavaScript作为浏览器脚本语言,运行环境主要是在浏览器的**渲染主线程;**其次JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作;所以种种原因造就了javaScript 在创造之初就是单线程的;

浏览器消息队列

渲染主线程是浏览器中最繁忙的线程,需要处理各种各样的任务;详情参考后续文章 《浏览器的渲染原理》;因为 JavaScript 是单线程的,在某一时刻内只能执行特定的一个任务,并且会阻塞其它任务执行。那么如何去调度这些任务呢?
答案就是 队列;浏览中通过队列的方式来调度这个任务,先进的任务先执行,后进的任务后执行,在同一时间只执行一个任务;
浏览器消息队列模型(EventLoop)

那么现在解释一下什么叫做事件循环

  1. 在最开始的时候,渲染主线程会进入一个无限的死循环
  2. 每一次循环都会检查消息队列中是否还有任务存在;如果有,就去第一个任务来运行,执行完后在取下一个任务来运行;如果消息队列里面空了,那就进入休眠状态,知道有新的任务被添加进来;
  3. 其他所有线程都可以随时向消息队列里面添加任务。新的任务都会添加到队列的末尾,在添加任务任务的时候如果主线程处于休眠状态,会被唤起来执行步骤2;

image.png
浏览器Message loop 源码

Js中同步和异步

JS中的同步很好理解,就是指代码按照顺序依次执行,每一行代码执行完成后才会执行下一行代码。渲染主线程处理消息队列中任务的方式就是一个同步执行,但是同步处理会阻塞代码的执行,直到前面的代码执行完成,后面的代码才会得到执行。 在渲染主线程中会有遇到一些无法立即执行的任务,比如:

  • 计时完成后需要执行的任务:setTimeout、setInterval
  • 网络通信完成后需要执行的任务:XHR、Fetch
  • 用户操作后的需要执行的任务: addEventListener

如果处理这些任务还是按照同步的方式去执行的话,会造成整个渲染主线程长时间处于阻塞状态,无法正常处理其他任务;
消息队列的特性:绝不阻塞。所以Javascript采用异步的方式处理这些无法立即执行的任务;
异步处理是指代码执行时不会阻塞后续代码的执行,同时会在后台执行一些其他的操作。在这些任务完成前,JavaScript 完全可以往下执行其他操作,当这些耗时的任务完成后则以回调的方式执行相应处理。
当然对于不可避免的耗时操作(如:繁重的运算,多重循环),HTML5 提出了
Web Worker
,它会在当前 JavaScript 的执行主线程中利用 Worker 类新开辟一个额外的线程来加载和运行特定的 JavaScript 文件,这个新的线程和 JavaScript 的主线程之间并不会互相影响和阻塞执行,而且在 Web Worker 中提供了这个新线程和 JavaScript 主线程之间数据交换的接口:postMessage 和 onMessage 事件。但在 HTML5 Web Worker 中是不能操作 DOM 的,任何需要操作 DOM 的任务都需要委托给 JavaScript 主线程来执行,所以虽然引入 HTML5 Web Worker,但仍然没有改线 JavaScript 单线程的本质。

任务和队列的优先级

任务没有优先级,在队列中先进显出,但是队列有优先级;
每个任务都有一个任务类型,同一个类型的任务必须在同一个队列,不同类型的任务可以属于不同的队列。再一次事件循环中,浏览器可以根据实际情况从不同的队列中取出任务执行
chrome中所有的任务类型如下:

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_TASK_TYPE_H_
#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_TASK_TYPE_H_

namespace blink {

    // A list of task sources known to Blink according to the spec.
    // This enum is used for a histogram and it should not be re-numbered.
    //
    // For the task type usage guideline, see https://bit.ly/2vMAsQ4
    //
    // When a new task type is created:
    // * Set the new task type's value to "Next value"
    // * Update kMaxValue to point to the new task type
    // * Increment "Next value"
    // * Update TaskTypes.md
    //
    // Next value: 87
    enum class TaskType : unsigned char {
        ///
        // Speced tasks should use one of the following task types
        ///

        // Speced tasks and related internal tasks should be posted to one of
        // the following task runners. These task runners may be throttled.

        // This value is used as a default value in cases where TaskType
        // isn't supported yet. Don't use outside platform/scheduler code.
        kDeprecatedNone = 0,

        // https://html.spec.whatwg.org/multipage/webappapis.html#generic-task-sources
        //
        // This task source is used for features that react to DOM manipulations, such
        // as things that happen in a non-blocking fashion when an element is inserted
        // into the document.
        kDOMManipulation = 1,
        // This task source is used for features that react to user interaction, for
        // example keyboard or mouse input. Events sent in response to user input
        // (e.g. click events) must be fired using tasks queued with the user
        // interaction task source.
        kUserInteraction = 2,
        // TODO(altimin) Fix the networking task source related namings once it is
        // clear how
        // all loading tasks are annotated.
        // This task source is used for features that trigger in response to network
        // activity.
        kNetworking = 3,
        // This is a part of Networking task that should not be frozen when a page is
        // frozen.
        kNetworkingUnfreezable = 75,
        // Tasks associated with loading that should also block rendering. Split off
        // from kNetworkingUnfreezable so tasks such as image loading can be
        // prioritized above rendering. Note that not all render-blocking resources
        // use this queue (e.g., script load tasks are not put here).
        kNetworkingUnfreezableRenderBlockingLoading = 83,
        // This task source is used for control messages between kNetworking tasks.
        kNetworkingControl = 4,
        // Tasks used to run low priority scripts.
        kLowPriorityScriptExecution = 81,
        // This task source is used to queue calls to history.back() and similar APIs.
        kHistoryTraversal = 5,

        // https://html.spec.whatwg.org/multipage/embedded-content.html#the-embed-element
        // This task source is used for the embed element setup steps.
        kEmbed = 6,

        // https://html.spec.whatwg.org/multipage/embedded-content.html#media-elements
        // This task source is used for all tasks queued in the [Media elements]
        // section and subsections of the spec unless explicitly specified otherwise.
        kMediaElementEvent = 7,

        // https://html.spec.whatwg.org/multipage/scripting.html#the-canvas-element
        // This task source is used to invoke the result callback of
        // HTMLCanvasElement.toBlob().
        kCanvasBlobSerialization = 8,

        // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
        // This task source is used when an algorithm requires a microtask to be
        // queued.
        kMicrotask = 9,

        // https://html.spec.whatwg.org/multipage/webappapis.html#timers
        // For tasks queued by setTimeout() or setInterval().
        //
        // Task nesting level is < 5 and timeout is zero.
        kJavascriptTimerImmediate = 72,
        // Task nesting level is < 5 and timeout is > 0.
        kJavascriptTimerDelayedLowNesting = 73,
        // Task nesting level is >= 5.
        kJavascriptTimerDelayedHighNesting = 10,
        // Note: The timeout is increased to be at least 4ms when the task nesting
        // level is >= 5. Therefore, the timeout is necessarily > 0 for
        // kJavascriptTimerDelayedHighNesting.

        // https://html.spec.whatwg.org/multipage/comms.html#sse-processing-model
        // This task source is used for any tasks that are queued by EventSource
        // objects.
        kRemoteEvent = 11,

        // https://html.spec.whatwg.org/multipage/comms.html#feedback-from-the-protocol
        // The task source for all tasks queued in the [WebSocket] section of the
        // spec.
        kWebSocket = 12,

        // https://html.spec.whatwg.org/multipage/comms.html#web-messaging
        // This task source is used for the tasks in cross-document messaging.
        kPostedMessage = 13,

        // https://html.spec.whatwg.org/multipage/comms.html#message-ports
        kUnshippedPortMessage = 14,

        // https://www.w3.org/TR/FileAPI/#blobreader-task-source
        // This task source is used for all tasks queued in the FileAPI spec to read
        // byte sequences associated with Blob and File objects.
        kFileReading = 15,

        // https://www.w3.org/TR/IndexedDB/#request-api
        kDatabaseAccess = 16,

        // https://w3c.github.io/presentation-api/#common-idioms
        // This task source is used for all tasks in the Presentation API spec.
        kPresentation = 17,

        // https://www.w3.org/TR/2016/WD-generic-sensor-20160830/#sensor-task-source
        // This task source is used for all tasks in the Sensor API spec.
        kSensor = 18,

        // https://w3c.github.io/performance-timeline/#performance-timeline
        kPerformanceTimeline = 19,

        // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15
        // This task source is used for all tasks in the WebGL spec.
        kWebGL = 20,

        // https://www.w3.org/TR/requestidlecallback/#start-an-event-loop-s-idle-period
        kIdleTask = 21,

        // Use MiscPlatformAPI for a task that is defined in the spec but is not yet
        // associated with any specific task runner in the spec. MiscPlatformAPI is
        // not encouraged for stable and matured APIs. The spec should define the task
        // runner explicitly.
        // The task runner may be throttled.
        kMiscPlatformAPI = 22,

        // Tasks used for DedicatedWorker's requestAnimationFrame.
        kWorkerAnimation = 51,

        // Obsolete.
        // kNetworkingWithURLLoaderAnnotation = 50, (see crbug.com/860545)
        // kExperimentalWebSchedulingUserInteraction = 53,
        // kExperimentalWebSchedulingBestEffort = 54,

        // https://drafts.csswg.org/css-font-loading/#task-source
        kFontLoading = 56,

        // https://w3c.github.io/manifest/#dfn-application-life-cycle-task-source
        kApplicationLifeCycle = 57,

        // https://wicg.github.io/background-fetch/#infrastructure
        kBackgroundFetch = 58,

        // https://www.w3.org/TR/permissions/
        kPermission = 59,

        // https://w3c.github.io/ServiceWorker/#dfn-client-message-queue
        kServiceWorkerClientMessage = 60,

        // https://w3c.github.io/web-locks/#web-locks-tasks-source
        kWebLocks = 66,

        // Task type used for the Prioritized Task Scheduling API
        // (https://wicg.github.io/scheduling-apis/#the-posted-task-task-source).
        // This task type should not be passed directly to
        // FrameScheduler::GetTaskRunner(); it is used indirectly by
        // WebSchedulingTaskQueues.
        kWebSchedulingPostedTask = 67,

        // https://w3c.github.io/screen-wake-lock/#dfn-screen-wake-lock-task-source
        kWakeLock = 76,

        // https://storage.spec.whatwg.org/#storage-task-source
        kStorage = 82,

        // https://www.w3.org/TR/clipboard-apis/#clipboard-task-source
        kClipboard = 85,

        // https://www.w3.org/TR/webnn/#ml-task-source
        kMachineLearning = 86,

        ///
        // Not-speced tasks should use one of the following task types
        ///

        // The default task type. The task may be throttled or paused.
        kInternalDefault = 23,

        // Tasks used for all tasks associated with loading page content.
        kInternalLoading = 24,

        // Tasks for tests or mock objects.
        kInternalTest = 26,

        // Tasks that are posting back the result from the WebCrypto task runner to
        // the Blink thread that initiated the call and holds the Promise. Tasks with
        // this type are posted by:
        // * //components/webcrypto
        kInternalWebCrypto = 27,

        // Tasks to execute media-related things like logging or playback. Tasks with
        // this type are mainly posted by:
        // * //content/renderer/media
        // * //media
        kInternalMedia = 29,

        // Tasks to execute things for real-time media processing like recording. If a
        // task touches MediaStreamTracks, associated sources/sinks, and Web Audio,
        // this task type should be used.
        // Tasks with this type are mainly posted by:
        // * //content/renderer/media
        // * //media
        // * blink/renderer/modules/webaudio
        // * blink/public/platform/audio
        kInternalMediaRealTime = 30,

        // Tasks related to user interaction like clicking or inputting texts.
        kInternalUserInteraction = 32,

        // Tasks related to the inspector.
        kInternalInspector = 33,

        // Obsolete.
        // kInternalWorker = 36,

        // Translation task that freezes when the frame is not visible.
        kInternalTranslation = 55,

        // Tasks used at IntersectionObserver.
        kInternalIntersectionObserver = 44,

        // Task used for ContentCapture.
        kInternalContentCapture = 61,

        // Navigation tasks and tasks which have to run in order with them, including
        // legacy IPCs and channel associated interfaces.
        // Note that the ordering between tasks related to different frames is not
        // always guaranteed - tasks belonging to different frames can be reordered
        // when one of the frames is frozen.
        // Note: all AssociatedRemotes/AssociatedReceivers should use this task type.
        kInternalNavigationAssociated = 63,

        // Tasks which should run when the frame is frozen, but otherwise should run
        // in order with other legacy IPC and channel-associated interfaces.
        // Only tasks related to unfreezing itself should run here, the majority of
        // the tasks
        // should use kInternalNavigationAssociated instead.
        kInternalNavigationAssociatedUnfreezable = 64,

    // Task used to split a script loading task for cooperative scheduling
    kInternalContinueScriptLoading = 65,

    // Tasks used to control frame lifecycle - they should run even when the frame
    // is frozen.
    kInternalFrameLifecycleControl = 68,

    // Tasks used for find-in-page.
    kInternalFindInPage = 70,

    // Tasks that come in on the HighPriorityLocalFrame interface.
    kInternalHighPriorityLocalFrame = 71,

    // Tasks that are should use input priority task queue/runner.
    kInternalInputBlocking = 77,

    // Tasks related to the WebGPU API
    kWebGPU = 78,

    // Cross-process PostMessage IPCs that are deferred in the current task.
    kInternalPostMessageForwarding = 79,

    // Tasks related to renderer-initiated navigation cancellation.
    kInternalNavigationCancellation = 80,

    ///
    // The following task types are only for thread-local queues.
    ///

    // The following task types are internal-use only, escpecially for annotations
    // like UMA of per-thread task queues. Do not specify these task types when to
    // get a task queue/runner.

    kMainThreadTaskQueueV8 = 37,
    kMainThreadTaskQueueV8LowPriority = 84,
    kMainThreadTaskQueueCompositor = 38,
    kMainThreadTaskQueueDefault = 39,
    kMainThreadTaskQueueInput = 40,
    kMainThreadTaskQueueIdle = 41,
    // Removed:
    // kMainThreadTaskQueueIPC = 42,
    kMainThreadTaskQueueControl = 43,
    // Removed:
    // kMainThreadTaskQueueCleanup = 52,
    kMainThreadTaskQueueMemoryPurge = 62,
    kMainThreadTaskQueueNonWaking = 69,
    kMainThreadTaskQueueIPCTracking = 74,
    kCompositorThreadTaskQueueDefault = 45,
    kCompositorThreadTaskQueueInput = 49,
    kWorkerThreadTaskQueueDefault = 46,
    kWorkerThreadTaskQueueV8 = 47,
    kWorkerThreadTaskQueueCompositor = 48,

    kMaxValue = kMachineLearning,
};

}  // namespace blink

  #endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_TASK_TYPE_H_

目前浏览器至少需要包含三个队列:

  • 微队列:用户存放需要最快执行的队列,优先级最高,浏览器中必须存在这个微队列

添加任务到微队列的的主要方式: Promise、MutationObserver

  • 交互队列:用于存放用户交互后的产生的事件处理任务,优先级高
  • 延时队列:用于存放计时器到期后的任务,优先级中等

接下来分析一个例子来感受一下消息队列
eventLoop.png
先同步执行整个代码块,首先会先执行到fn1函数声明可以先忽略;然后会把setTimeout放到延时进程中去执行,再加上只需要等待0ms,所有setTimeout的回调函数会立刻被放到延时队列中,但不会执行;紧接着开始执行到Promise.resolve(),此时相当于把then的回调函数放到为队列中;最后执行console.log("4");
同步代码执行完毕后,各个消息队列的状态:

接下来按照之前说的队列优先级,开始执行微队列中的任务;在fn1这个函数中,首先执行了console.log("1");接下来执行Promise.resolve(),按照刚才说的就相当于把then函数的回调放到微队列中;所以在fn1函数执行完毕后,此时的队列状态为

此时微队列中还存在任务,所有继续从微队列中拿出第一项任务开始执行 console.log("2"),执行完毕后此时微队列中全部执行完毕,接下来开始执行下一个优先级的队列,最后执行延时队列中的 console.log("3");所以按照我们的分析,最后这段代码执行后输出的结果为:4 1 2 3
最后在终端验证下输出的结果:
image.png

总结:

  • JavaScript 是单线程的,同一时刻只能执行特定的任务,而浏览器是多线程的。
  • 异步任务(各种浏览器事件、定时器等)都是先添加到相应的任务队列中。当 JavaScript 主线程为空时,就会按照一定的优先级读取消息队列的第一个任务,然后执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值