JS执行机制

作者: KaylaLee | 来源:发表于2018-02-27 14:58 被阅读0次

    前言

    稀土掘金上看到这一次,彻底弄懂 JavaScript 执行机制从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理两篇关于JS执行机制的文章,受益匪浅,记得刚面试我现在这家公司的时候,面试官就问了我一些执行顺序的问题,之前没有系统的总结过,只用了些事例来回答,最近觉得要系统的梳理之前的积累的一些知识了,先从JS执行机制开始吧

    javascript特点

    js的特点在于它是单线程的,即同一时间只能做同一件事情,为什么js不能像java一样是多线程呢?
    主要原因在于应用场景不一样,作为浏览器脚本语言,更多的是处理DOM的操作和与用户的交互。假如js创建多线程,一个线程添加一个DOM节点,另一个线程删除当前这个DOM节点,浏览器就无法知道是以那个线程的操作为主了。为了使同步问题变得不那么复杂,在设计之初,js就是按照单线程来设计的。h5的API中提出web worker标准,允许创建多线程,但是子线程是受主线程控制的,而且不能操作DOM。

    进程和线程

    工厂的资源 -> 系统分配的内存(独立的一块内存)
    工厂之间的相互独立 -> 进程之间相互独立
    多个工人协作完成任务 -> 多个线程在进程中协作完成任务
    工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成
    工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

    浏览器是多进程的
    • 浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
    • 每打开一个Tab页,就相当于创建了一个独立的浏览器进程
    浏览器内核(渲染进程)

    如页面的渲染、js的执行、事件的循环,浏览器的渲染进程是多线程的
    主要包括:

    1. GUI渲染线程
    • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和Render Object树,布局和绘制等
    • 界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时
    • GUI渲染线程和JS引擎线程时互斥的
    1. JS引擎线程
    • JS内核,负责处理JS脚本程序(V8引擎)
    • JS引擎线程解析js脚本,运行代码。
    • JS引擎一直等待着任务队列中任务的到来,再加以处理,一个Tab页面(渲染进程)无论什么时候只有一个JS线程再运行JS程序。
    • 如果JS执行时间过长,就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
    1. 事件触发线程
    • 用于控制事件循环(JS引擎忙不过来,需要浏览器另开线程协助)
    • 当JS引擎执行代码块如setTimeOut(也可以来自浏览器内核的其他线程,如鼠标点击,ajax异步请求等)会将对应的任务添加到事件线程中
    • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
    • JS的单线程关系,这些等待处理队列中的事件都得排队等待JS引擎处理完才会执行
    1. 定时触发器线程
    • setInterval与setTimeout所在线程
    • 浏览器定时计数器并不是由JS引擎计数的(js引擎是单线程的,如果处于阻塞线程状态就会影响计时的准确性)
    • 通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后再执行)
    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms
    1. 异步HTTP请求线程
    • 在XMLHTTPRequest在连接后通过浏览器新开一个线程请求
    • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调放入事件队列中,再由js引擎执行。
      事件循环机制基于事件触发线程的

    浏览器渲染流程

    浏览器输入URL,浏览器主进程接管,开启一个下载线程
    DNS查询
    IP寻址
    HTTP请求
    等待响应,获取内容
    将内容通过RendererHost接口转交给Renderer进程
    浏览器渲染流程开始

    浏览器内核拿到内容后,渲染大概分成以下步骤:

    1. 解析html建立dom树
      2.解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合成render树)
      3.布局render树(layout/reflow),负责各元素尺寸,位置的计算
      4.绘制render树,绘制页面像素信息
      5.浏览器会将各层的信息发送给GPU,GPU会将各层合成,显示在屏幕上

    Event Loop谈JS的运行机制

    • JS分成同步任务和异步任务
    • 同步任务都在主线程上执行,形成一个执行栈
    • 主线程外,事件触发线程管理一个任务队列,只要有异步任务有了运行结果,就在任务队列中放置一个事件
    • 一旦执行栈中所有的同步任务执行完成(JS引擎空闲),系统会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行
      应该就可以理解了:为什么有时候setTimeout推入的事件不能准时执行?因为可能在它推入到事件列表时,主线程还不空闲,正在执行其它代码, 所以自然有误差

    时间循环:macrotask与microtask

    • macrotask(宏任务):每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放回到执行栈中执行)
    • 每个task会从头到尾将这个任务执行完毕,不会执行其他
    • 浏览器为了能使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个task执行前,对页面进行渲染(task->渲染->task->...)
      microtask(微任务),当前task执行结束后立即执行的任务
    • 在当前task任务后,下一个task之前,在渲染之前
    • 响应速度比setTimeout快,因为无需等待渲染
    • 在某个macrotask执行完后,就会将在他执行期间产生的所有microtask都执行完毕(在渲染前)
      macrotask:主代码块,setTimeout、setInterval、MessageChannel、postMessage、setImmediate等(事件队列中的每一个事件都是一个macrotask)
      microtask: MutationObsever 、Promise.then、process.nextTick等
      根据线程理解:
    • macrotask中的事件都是放在一个事件队列中的,而这个队列由事件触发线程维护
    • microtask中的微任务都是添加到微任务队列中,等待当前macrotask执行完毕后执行,这个队列由JS引擎线程维护(在主线程下无缝执行的)
      运行机制:
    • 执行一个宏任务(栈中没有就从事件队列中获取)
    • 执行过程中如果遇到微任务,就 添加到微任务的任务队列中
    • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务
    • 当前宏任务执行完毕后,开始检查渲染,然后GUI线程接管渲染
    • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

    相关文章

      网友评论

        本文标题:JS执行机制

        本文链接:https://www.haomeiwen.com/subject/mniyxftx.html