• 03-JS事件循环-宏任务与微任务


    1.关于javascript

    javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的,一切javascript多线程都是纸老虎!

    2.javascript事件循环

    既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:

    • 同步任务
    • 异步任务

    当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制,所以我们用导图来说明:

    导图要表达的内容用文字来表述的话:

    • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
    • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
    • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
    • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

    我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

     

    3.Promise与process.nextTick(callback)

    Promise的定义和功能本文不再赘述,不了解的读者可以学习一下阮一峰老师的Promise。而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。

    我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:

    • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
    • micro-task(微任务):Promise,process.nextTick,async 函数 await 下面的代码

    不同类型的任务会进入对应的Event Queue,比如setTimeoutsetInterval会进入相同的Event Queue。

    事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。

    事件循环的进程模型

    • 选择当前要执行的任务队列,选择任务队列中最先进入的任务,如果任务队列为空即null,则执行跳转到微任务(MicroTask)的执行步骤。
    • 将事件循环中的任务设置为已选择任务。
    • 执行任务。
    • 将事件循环中当前运行任务设置为null。
    • 将已经运行完成的任务从任务队列中删除。
    • microtasks步骤:进入microtask检查点。
    • 更新界面渲染。
    • 返回第一步。

    执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务(microTask)队列是否为空,如果为空的话,就执行Task(宏任务),否则就一次性执行完所有微任务。
    每次单个宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask)后,设置微任务(microTask)队列为null,然后再执行宏任务,如此循环。

    例子:

     输出结果:

    1 7 6 8 2 4 3 5 9 11 10 12

     

    4.process.nextTick和Promise都是Microtasks(微任务),为什么process.nextTick会先执行?

    rocess.nextTick 永远大于 promise.then,原因其实很简单。。。在Node中,_tickCallback在每一次执行完TaskQueue中的一个任务后被调用,而这个_tickCallback中实质上干了两件事:

    1.nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)

    2.第一步执行完后执行_runMicrotasks函数,执行microtask(微任务)中的部分(promise.then注册的回调)

    所以很明显 process.nextTick > promise.then

    5.总结

    (1)js的异步

    我们从最开头就说javascript是一门单线程语言,不管是什么新框架新语法糖实现的所谓异步,其实都是用同步的方法去模拟的,牢牢把握住单线程这点非常重要。

    (2)事件循环Event Loop

    事件循环是js实现异步的一种方法,也是js的执行机制。

    (3)javascript的执行和运行

    执行和运行有很大的区别,javascript在不同的环境下,比如node,浏览器,Ringo等等,执行方式是不同的。而运行大多指javascript解析引擎,是统一的。

    (4)setImmediate

    微任务和宏任务还有很多种类,比如setImmediate等等,执行都是有共同点的,有兴趣的同学可以自行了解。

    (5)最后的最后

    • javascript是一门单线程语言
    • Event Loop是javascript的执行机制

     

    6.深入浅出分析process.nextTick() 

    process.nextTick() 是 Node 的一个定时器,让任务可以在指定的时间运行。其中 Node 一共提供了 4 个定时器,它们分别是 setTimeout()、setInterval()、setImmediate()、process.nextTick()。

    process.nextTick() 这个名字有点误导,它是在本轮循环执行的,而且是所有异步任务里面最快执行的。

    Node 执行完所有同步任务,接下来就会执行process.nextTick的任务队列。所以,下面这行代码是第二个输出结果。

     process.nextTick(() => console.log(3));

    基本上,如果你希望异步任务尽可能快地执行,那就使用 process.nextTick。

    根据语言规格,Promise 对象的回调函数,会进入异步任务里面的”微任务”(microtask)队列。
    微任务队列追加在 process.nextTick 队列的后面,也属于本轮循环。所以,下面的代码总是先输出 3,再输出 4。

     process.nextTick(() => console.log(3)); 
     Promise.resolve().then(() => console.log(4)); // 3 // 4

    注意,只有前一个队列全部清空以后,才会执行下一个队列。

     process.nextTick(() => console.log(1)); 
     Promise.resolve().then(() => console.log(2)); 
     process.nextTick(() => console.log(3)); 
     Promise.resolve().then(() => console.log(4)); // 1 // 3 // 2 // 4

    上面代码中,全部 process.nextTick 的回调函数,执行都会早于 Promise 的。


    补充:为什么定时器是宏任务,还老是等到第二轮去执行? 不是说先执行宏任务后是微任务吗? 就很矛盾,原因是啥?

    除了放置异步任务的事件,"任务队列"还可以放置定时事件,setTimeout(fn,0)的含义是,指定任务在主线程最早可得的空闲时间执行,也就是说,尽可能早的执行,他在"任务队列"尾部添加一个事件,因此要等到同步任务和"任务队列"现在的事件执行完以后,才会得到执行。

    意思是,我定时器,先跟主线程说了,你有空的时候去执行,而且你有空必须先执行我,我跟你说一个事件,啥时候去执行我,比如1s后,我说的这个动作就跟预定一下,就算定时器时间设置为0,也会有个最低的40ms,这是人家自己设的规定。那么我就算设置这个最小值,我也是先预定告诉主线程,等你有空了40毫秒后就执行我把,我是VIP。对于定时器来说,他设置这个40ms是他以为时间很短,立马就到他执行了,但是

    定时器只是在指定的时间将事件插入到"任务队列",必须等到执行栈中当前的代码(同步任务以及"任务队列"当前任务)执行完以后,主线程才会执行他指定的回调函数,如果当前代码执行耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定在定时器规定的时间执行。

    前面有很个老大难,主线程一直没忙过来,定时器原来设置的40ms可能要远超这个数。

    我觉得因为定时器先告知了主线程,他告知的这个行为就是“先” 的行为。所以把他归为宏任务。

    https://www.cnblogs.com/lvruifang/p/7200759.html     定时器存在的弊端,以及解决的方法

    https://blog.csdn.net/cxu123321/article/details/85539500   arguments.callee的作用

  • 相关阅读:
    try? try! try do catch try 使用详解
    Swift Write to file 到电脑桌面
    NSLayoutConstraint 使用详解 VFL使用介绍
    automaticallyAdjustsScrollViewInsets 详解
    Swift 给UITableView 写extension 时 报错 does not conform to protocol 'UITableViewDataSource'
    OC Swift中检查代码行数
    Swift中 @objc 使用介绍
    SWift中 '?' must be followed by a call, member lookup, or subscript 错误解决方案
    Swift 中 insetBy(dx: CGFloat, dy: CGFloat) -> CGRect 用法详解
    求1000之内所有“完数”(注:C程序设计(第四版) 谭浩强/著 P141-9)
  • 原文地址:https://www.cnblogs.com/haoqiyouyu/p/14198067.html
Copyright © 2020-2023  润新知