-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JS task到底是怎么运行的 #4
Comments
通过回答JS函数作用域以及变量提升问题。让我对作用域有了更深入的理解,同时认真了解浏览器和规定的差异情况,推荐补充至。 |
Chrome 提交了优化ECMAScript编辑性更改: 以上两种修改会导致同步调用两个async函数时,执行权交换顺序会发生改变。 |
在8张图让你一步步看清 async/await 和 promise 的执行顺序,文章中讨论发现,Chrome的编辑性更改已经在Chrome 72版中实现,并且babel最新版也按照该规范进行编译。 而我的文章中讨论未举例顺序执行:await后面直接返回promise和直接promise微任务的情况(该情况则是Chrome编辑性更改中影响的执行权交换顺序),因此推荐补充至。 |
这次 await 规范的变更分析可以看我这个,不过是英文的,xianshenglu/blog#60 ,或者你直接去看这个决议 tc39/ecma262#1250 |
继续补充关于ES6默认参数作用域的奇特问题,A question about JS parameter scopes, and closures over them vs function scopes |
结果我能推出,但是这解释看不懂... |
现在前端构建的应用越来越复杂,引用的轮子越来越多,分工精细化后,应用开发者理应不关注背后逻辑是怎么发生的,但是目前所有的轮子及功能能否友好的相处还依然未达到生产的稳定要求。
为了提高代码的稳定性及以后写代码少遇点坑,我觉得有必要明白多一点为什么。
前置知识
下面这些内容应用开发者基本不会涉及到,但是我觉得有必要搞清楚。
JS 环境、堆栈的概念
H&S
:堆栈,Heap & StackVO
:变量对象,Variable ObjectAO
:活动对象,Active ObjectSC
:作用域链,Scope ChainEC
:执行环境(或执行上下文),Execution ContextECS
:执行环境栈,Execution Context Stack首先简述这几个概念是什么意思,从哪来的,为啥需要这个东西、干啥用。
H&S (堆栈)
单说堆栈的概念,大家都知道,JS中堆栈是具体干啥的呢。
VO(变量对象)/ AO(活动对象)
SC(作用域链)
作用域链也是指针对象存储在栈中,这个大家可能就不好理解了,域本身是一个范围,怎么就是对象呢。如果不用空间的概念来思考,而是用该空间内具有物来思考,就好理解了。
需要说明的是,函数的
[[Scope]]
在代码被解析时,就被建立,不会改变,取决于文本环境,直到代码片段被出栈。而在函数调用时,则初始化当前Scope
,Scope = AO&VO + [[Scope]]
其中,AO始终在Scope
的最前端。EC(执行上下文)
那什么是可执行代码呢?
可执行代码的类型
也就是说,控制器开始执行上述代码的时候,就进入了一个执行场景,这个执行场景就是EC,目的是告诉你,这个段代码,有什么要求,有什么东西可以用,代码是给谁执行的。
这个好理解,做任务先看说明嘛。那么,这个EC是怎么初始化出来的呢?
EC建立的细节
当函数被调用,但未执行任何其内部代码之前,控制器干了三件事:
到这里大概明白了变量为啥会提升了吧。
ECS(执行环境栈)
准备了那么多,终于可以执行了,但是执行前,先了解一下过程中是怎么存储调用栈的?为什么报错了有的时候能够获取调用栈,有的时候却不行?
据说是MDN上的例子:
当控制器执行函数时,首先它将默认进入全局执行上下文,然后控制器主进程的时序(执行权)将进入被调用的函数,并创建一个新的执行上下文,并将新创建的上下文压入执行栈的顶部。
如果你在当前函数内部调用的其他函数,相同的事情会在此上演。代码的执行流程进入内部函数,创建一个新的执行上下文并把它压入执行栈的顶部。浏览器总会执行位于栈顶的执行上下文,一旦当前上下文函数执行结束,它将被从栈顶弹出,并将上下文控制权交给当前的栈。这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。
也就是说,你在嵌套多层的函数调用时,将会保存调用函数的执行栈,及相应上下文。同时,如果被调用的函数耗时非常久,那么单线程的JS将会卡住。如果一段JS函数存在死循环调用,那么内存将会被该执行栈吃个干净。
说到这些问题,不得不讲下面的内容:事件循环。
JS 事件循环
地球人都知道,JS是单线程,那么JS继续执行异步代码的方式大家也很清楚,通过事件回调函数。但是具体是怎么个回事。函数执行为啥有时候会有点不符合预期,必须要了解一下了。
那么事件队列又是咋回事呢?
事件队列
举个栗子就好理解了:食堂大家都在排队打饭,A(函数)拿出饭盒放到台面上(栈),开始选菜(执行),选到一半或者选完,突然说:“哎,我上铺兄弟让我帮带一份”,边说着又拿出一个饭盒(调用函数)放到台面上(栈),选完菜,到后面的B……
除了一开始的解释即执行的过程,那么响应过程呢?
事件广播
这里没有放入
MutationObserver
,因为MutationObserver
不属于同步事件触发,而是一个异步回调。上面的栗子有些特殊情况,比如:某日教职工交代打饭大妈:“哎,今天领导视察,非要吃食堂,等下领导来了我喊一声,你给领导打一份饭”。
过了一会……
“领导驾到!”,教职工喊道,打饭大妈一个激灵,赶紧帮眼前的同学打好饭,一看没人排队了,赶紧打了一份大餐找了个人给领导端了过去……
除了突然触发的事件,还有定时触发的情况,那么就要说另一个计时器了。
计时器
继续上面的栗子:每天三餐外加宵夜……就是定时器任务……
那么上面这些函数调用执行执行时,是如何在主线程中顺序执行呢?
任务和微任务
W3C规定:
一次事件循环包括:执行tasks,检查Microtasks队列并执行,执行UI渲染(如果需要)。
tasks任务包括:函数、加入队列的事件回调。
Microtasks任务包括:
Promise.then
、MutationObserver
回调、process.nextTick
。也就是说,一段代码会被分为tasks部分和Microtasks部分,执行完后,对该段代码内的UI变更进行处理(不包含内部为了执行代码立即进行的重绘),很明显,Microtasks就是为了实现异步操作而设计的。
同时W3C又做了如下规定:
第一段说,可以跳过UI渲染继续执行:例如:
setImmediate
,第二段说,浏览器可能在JS运行时强制渲染:获取offset等参数时;这些按下不表,有兴趣可以关注W3C原文,或看这篇解析,这里要说的是代码的划分执行顺序:栗子:
上面这个栗子说明,
MutationObserver
和Promise
执行后,回调均被放置在本次事件循环
tasks之后的Microtasks队列里,并依次执行。栗子2:
这个就很明显了,
setTimeout
被放到了下一轮事件循环(由计时器线程触发)。到上面为止,基本上都还符合预期,下面说一个特殊情况。
async/await
大家都喜欢用new API,好用简便,不用回调不用一直
next()
,但是这个栗子1:喵喵喵??和说好都不一样呢,怎么后面的先执行了?
其实这种情况在前端代码里很常见,同步函数和普通函数混用时,如果没搞清楚就可能出现这个问题。
W3C是这么说的:
由于
Promise.then
属于Microtasks,async/await
则将这之后全部代码推入了Microtasks队列变成子任务执行,并转移执行权,达到不阻塞的效果。事实是这样吗?再看一个栗子2:
惊不惊喜?意不意外?
async/await
不仅将Promise.then
扔到Microtasks,Microtasks中执行一个task后,后续非Microtasks代码则被当成另外个子任务重新排队了,并再次交换执行权……再看一个栗子3:
执行权就如击鼓传花般……,同时两个同步函数利用Microtasks在内部保持同步执行的情况下,形成了一个微妙的“双线程同步执行”的情况。
再复杂一点,栗子4:
这次发现,由于Microtasks没有待执行任务,该此事件循环结束。但由于函数进入了计时器线程,响应的执行栈被缓存下来,因此被加入下次事件循环后,Microtasks任务队列也被加入了下次事件循环。
这个问题到这里基本上满足调试需求了,不过还没讨论浏览器差异……
结合上面这些栗子,再来思考一个问题:
是不是觉得豁然开朗了?
这里还有一个很有意思的探讨:Tasks, microtasks, queues and schedules,里面讨论了DOM事件触发及浏览器差异,有兴趣可以了解看看。
开启多线程
to be Continued
参考:
深入理解JavaScript执行上下文、函数堆栈、提升的概念
javascript 执行环境,变量对象,作用域链
ES6入门
并发模型与事件循环
event
node 事件机制
理解javascript定时器中的单线程
通过了解渲染过程来提高页面性能
Mutation Observer API
The text was updated successfully, but these errors were encountered: