Javascript 异步实现 & 事件循环

不问归期 提交于 2020-01-24 22:37:20

浏览器的进程和线程

一、js的单线程和异步

  • js的单线程(single threaded)和异步(asynchronous)两个基本矛盾的概念是怎么整合到js上的
  • 首先必须肯定js本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的
  • 宿主环境通过某种方式(事件驱动)使得js具备了异步的属性

二、浏览器的进程

  • 浏览器的主进程:负责协调、主控
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  • GPU进程:最多一个,用于3D绘制等
  • 浏览器渲染进程(浏览器内核):主要作用为页面渲染,脚本执行,事件处理等

三、浏览器内核线程

1、JS引擎线程

  • JS引擎一直等待着任务队列中任务的到来,然后加以处理
  • 一个Tab页中无论什么时候都只有一个JS引擎在运行JS程序

2、GUI渲染线程

  • GUI渲染线程与JS引擎线程是互斥的
  • 当JS引擎执行时GUI线程会被挂起
  • GUI更新会被保存在一个队列中等到JS引擎空闲时执行

3、定时器线程

  • 传说中的setInterval与setTimeout所在线程
  • 二者的定时计数功能不是由JS引擎完成的,而是通过单独的线程来完成的,从而保证计时准确
  • 计时完毕,将回调添加到事件队列中,等待JS引擎空闲执行

4、事件触发线程

  • 点击事件等会在对应条件触发的时候被添加到事件队列,等js引擎空闲执行
  • 说白了就是内核通过一个单独的线程来进行事件的监听和处理

5、异步HTTP请求线程

  • 在发送http请求的时候,这个线程会被执行
  • 这个线程会把回调的事件放入到事件队列中等待js引擎执行

总结:浏览器多进程,浏览器内核多线程


事件循环(JS执行机制)

一、同步和异步任务

  • javascript是一门单线程语言,所有任务需要排队,前一个任务结束,才会执行后一个任务
  • 如果上一个任务的执行过程耗时很长,下一个任务就不得不等待很长的时间,所以任务分成了 同步任务异步任务
  • 同步任务在主线程上是一个个执行,而异步任务则会进入任务队列(task queue),只有在所有同步任务执行完,异步任务才会被执行

1、同步异步任务的执行机制

  • js中的异步的代码主要是定时器、事件响应回调、异步请求(ajax)
  • 上面的三者都会在 Event Table 中注册回调函数,适当的时候(定时到时、事件触发、请求完成)会将这些函数推入到Event Queue,等待js引擎的执行
    在这里插入图片描述

图片引用自:https://juejin.im/post/59e85eebf265da430d571f89

2、setTimeout & setInterval

  • 二者并不能保证到了相应的时间后会立即执行
  • 比如下面的这段代码并不是 3 秒之后就能输出setTimeout里面的内容,遇到setTimeout,会将里面的任务注册到 Event Table,这里的定时3秒,意思是3秒之后会将这个任务推入到 Event Queue 等待js引擎的执行,但是js引擎空闲的时候才能执行它呀,要是一直有一个比较耗时的任务一直霸占着js引擎的话,就不能保证这个定时任务到底什么时候执行了
  • setInterval 也是一样的,这就导致了一旦setInterval的回调函数执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了
function sleep(delay) {
  var start = (new Date()).getTime();
  while ((new Date()).getTime() - start < delay) {
    continue;
  }
}

setTimeout(() => {
    console.log('test-----');
},3000)

sleep(10000)

二、宏任务和微任务

  • 看到有的文章里面说宏任务和微任务是异步任务的两种,感觉不是很准确,个人觉得同步异步任务和宏微任务仅仅是从两个不同的维度对js的任务进行划分而已

1、常见的宏任务和微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick(Node里面的内容)

2、js事件循环的简单描述

  • 宏任务和微任务在js 的执行过程中相应的二者分别进入宏任务和微任务队列
  • 一次完整事件循环过程:宏任务-----清空微任务队列(全部执行)----UI渲染-----再取一个宏任务执行(开始下一次循环)
注意点
  • 宏任务是一次取一个进行执行,首次执行代码可以看成是一次宏任务
  • 微任务的执行时机是宏任务执行结束后到UI渲染的间隙
  • 一次循环的过程,微任务队列会被清空,也就是全部执行完
    在这里插入图片描述

三、一个示例展示事件循环的过程

  • 第一次循环的输出是:1、7、6、8
  • 第二次循环的输出是:2、4、3、5
  • 第三次循环的输出是:9、11、10、12
console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

参考文章:
https://www.cnblogs.com/hhmm/p/10340542.html
https://blog.csdn.net/lay136362687/article/details/81198076
https://juejin.im/post/59e85eebf265da430d571f89

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!