js的事件循环(Eventloop) 机制

这篇借助于同事准备的技术分享,书本知识,自己的理解梳理而成

高级程序设计第三版:
js 是一门单线程的语言,运行于单线程的环境中,例如定时器等并不是线程,定时器仅仅只是计划代码在未来的某个时间执行,浏览器负责排序,指派某段代码在某个时间点运行
的优先级

1.为什么规定浏览器必须是单线程?
JS主要用途之一是操作DOM,如果JS同时有两个线程,同时对同一个dom进行操作,一个需要删除dom,一个需要添加dom,这时浏览器应该听哪个线程的,如何判断优先级,所以为了简化操作,规定js是一门单线程的语言。

2.有关于js是单线程的理解
所谓的"JS是单线程的"是指解释和执行JS代码的线程,只有一个,一般称之为“主线程”,而浏览器并不是单线程的,是多线程并且是多进程的,而对于前端最关心的还是渲染进程.
js的事件循环(Eventloop) 机制

  1. GUI渲染线程
    ● 负责渲染浏览器界面,解析HTML、CSS,构建DOM树和RenderObject树,布局和绘制
    ● 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
    ● GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中,等到JS引擎空闲时立即被执行。
  2. JS引擎线程
    ● 也称JS内核,负责处理JS脚本程序。例如V8引擎
    ● JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(Renderer进程)中无论什么时候都只有一个JS引擎线程在运行JS程序
    ● GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,页面渲染就不连贯。
  3. 定时触发器线程
    ● 传说中的setInterval和setTimeout所在的线程
    ● 定时器线程其实只是一个计时的作用,他并不会真正执行时间到了的回调,真正执行这个回调的还是JS主线程。当时间到了,定时器线程就通知事件触发线程,让事件触发线程将setTimeout的回调事件添加到待处理任务队列的尾部,等待JS引擎的处理。
    ● W3C在HTML5标准中规定,要求setTimeout中低于4ms的时间间隔算4ms
  4. 事件触发线程
    ● 归属于浏览器而不是JS引擎,用来控制事件循环(可以理解为:JS引擎自己都忙不过来,需要浏览器另开线程协助)
    ● 当JS引擎执行setTimeout时(或者是来自浏览器内核的其他线程,如鼠标点击、ajax异步请求等),当这些事件满足触发条件被触发时,该线程就会将对应回调事件添加到添加到待处理任务队列的尾部,等待JS引擎的处理
    ● 由于JS是单线程关系,所以这些待处理任务队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
  5. 异步http请求线程
    ● 这个线程负责处理异步的ajax请求,当请求完成后如果设置有回调函数,他也会通知事件触发线程,然后事件触发线程将这个回调再放入任务队列中尾部,等待JS引擎执行

3.单线程如何实现异步?
大家都知道JS是单线程的脚本语言,在同一时间,只能做同一件事,为了协调事件、用户交互、脚本、UI渲染和网络处理等行为,防止主线程阻塞,设计者给JS加了一个事件循环(Event Loop)的机制

理解什么是执行上下文?
可以看这篇文章https://amberzqx.com/2020/02/04/JavaScript%E7%B3%BB%E5%88%97%E4%B9%8B%E6%89%A7%E8%A1%8C%E4%B8%8A%E4%B8%8B%E6%96%87%E5%92%8C%E6%89%A7%E8%A1%8C%E6%A0%88/

当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。执行上下文(可执行代码段)总共有三种类型:
全局执行上下文(全局代码):不在任何函数中的代码都位于全局执行上下文中,只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。
函数执行上下文(函数体):只有调用函数时,才会为该函数创建一个新的执行上下文,可以存在无数个,每当一个新的执行上下文被创-建,它都会按照特定的顺序执行一系列步骤。
Eval 函数执行上下文(eval 代码): 指的是运行在 eval 函数中的代码,很少用而且不建议使用

执行上下文又包括三个生命周期阶段:创建阶段 → 执行阶段 → 回收阶段
JS引擎创建了执行上下文栈(执行栈)来管理执行上下文。可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出,后进先出的原则,就像下面的汉诺塔,第一个最大的先进去,当拿出来的时候肯定是最后一个出来的,最小的那个后进去,拿出来的时候是最先拿出来的~因为JS执行中最先进入全局环境,所以处于"栈底的永远是全局执行上下文"。而处于"栈顶的是当前正在执行函数的执行上下文"
js的事件循环(Eventloop) 机制

举个例子:

 const?firstFunction?=?()?=>?{
        ????console.log(‘1‘);
        ????secondFunction();
        ????console.log(‘2‘);
        }
        const?secondFunction?=?()?=>?{
        ????console.log(‘3‘);
        }
        firstFunction();
        console.log(4)

        // 1324
        //从上到下的执行
        //图一:从上到下执行,先是全局作用域,那就是栈底第一个
        //图二: firstFunction的调用,打印出1,现在栈顶是secondFunction,因为函数里面还没有执行完,所以还没有被销毁
        //图三:?secondFunction的调用,打印3,secondFunction,因为函数里面执行完,所以要被销毁到图四
        //再执行栈顶firstFunction里面的2到图5

js的事件循环(Eventloop) 机制
看上面的图是不是对应汉诺塔放进去,拿出来的一个过程

JavaScript 是一个单线程序的解释器,因此一定时间内只能执行一段代码。为了控制要执行的代码,就
有一个 JavaScript 任务队列。这些任务会按照将它们添加到队列的顺序执行如果队列是空的,那么添加的代码会立即
执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行

同步任务,异步任务,任务队列

同步任务指的是,在主线程上,排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程,而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

作者:泡杯感冒灵
链接:https://www.jianshu.com/p/fb1c07b4d90d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

js的事件循环(Eventloop) 机制

上一篇:HTML表单


下一篇:PHP入门之流程控制