重写宏任务与微任务!

事件循环

  • js引擎是单线程的,也就是同一时间只能做同一件事
  • js做的任务分为同步和异步任务,异步的话意思就是一个任务不是连续完成的,而是先执行一段代码,再执行另一段,另一段的代码也称为回调,而同步代码则是一次性完成。
  • 像一些读取文件,网络请求这样的操作,就是一个异步任务,因为中间的操作,需要别的线程或者进程去完成,不需要js引擎,所以这个时候就可以先去完成其他任务,节约时间,等中间操作完成之后,再去执行相应的回调函数就可以了
  • 回调函数分为2种,一种是同步回调,另一种是异步回调,同步回调就是在函数返回前就执行了这个回调函数,异步回调是函数返回之后才执行这个回调函数
  • js异步的实现就是事件队列来完成的

事件循环是由一个队列组成的,异步任务的回调遵循先进先出,在 JS 引擎空闲时会一轮一轮地被取出,所以被叫做循环。

根据队列中任务的不同,分为宏任务和微任务。

js单线程的原因

如果js是多线程的话,对于一个dom元素,如果一个线程删除掉这个元素,另一个线程要修改这个元素,这是就会出现矛盾

js需要异步的原因

js是单线程的,单线程在执行程序时,所走的程序路径按照连续顺序拍下来,前面的处理好才会执行后面的代码,即自上而下执行,若js没有异步的话,当前面的代码执行需要花费很多时间而与后面的代码没有关联,后面的代码就会被堵塞,影响用户体验

宏任务与微任务

  • 主线程在处理任务的时候,很有可能同时添加了新的任务,还有可能通过Io线程进行进程通信ipc添加新任务,所以需要一个消息队列用来存储任务
  • 单线程模式下,有以下要解决的问题
    • 如何处理较高级别的任务,比如一个点击任务,用户点击完需要登台排在这个点击任务之前消息队列里的任务执行完之后再执行,显然很影响体验,为了解决这个问题,引入了微任务。即消息队列中的任务称为宏任务,每个宏任务对应有一个微任务队列,当一个宏任务执行完之后,会先执行这个宏任务的微任务队列,再执行下一个宏任务
    • 如何解决处理单个任务执行时间过长的问题

setTimeout如何实现

  • settimeout这种任务不适合添加在任务队列中,因为并不确定任务队列里的任务还要执行多久,而settimeout的延迟时间是确定的,对此,引入了延迟多列,会将settimeout的回调函数添加进延迟队列中,并记录回调函数被添加的时间和延迟时间
  • 在主线程中每执行完一个宏任务之后,就会根据发起时间和延迟时间计算延迟队列中是否有到期需要执行的任务
  • 定时器还可以取消,给定时器分配一个唯一的Id,但执行到定时器取消的任务时,就从延迟队列中将该任务删除掉
陷阱
  • 执行时间不准确:主进程会将当前任务执行完成之后,再去执行延迟队列的任务,如果当前任务执行的时间超过了定时器设置的延迟时间,就会出现定时器执行的时间不准确

  • 如果定时器存在嵌套调用时,系统会设置最短的调用时间为4ms,在chrome中,如果定时器被嵌套调用超过5次的话,就会认为该函数被阻塞了,如果此时定时器的调用时间间隔少于4ms的时候就会将每次调用的时间设置为4ms。比如下面这段代码,前5次会执行的比较快,因为设置的时间间隔少于4ms,所以后面就会每隔4ms之后再执行函数

    function cb() { setTimeout(cb, 0); }
    setTimeout(cb, 0);
    
  • 延迟执行时间有最大值,浏览器一般以32bit来存储延迟时间的,32bit最多能够存放的数字是2147483647 ms,这就表示如果设置的时间超过这个时间就会溢出,但出现溢出的话,定时器的任务会被立即执行

  • 未激活的页面,setTimeout 执行最小间隔是 1000 毫秒

注意,定时器是一个宏任务,看下下面这段代码

new Promise((resolve) => {
    resolve('OK')
}).then(() => {
    setTimeout(() => {
        console.log('11');
    }, 100)
})

setTimeout(() => {
    console.log(22);
}, 100)

一开始以为会先输出11再是22,因为我想的是当同步代码执行完之后,看到了最下面的定时器是个宏任务所以不去理睬它,先执行then里面的微任务,就把第一个定时器添加到任务队列里,接着再添加第二个定时器。

但这样想是错误的,可能会被之前的延时队列搞混了,正确的应该是当执行完new promise里面的代码之后,看到then里面是微任务,就把它添加到微任务队列中,接着往下执行,就会看到最后一个定时器,就把它加入到宏任务队列中,当执行到下一个宏任务的时候,因为里面有一个需要延迟执行的回调函数,所以会将这个回调函数加入到延迟队列中,接着再以同样操作将第一个定时器的回调函数添加到延迟队列中,所以执行结果是22 11

只有当settimeout这个任务被执行的时候,才会把里面的回调函数添加到执行队列中

浏览器的事件循环

当新打开一个页面的时候,浏览器会为这个页面分配一个渲染进程,渲染进程包括渲染主线程,io线程等,渲染主线程主要是来执行任务的,也可以理解为执行js代码,就包括

  • 渲染dom,如解析dom,计算布局,绘制
  • 用户交互事件,如鼠标点击,页面滚动,放大缩小等
  • js脚本执行事件
  • 网路请求完成,文件读写完成事件

浏览器的事件循环由一个宏任务队列和多个微任务队列组成

  • 首先,先执行一个宏任务,每个宏任务都有自己的微任务队列
  • 当遇到宏任务时,就添加到宏任务队列中,当遇到微任务时,就添加到自己的微任务队列中
  • 同步代码执行完成之后,再执行自己的微任务队列里的任务,这个过程中如果再遇到微任务,还是会添加到自己的微任务队列中

Node的事件循环

node的事件循环跟浏览器的不太一样,他给宏任务和微任务分了等级,也就是如果在一个宏任务队列中出现多个任务时,会按照他们的等级来执行,而不是根据进入队列的时间来执行

  • 宏任务的优先级如下,图是在掘金里面找的:

  • 具体每个任务队列是做什么的,目前还没有深入的了解

  • 在node11之前,会先把一个宏任务队列里的任务全部执行完之后,再去执行微任务队列里的全部任务,但再node11之后,会在一个宏任务执行完之后就去执行微任务队列里的任务

由于对于这一块理解还不是很深刻,日后有机会再来修改!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值