javascript的事件循环机制个人总结

165 阅读4分钟

事件循环机制

javascript语言的单线程和非阻塞特性

  1. javascript是单线程的,这样设计的好处就是不必担心并发上的问题。原因是他本就是为和用户交互而创造的,如过是多线程的-1个线程删除dom 一个线程增加dom 这种冲突操作以谁为准?所以单线程就没有这种线程间冲突的问题。只需要关心别做出阻塞线程的事情即可(如网络请求 死循环..),大大简化了编程方式。
  2. 非阻塞特性 .非阻塞就是一个异步函数的执行条件不会去占用主线程,比如一个定时3s的定时器,执行条件为3s-事件循环机制不会让他在主线程身上等待3s 而是会让他在其他地方去等,一旦执行条件3s到了之后再去将入到执行堆栈中。就不会造成异步函数的执行条件白白占用线程浪费资源的情况-这就是非阻塞。javascript的非阻塞特性是由事件循环(event loop)机制去实现的。

事件循环

javascrpit的事件循环分为浏览器环境下和nodejs环境下,两种环境的实现不同。

浏览器环境

事件循环组成的关键概念

  • 执行堆栈 (堆:heap 栈:stack) 先进先出顺序原则 不管是同步任务还是异步任务最终都要推到这里给主线程去执行。
  • 事件队列(callback queue)遇到异步任务会不会立马将其加入到执行栈,而是先挂起,等到异步任务返回结果加入到事件队列中去。

这两个概念是事件循环机制的重要组成。具体的机制就是 当遇到同步代码就将其加入到执行堆栈中,遇到异步任务就会先将其挂起,等到他的返回结果就是异步执行条件完成就加入事件队列。当执行栈当中的同步任务执行完毕是清空状态时就会去事件队列查找是否有等待执行的异步回调-如果有就推到执行栈执行。就在执行堆栈和事件队列反复如此,知道所有任务都被执行。

宏任务和微任务

以上的事件循环机制的描述较为宏观,实际上异步任务还分为宏任务和微任务,也就是说事件队列还分为微任务队列和红任务队列。这两个概念主要是解决异步任务之间的执行顺序问题。

特性

  • 是按照异步任务类型去区分宏任务还是微任务的

    1. 整体代码script 定时器 setImmediate 都属于宏任务
    2. promise.then MutationObserver node环境的process.nextTick等都属于微任务(new promise可是同步任务).
  • 微任务早于宏任务执行 因为script顶层也算作一个宏任务,所以其实事件循环可以看做一个一个的宏任务内部执行栈和微任务队列的循环,每一个宏任务包含很多同步任务和很多微任务,当前宏任务的微任务永远早于下一个宏任务的执行.

所以以上的关于事件循环的宏观描述可以再细节一些就是第一轮循环执行script宏任务--执行同步任务--遇到微任务加入到微任务队列,宏任务加入宏任务队列--同步任务执行完 去按照微任务队列的顺序执行,微任务队列也被清空就去宏任务队列 将其最前的添加到执行栈 如此反复...

举一个浏览器环境的运行例子:

    <script>
        console.log('1');

        setTimeout(function () {
            console.log('2');

            new Promise(function (resolve) {
                console.log('4');
                resolve();
            }).then(function () {
                console.log('5')
            })
        })

        new Promise(function (resolve) {
            console.log('7');
            resolve();
        }).then(function () {
            console.log('8')
        })

        setTimeout(function () {
            console.log('9');

            new Promise(function (resolve) {
                console.log('11');
                resolve();
            }).then(function () {
                console.log('12')
            })
        })
    </script>

执行第一个宏任务script-执行宏任务的同步任务1 和new promise的7,遇到了微任务then放到微任务队列--打印完1和7没有同步任务执行栈为空了--去当前宏任务的微任务队列 发现有一个then--推到执行栈 输出8 --发现微任务队列也空了---第一个宏任务的循环完毕 开始下一个宏任务的循环即第一个settimeout 同理输出2 4 5,执行第三个宏任务 同理输出9 11 12 .

最终结果即1 7 8 2 4 5 9 11 12