文章来自我的 github 博客,包括技术输出和学习笔记,欢迎star。

先来明白些概念性内容。

进程、线程

  • 进程是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的。

  • 线程是进程的执行流,是CPU调度和分派的基本单位,同个进程之中的多个线程之间是共享该进程的资源的。

浏览器内核

  • 浏览器是多进程的,浏览器每一个 tab 标签都代表一个独立的进程(也不一定,因为多个空白 tab 标签会合并成一个进程),浏览器内核(浏览器渲染进程)属于浏览器多进程中的一种。

  • 浏览器内核有多种线程在工作。

    • GUI 渲染线程:

      • 负责渲染页面,解析 HTML,CSS 构成 DOM 树等,当页面重绘或者由于某种操作引起回流都会调起该线程。
      • 和 JS 引擎线程是互斥的,当 JS 引擎线程在工作的时候,GUI 渲染线程会被挂起,GUI 更新被放入在 JS 任务队列中,等待 JS 引擎线程空闲的时候继续执行。
    • JS 引擎线程:

      • 单线程工作,负责解析运行 JavaScript 脚本。
      • 和 GUI 渲染线程互斥,JS 运行耗时过长就会导致页面阻塞。
    • 事件触发线程:

      • 当事件符合触发条件被触发时,该线程会把对应的事件回调函数添加到任务队列的队尾,等待 JS 引擎处理。
    • 定时器触发线程:

      • 浏览器定时计数器并不是由 JS 引擎计数的,阻塞会导致计时不准确。
      • 开启定时器触发线程来计时并触发计时,计时完成后会被添加到任务队列中,等待 JS 引擎处理。
    • http 请求线程:

      • http 请求的时候会开启一条请求线程。
      • 请求完成有结果了之后,将请求的回调函数添加到任务队列中,等待 JS 引擎处理。

JavaScript 引擎是单线程

JavaScript 引擎是单线程,也就是说每次只能执行一项任务,其他任务都得按照顺序排队等待被执行,只有当前的任务执行完成之后才会往下执行下一个任务。

HTML5 中提出了 Web-Worker API,主要是为了解决页面阻塞问题,但是并没有改变 JavaScript 是单线程的本质。了解 Web-Worker

JavaScript 事件循环机制

JavaScript 事件循环机制分为浏览器和 Node 事件循环机制,两者的实现技术不一样,浏览器 Event Loop 是 HTML 中定义的规范,Node Event Loop 是由 libuv 库实现。这里主要讲的是浏览器部分。

Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。

  • JS 调用栈

    JS 调用栈是一种后进先出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成之后就从栈顶部移出该函数,直到栈内被清空。

  • 同步任务、异步任务

    JavaScript 单线程中的任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。

  • Event Loop

    调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。

  • 定时器

    定时器会开启一条定时器触发线程来触发计时,定时器会在等待了指定的时间后将事件放入到任务队列中等待读取到主线程执行。

    定时器指定的延时毫秒数其实并不准确,因为定时器只是在到了指定的时间时将事件放入到任务队列中,必须要等到同步的任务和现有的任务队列中的事件全部执行完成之后,才会去读取定时器的事件到主线程执行,中间可能会存在耗时比较久的任务,那么就不可能保证在指定的时间执行。

  • 宏任务(macro-task)、微任务(micro-task)

    除了广义的同步任务和异步任务,JavaScript 单线程中的任务可以细分为宏任务和微任务。

    macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

    micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。

    console.log(1);
    setTimeout(function() {
    console.log(2);
    })
    var promise = new Promise(function(resolve, reject) {
    console.log(3);
    resolve();
    })
    promise.then(function() {
    console.log(4);
    })
    console.log(5);

    示例中,setTimeout 和 Promise被称为任务源,来自不同的任务源注册的回调函数会被放入到不同的任务队列中。

    有了宏任务和微任务的概念后,那 JS 的执行顺序是怎样的?是宏任务先还是微任务先?

    第一次事件循环中,JavaScript 引擎会把整个 script 代码当成一个宏任务执行,执行完成之后,再检测本次循环中是否寻在微任务,存在的话就依次从微任务的任务队列中读取执行完所有的微任务,再读取宏任务的任务队列中的任务执行,再执行所有的微任务,如此循环。JS 的执行顺序就是每次事件循环中的宏任务-微任务。

    • 上面的示例中,第一次事件循环,整段代码作为宏任务进入主线程执行。
    • 遇到了 setTimeout ,就会等到过了指定的时间后将回调函数放入到宏任务的任务队列中。
    • 遇到 Promise,将 then 函数放入到微任务的任务队列中。
    • 整个事件循环完成之后,会去检测微任务的任务队列中是否存在任务,存在就执行。
    • 第一次的循环结果打印为: 1,3,5,4。
    • 接着再到宏任务的任务队列中按顺序取出一个宏任务到栈中让主线程执行,那么在这次循环中的宏任务就是 setTimeout 注册的回调函数,执行完这个回调函数,发现在这次循环中并不存在微任务,就准备进行下一次事件循环。
    • 检测到宏任务队列中已经没有了要执行的任务,那么就结束事件循环。
    • 最终的结果就是 1,3,5,4,2。

