• JS的运行机制


    首先我们应该先知道浏览器内核渲染进程是由多线程组成的,其中主要包括以下几个

    1、GUI渲染线程

      。主要负责渲染浏览器界面,解析HTML和CSS,构建DOM树和RenderObject树,布局和绘制等

      。当页面需要重绘或者由于某种操作引发页面回流时,该线程就会执行

      。注意,GUI渲染线程和JS引擎线程是互斥的,当JS引擎线程运行的时候,GUI渲染线程就会被挂起,GUI更新会被保存在一个队列中,等待JS引擎空闲下来立即执行

    2、JS引擎线程

      。又称为JS内核,主要负责处理javascript脚本程序

      。JS引擎负责解析javascript脚本,运行代码

      。JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页面中无论什么时候都只有一个js线程在执行js程序

      。同样,JS引擎线程和GUI渲染线程是互斥的,所以如果JS线程执行的时间过长,这样会造成页面的渲染不连贯,导致页面渲染加载阻塞

    3、事件触发线程

      。该线程归属于浏览器而不是JS引擎线程,用来控制事件循环。(可以这样理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)

      。当JS引擎执行代码块 如setTimeOut(也可来自浏览器内核的其他线程,如鼠标点击,AJAX异步请求等)时,会把对应的任务添加到事件触发线程中

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

      。由于JS引擎是单线程的,所以这些待处理任务队列中的事件都得排队等待JS引擎处理(JS引擎空闲时才会去执行)

    4、定时触发器线程

      。传说中的setTimeOut和setInterval所在的线程

      。浏览器定时计数器并不是由javascript引擎计数的(因为JS引擎是单线程的,如果线程处于阻塞状态就会影响计时的准确性)

      。因此通过单独的线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)

      。需要注意,W3C在HTML标准中规定,setTimeOut小于4ms的时间间隔算为4ms

    5、异步http请求线程

      。在XMLHttpRequest连接后是通过浏览器新开一个线程请求

      。在检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中,由js引擎空闲时执行

    我们都知道JS引擎是单线程的,这是因为JS的作用主要是与用户互动,以及操作DOM,这决定了他只能是单线程的,否则会带来很多同步的问题,假如JS有两个线程,同一时间一个线程再某个DOM节点上添加东西,一个线程再删除该DOM节点,这时候就会出现问题

    然后我们还需要理解一些概念:

    .JS分为同步任务和异步任务

    1.同步任务都在主线程上执行,形成一个执行栈

    2.主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了结果,就会在任务队列中添加一个事件

    3.一旦执行栈中所有同步任务执行完毕,此时,JS引擎空闲,系统就会读取任务队列,将可执行的异步任务添加到可执行栈中,开始执行

    如此循环上面的步骤

    看到这里我们大概明白了,为什么setTimeOut推入的事件没有在规定的时间执行,这是因为,当它推入到事件队列中时,主线程还没有空闲,JS引擎还在执行主线程的任务,所以自然会有误差

    关于定时器:

    上述事件循环机制的核心是:JS引擎线程和事件触发线程

    但事件上还有一些隐藏的细节,譬如,调用setTimeOut后,是如何等待特定时间后才添加到事件队列中的?

    是JS引擎检测的么?当然不是了。它是由定时器线程控制

    为什么要单独的定时器线程?因为JS引擎是单线程的,如果处于阻塞线程状态就会影响计时的准确,因此很有必要单独开一个线程来计时

    什么时候会用到定时器线程?当使用setTimeOut或setInterval时,他需要定时器线程计时,计时完成后就会将特定的事件推入事件队列中。

    譬如:

    setTimeout(function(){

      console.log('hello!');

    },1000);

    这段代码的作用是1000毫秒计时完成后(由定时器线程计时),将回调函数推入事件队列中,等待主线程执行

    setTimeout(function(){

      console.log('hello!');

    },0);

    console.log('begin');

    这段代码的效果是最快的时间内将回调函数推入事件队列中,等待主线程执行

    注意

    执行结果是先begin后hello!

    虽然代码的本意是0毫秒后就推入事件队列,但是W3C在HTML标准中规定,要求setTimeout中低于4ms的时间间隔算为4ms

    就算不等待4ms,就算假设0毫秒就推入事件队列,也会先执行begin(因为只有可执行栈内空了后才会主动读取事件队列)

     

  • 相关阅读:
    [译]理解 iOS 异常类型 <🌟>
    LeetCode 24. 两两交换链表中的节点
    解决The operation couldn’t be completed. Unable to log in with account
    <Typora> 常用操作快捷键
    LeetCode 23. 合并K个升序链表
    CSS盒子模型
    CCS属性
    CSS
    form表单
    html
  • 原文地址:https://www.cnblogs.com/lvruifang/p/9406793.html
Copyright © 2020-2023  润新知