美文网首页
javascript中的Event Loop详解

javascript中的Event Loop详解

作者: lihuanji | 来源:发表于2018-01-20 21:56 被阅读0次

    首先来一段代码开篇

        console.log(1);
        
        setTimeout(function() {
          console.log(2);
        });
        
        function fn() {
            console.log(3);
            setTimeout(function() {
              console.log(4);
            }, 2000);
        }
        
        new Promise(function(resolve, reject){
            console.log(5);
            resolve();
            console.log(6);
        }).then(function() {
           console.log(7);
        })
        
        fn();
        console.log(8);
    

    思考一下,能给出准确的输出顺序吗?

    下面一步步的了解,最后看看这块代码怎么去执行的。

    1.进程,单线程与多线程

    进程: 运行的程序就是一个进程,比如你正在运行的浏览器,它会有一个进程。

    线程: 程序中独立运行的代码段。

    一个进程由单个或多个线程组成,线程是负责执行代码的。

    学过JS的想必都知道JS是单线程的,那么既然有单线程就有多线程,下面首先看看单线程与多线程的区别。

    • 单线程
      从头执行到尾,一行一行执行,如果其中一行代码报错,那么剩下代码将不再执行。同时容易代码阻塞。

    • 多线程
      代码运行的环境不同,各线程独立,互不影响,避免阻塞。

    2. Event Loop(浏览器)

    js既然是单线程,那么肯定是排队执行代码,那么怎么去排这个队,就是Event Loop。虽然JS是单线程,但浏览器不是单线程。浏览器中分为以下几个线程:

    • js线程
    • UI线程
    • 事件线程(onclick,onchange,...)
    • 定时器线程(setTimeout, setInterval)
    • 异步http线程(ajax)

    其中JS线程和UI线程相互互斥,也就是说,当UI线程在渲染的时候,JS线程会挂起,等待UI线程完成,再执行JS线程

    • JS会存在执行栈,从上至下执行js代码,当遇到异步api时,列如上面所述的各种非JS线程的事件,那么会扔给对应的线程去处理,等处理完毕后,则把回调函数放入事件队列中,等待执行栈执行完毕,再去读取事件队列中的回调函数执行。
      [图片上传失败...(image-fd34d8-1516456564422)]

      • 当一个函数执行,会产生一个新的执行栈,当执行完毕返回上一层执行栈,直到回到全局执行栈
      • 当一个函数调用自己,会产生一个新的执行栈。

    整个过程,执行栈,读取事件队列就是Event Loop

    • 再来看看promise, 如果对promise不是很了解的同学可以看看另一篇我写的文章Promise是个什么鬼?实现一个Promise.

      Promise在整个执行中是个特殊的存在,传入Promise的fn是在当前执行栈中的,会立即执行,但它的then方法是在执行栈之后,事件队列之前,当然这个和浏览器实现有关,大部分浏览器是微任务(Microtask),也有浏览器放入了宏任务(Macrotask),chorme大哥是放入了微任务,其他纷纷效仿。那大家可能会问什么是微任务?什么是宏任务了?

      • 宏任务(Macrotask) 也就是上面所说的 事件队列 callback queue
      • 微任务(microtask) 是在执行栈和事件队列之间 在执行栈之后先清空在微任务中的任务,再去执行事件队列

    3. Node Event Loop

    Nodejs是通过V8引擎去解析的,解析后的代码会去调用node提供的api执行,这些API由libuv这个库去分配线程执行,最后异步返回给V8引擎。

    在Node中提供了2个方法和我们的执行队列有关

    • process.nextTick

    把方法放入执行栈的底部,并不放入宏任务和微任务

       cosnole.log(1);
       
       process.nextTick(function(){
           console.log(2);
       });
       
       new Promise(function() {
           console.log(3);
       }).then(function() {
           console.log(4);
       })
       
       console.log(5);
    

    因为nextTick是放入了执行栈的底部,那么会优先于Promise的then方法,故输出为1 3 5 2 4

    • setImmediate

    把方法放入宏任务的队列中去,但有一个奇怪的事发生,看下面代码:

       setImmediate(function() {
           console,log(1);
       });
       
       setTimeout(function() {
           console.log(2);
       }, 0);
    

    大家可以试试把代码多次执行,发现输出顺序不一定,他们都是放入了宏任务中,但在node文档中,setImmediate总是排在setTimeout前面,但是在实际中确不一定,不知道是不是一个bug。

    4. 讲讲setTimeout, setInterval

    • 任务队列与定时器
      上面讲到了定时器都是放入了宏任务。如果当前执行栈消耗时间已经大于我们设置的定时器时间,那么定时器的回调在宏任务里,并没有及时去调用,所有这个时间不是特别准确。
        setTimeout(function(){
            console.log(1);
        }, 2000);
        
        task();
    

    假设task函数执行需要5秒钟,那么打印1需要在5秒之后再打印,task占用了当前执行栈,要等执行栈执行完毕后再去读取微任务,等微任务完成,这个时候才会去读取宏任务里面的setTimeout回调函数执行。setInterval同理,例如每3秒放入宏任务,也要等到执行栈的完成。

    • 定时器自身
      有时候为了延后执行代码会写:
        setTimeout(function() {
            console.log(1);
        },0);
    

    但是根据标准这个时候最低是4毫秒,即便现在执行栈已经完成。0是不成立的。写0浏览器为默认为最低毫秒数。

    5. 回到开篇的代码

    现在再回到上面的代码,有答案了吗?

        // 非异步api,立即执行
        console.log(1);
        
        // 放入全局宏任务
        setTimeout(function() {
          console.log(2);
        });
        
        // 声明函数,但暂时未调用,不会立马形成执行栈
        function fn() {
             // 调用fn时立即执行
            console.log(3);
            
            // 放入当前fn执行栈宏任务
            setTimeout(function() {
              console.log(4);
            }, 2000);
        }
        
        new Promise(function(resolve, reject){
            // task任务立即执行
            console.log(5);
            resolve();
            console.log(6);
        }).then(function() {
           // then方法放入微任务
           console.log(7);
        })
        
        // 调用fn进入下个执行栈
        fn();
        
        // fn执行栈完成执行
        console.log(8);
    

    答案就是 1 5 6 3 8 7 2 4

    相关文章

      网友评论

          本文标题:javascript中的Event Loop详解

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