参考

JS浏览器事件循环机制的更多相关文章

  1. 浏览器中 JS 的事件循环机制

    目录 事件循环机制 宏任务与微任务 实例分析 参考 1.事件循环机制 浏览器执行JS代码大致可以分为三个步骤,而这三个步骤的往复构成了JS的事件循环机制(如图). 第一步:主线程(JS引擎线程)中执行 ...

  2. JS JavaScript事件循环机制

    区分进程和线程 进程是cpu资源分配的最小单位(系统会给它分配内存) 不同的进程之间是可以同学的,如管道.FIFO(命名管道).消息队列 一个进程里有单个或多个线程 浏览器是多进程的,因为系统给它的进 ...

  3. Node.js 的事件循环机制

    目录 微任务 事件循环机制 setImmediate.setTimeout/setInterval 和 process.nextTick 执行时机对比 实例分析 参考 1.微任务 在谈论Node的事件 ...

  4. js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)

    javascript是单线程,一切javascript版的"多线程"都是用单线程模拟出来的,通过事件循环(event loop)实现的异步. javascript事件循环 事件循环 ...

  5. JS:事件循环机制、调用栈以及任务队列

    点击查看原文 写在前面 js里的事件循环机制十分有趣.从很多面试题也可以看出来,考察简单的setTimeout也就是考察这个机制的. 在之前,我只是简单地认为由于函数执行很快,setTimeout执行 ...

  6. js的事件循环机制和任务队列

    上篇讲异步的时候,提到了同步队列和异步队列的说法,其实只是一种形象的称呼,分别代表主线程中的任务和任务队列中的任务,那么此篇我们就来详细探讨这两者. 一.来张图感受一下 如果看完觉得一脸懵逼,请继续往 ...

  7. js高级-浏览器事件循环机制Event Loop

    JavaScript 是队列的形式一个个执行的 同一时间只能执行一段代码,单线程的  (队列的数据结构) 浏览器是多线程的 JavaScript执行线程负责执行js代码 UI线程负责UI展示的 Jav ...

  8. JS基础-事件循环机制

    从一道题浅说 JavaScript 的事件循环 原文链接: https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7 ...

  9. 浏览器事件循环机制(event loop)

    JS是单线程的 JS是单线程的,或者说只有一个主线程,也就是它一次只能执行一段代码.JS中其实是没有线程概念的,所谓的单线程也只是相对于多线程而言.JS的设计初衷就没有考虑这些,针对JS这种不具备并行 ...

随机推荐

  1. java虚拟机规范(se8)——class文件格式(六)

    4.7.4 StackMapTable 属性 StackMapTable 属性是一个变长属性,位于 Code(§4.7.3)属性的属性表中.这个属性会在虚拟机类加载的类型阶段(§4.10.1)被使用. ...

  2. Introduction to Object-Oriented JavaScript 转载自:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript

    Introduction to Object-Oriented JavaScript IN THIS ARTICLE JavaScript review Object-oriented program ...

  3. db2表

    生成列: CREATE TABLE t1 (c1 INT,                 c2 DOUBLE,                 c3 DOUBLE GENERATED ALWAYS ...

  4. mqtt haproxy 代理及负载搭建

    目录 mqtt 分布集群搭建 haproxy 安装配置 解压 安装 配置haproxy.cfg 启动haproxy 配置mqtt 测试 负载配置说明 负载均衡算法 ACL规则定义 全局配置 默认配置 ...

  5. openface人脸识别框架

    openface的githup文档地址:http://cmusatyalab.github.io/openface/ openface的安装: 官方推荐用docker来安装openface,这样方便快 ...

  6. #Ubuntu16.0.4 LTS 安装RabbitMQ

    1.ubuntu下安装配置rabbitmq-server服务器环境:ubuntu16.0.4(向下兼容14.0.4)软件版本:RabbitMQ 3.7.5 .Erlang 20.1.7参考文档:htt ...

  7. 2019牛客暑期多校训练营(第三场)I Median

    题意:给出n-2的中位数序列b,b[i]代表原序列中(a[i],a[i+1],a[i+2])的中位数,求a. 解法:比赛的时候没做出来,赛后看题解的.解法跟网上各位大佬一样:首先要证明其实原序列a中的 ...

  8. python 发送json数据操作实例分析 - python

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 本文实例讲述了python 发送json数据操作.分享给大家供大家参考,具体如下: # !/usr/bin/env py ...

  9. maven eclipse远程部署tomcat

    pom.xml tomcat 配置信息  <properties><project.build.sourceEncoding>utf8</project.build.so ...

  10. python3 实现堡垒机功能(并发执行命令及上传下载文件)

    转载请注明出处,欢迎提出宝贵意见,谢谢! 功能介绍: 1.主机分组 登录后显示分组主机及主机数量 选择主机组后显示该主机组下所有主机信息,主机名及IP显示输入选择:1.执行命令利用线程并发组内所有主机 ...