美文网首页
JavaScript事件循环

JavaScript事件循环

作者: 凝固热 | 来源:发表于2021-07-25 20:08 被阅读0次

    为什么js是单线程

    JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

    所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    任务队列

    单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

    在CPU过于忙碌的情况下,比如IO设备很慢,这时候不得不等IO结束,才能继续下一个任务。

    预算,所有任务分为了两种,一种是同步任务,另一种是异步任务

    同步任务指的是在主线程上排队执行任务,前一个任务执行完才能进行下一个任务;
    异步任务则不一样,不进到主线程,而且进入任务队列的任务,只有任务队列通知主线程,异步任务才可以执行,该任务才会进入到主线程执行。

    异步执行的运行机制如下:

    • 所有同步任务都在主线程上执行,形成一个执行栈
    • 主线程之外,存在一个任务队列。只要异步任务有个运行结果,就在任务队列中放置一个事件
    • 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
    • 主线程不断重复上面的第三步
    事件循环

    事件和回调函数

    任务队列是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在任务队列中添加一个事件,表示相关的异步任务可以进入执行栈了。主线程读取任务队列,就是读取里面有哪些事件。

    任务队列中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入任务队列,等待主线程读取。

    所谓回调函数,就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数

    事件循环

    主线程从任务队列中读取事件,这个过程是循环不断的,这种运行机制成为事件循环

    Nodejs也是单线程的事件循环,但机制不同于浏览器环境

    • v8引擎解析js脚本
    • 解析后的代码,调用Node API
    • libuv库负责Node API的执行,将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给v8引擎
    • v8引擎再将结果返回给用户

    宏任务和微任务

    每个线程都有自己的事件循环,所有都能独立运行,然而同源窗口共享一个事件循环以同步通信。事件循环一直运行,来执行进入任务队列中的宏任务。

    宏任务

    浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
    鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。

    微任务

    微任务通常来说就是需要在当前 task 执行结束后立即执行的任务,比如对一系列动作做出反馈,或或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。

    解析如下例子:

    setTimeout(() => {
        console.log('5')
    }, 0)
    console.log('1');
    
    new Promise((resolve) => {
        console.log('2');
        resolve()
    }).then(() => {
        console.log('3');
    }).then(()=>{
        console.log('4')
    })
    
    • 首先浏览器执行js进入第一个宏任务进入到主线程,遇到setTimeout分发到宏任务队列中
    • 遇到console.log()直接执行, 输出1, 实例化的Promise方法里面的console.log也可直接执行, 输出2
    • 判断是否有可执行的微任务,当前微任务队列有两个then里面的回调方法,先后打印3, 4
    • 微任务全部执行完了,开始新的一个宏任务,而这个宏任务就是一开始加入到宏任务队列中的任务,放入到主线程执行,打印出5
    宏任务与微任务

    相关文章

      网友评论

          本文标题:JavaScript事件循环

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