【JavaScript】Event Loop

【JavaScript】Event Loop

原文链接:《从 JS Event Loop 机制看 Vue 中 nextTick 的实现原理》

Event Loop 即事件循环机制,是理解 JavaScript 运行机制的最关键的一点,文章中通过抛出一道题来引入这节课所要讲解的内容。

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

new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});

console.log(5);

// result: 2, 3, 5, 4, 1

单线程的 JavaScript

  • 定义:所谓单线程,是指在 JS 引擎中负责解释和执行 JavaScript 代码的线程只有一个;

  • 特点:JS 运行在浏览器中,是单线程的,每个 window 一个线程;

  • 原因:若为多线程,在 dom 操作中会产生混乱,如 A 线程修改 dom,B 线程却删除了这个 dom;

  • 效率:JavaScript 中有很多其他的类线程,也成为异步事件,如:Ajax请求,监控用户事件,定时器,读写文件等等。

  • 过程:当异步事件发生时,将他们放入执行队列,(主线程)等待当前代码执行完成。就不会长时间阻塞主线程。等主线程的代码执行完毕,然后再读取任务队列,返回主线程继续处理。如此循环这就是事件循环机制。

JavaScript 的内存空间

  • 栈数据结构

    • 结构:栈数据结构
    • 特点:先进后出,后进先出(FILO)
  • 堆数据结构

    • 结构:key - value 结构;
    • 特点:存储的 key - value 是无序的,通过 key 取出,无需关心顺序;
  • 队列数据结构

    • 结构:队列数据结构
    • 特点:先进先出,后进后出(FIFO)

执行上下文 (Execution Context) & 函数调用栈

  • 每当控制器转到可执行代码的时候,就会进入一个执行上下文;
  • 运行环境:
    • 全局环境:JavaScript 代码运行起来会首先进入该环境;
    • 函数环境:当函数被调用执行时,会进入当前函数中执行代码;
  • 栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文;
  • 需要注意的是:函数中,遇到 return 关键字能直接终止可执行代码的执行,因此会直接将当期那上下文弹出栈;
  • 总结:
    • 单线程,依次自顶而下执行,遇到函数就会创建函数执行上下文,并入栈;
    • 同步执行,只有栈顶的上下文处于执行中,其他上下文需等待;
    • 全局上下文只有一个,它在浏览器关闭时出栈;
    • 函数执行上下文的个数没有限制;
    • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

事件循环

  • 在 JavaScript 代码执行的过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列来搞定另一些代码的执行;

  • 特点:即任务队列的特点,先进先出;

  • 图例:队列数据结构

  • 任务队列:一个 JS 文件里事件循环只有一个,但是任务队列可以有多个,因此任务队列可以分为:

    • macro-task (task)

      // macro-task (task) 包括:
      1. setTimeout / setInterval;
      2. setImmediate;
      3. I/O operation;
      4. UI Rendering
      
    • micro-task (job)

      // micro-task (job) 包括:
      1. process.nextTick;
      2. Promise;
      3. Object.observe (已废弃);
      4. MutationObserver(html5新特性);
      
  • 以上这些我们称他们为事件源,事件源作为任务分发器,他们的回调函数才是被分发到任务队列,而本身会立即执行,例如:setTimeout 第一个参数被分发到任务队列,Promisethen 方法的回调函数被分发到任务队列( catch 方法同理);

  • 不同事件源的事件被分发到不同的任务队列,其中 setTimeout 和 setInterval 属于同源;

  • 流程:整体代码开始第一次循环。全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的 job。当所有可执行的 job 执行完毕之后。循环再次从 task 开始,找到其中一个任务队列执行完毕,然后再执行所有的 job,这样一直循环下去。

  • 规律:task–job–task–job…,往复循环直到没有可执行代码

  • 有趣的栗子:

console.log(1);

// 执行到此, Promise 的回调是同步执行,then / catch 才会被分发到 job 中
new Promise(function(resolve){
    console.log(2);
    resolve();
}).then(function(){
    console.log(3)
})

// 执行到此,setTimeout 执行将回调 function 分发至 task 中
setTimeout(function(){
    console.log(4);
    process.nextTick(function(){
        console.log(5);
     })
    new Promise(function(resolve){
        console.log(6);
        resolve()
    }).then(function(){
        console.log(7)
    })
})

// 执行到此, process.nextTick 的回调会被分发到 job 中
process.nextTick(function(){
    console.log(8)
})

// 同 setTimeout 原理相同
setImmediate(function(){
    console.log(9);
    new Promise(function(resolve){
            console.log(10);
            resolve()
        }).then(function(){
            console.log(11)
        })
       process.nextTick(function(){
           console.log(12);
        })
})

// 最后结果: 1, 2, 8, 3, 4, 6, 5, 7, 9, 10, 12, 11
  • 注意:

    • 执行遇到了 PromisePromise 构造函数的回调函数是同步执行;
    • nextTick 任务队列会比 Promise 的队列先执行;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值