须要铺垫的知识点:执行环境 + 执行栈web
执行环境
是函数被调用时所在的环境。执行环境中存储了函数执行时相关的全部事物。当咱们在函数内访问某一变量时,这个变量其实也是该函数执行环境提供的。由于执行环境是不能直接被访问的(不能被访问表明咱们在函数内部不能使用任何变量),因此ECMA
标准在函数被调用时,会构造一个可以被访问的对象——执行环境对象(Execution Context Object),这个对象上包含三个属性:变量对象、做用域链、this的值。浏览器
这三个属性的做用:数据结构
在全局执行环境中,VO 就是全局对象。在函数执行环境中,由于 VO 不能被直接访问的,这是会提供一个活动对象(Activation Object)简称 AO 来扮演 VO 的角色。AO 在进入函数执行环境的那一刻被建立。 函数接收到的参数,存储在变量对象的
arguments
属性上。 函数内部定义的变量和函数,都会在变量对象上拥有一个同名属性,变量的属性值为变量值,函数的属性值为该函数的指针。多线程
做用域是用来查找标识符的一种规则。决定了在当前函数内声明的标识符的可做用范围。 若是发生做用域嵌套,则会造成做用域链。做用域链包括当前做用域(也就是当前执行环境的变量对象)加上外层做用域链。 每一个函数对象的做用域链都被存储在其内部属性
[[Scope]]
上,做用域链上的外部做用域是经过复制外部函数的[[Scope]]
属性构成的。 即Scope Chain = [AO].concat([[Scope]])
dom
当函数做为某个对象的属性调用时,
this
指向那个属性。当函数自主调用时,this
指向undefined
,在非严格模式下,this
又指向全局对象,在浏览器中则是window
对象。异步
执行环境分为三种:async
window
为全局执行环境的变量对象,全部变量和函数都是定义在window 对象上的某个属性。 全局执行环境是一直存在的,直到浏览器关闭窗口后,才会被销毁。
浏览器用 JS 引擎执行 JS 代码,而 JS 引擎构建出执行栈,用来记录程序运行状况。执行栈遵循栈数据结构,先进先出,每当遇到一个函数调用,就会建立出其执行环境压入执行栈栈顶,当函数执行完成后将其推出执行栈。保证执行流按照执行栈的顺序有序执行。编辑器
须要铺垫的知识点:浏览器的多个线程函数
JS 是单线程语言意味着只会有一条线程在执行 JS,一次也只会执行一个 JS 任务,其他任务都须要排队等待上一个任务执行完毕才能执行。oop
同步:每一个任务按照顺序进行执行,必须等待前一个任务执行完毕,后一个任务才能够开始。
异步:能够将一个任务分红两段,先执行第一段,而后执行其余任务,等作好了准备,再来执行第二段。
单线程的特色就是同一时间只能作一件事情,而 JS 是被做为浏览器脚本语言使用的,主任务是提供用户与页面交互的能力。设想一下若是浏览器是多线程,有两个线程同时在对一个 dom 进行操做,这时浏览器就会不知道以哪一个线程为准。
咱们知道,浏览器上有不少操做是很耗时的,好比请求数据、加载媒体文件等,若是都是同步任务,则须要等待一个一个耗时操做的完成,而相对次要的耗时操做其实不该该让用户有等待的感受,用户体验会很是的很差。因此会经过一些其余线程来实现异步的形式。
至关于执行栈与以上三个线程的工做流程
主线程在执行栈和任务队列进行反复轮询的过程就是咱们常说的 JS 执行机制 —— 事件循环(Event Loop)。
JS 又把异步任务分为了2类:宏任务 + 微任务
全部异步任务一开始都会被汇总到一个事件列表(Event Table)中,当知足塞入事件队列(Event Queue)的条件时(好比异步请求完成、定时任务到达时间等),会将事件从 Event Table 中取出,并按照该异步任务类型将其回调函数放入到宏任务事件队列或微任务事件队列中,JS 引擎存在monitoring process
进程,会持续不断的检查主线程执行栈是否为空,一旦为空读取事件队列时,会优先读取微任务事件队列上的全部事件,并压入执行栈开始执行,然后执行栈又为空时再读取宏任务事件队列列头的第一个任务压入执行栈,开启第二轮事件循环。
在执行机制上两者的区别:
微任务处理优先级高于宏任务。 读取微任务事件队列时会一会儿读取一整个队列,而读取宏任务事件队列时只会取出第一个任务压入执行栈执行。
常见的一些异步任务分类
script
中的代码、
setTimeout
、
setInterval
Promise
Javascript 任务分为同步任务和异步任务,同步任务是前一个任务结束后一个任务才能开始。异步任务则不用等前一个任务完成就能够开始。
而异步任务又包括异步请求、异步回调(dom操做的回调、定时任务的回调)等,这些任务都会被放置在任务队列中。
对于异步请求和定时任务会先交给浏览器代为处理,等处处理完毕后将异步任务的回调函数推入任务队列的末尾,等待主线程读取。
讲到主线程和执行栈,就有一个不得不提的内容 async/await
async
函数会返回一个Promise
对象,便于回调函数管理,await
是一个运算符,用于组成表达式,await xxx
的计算结果取决于await
它等待的东西,也就是xxx
。若是它等待的不是一个Promise
,那么它的计算结果就是它等待的东西。
当await
等待的是一个Promise
时,它会对当前await
后面声明的代码进行阻塞,直到Promise
返回后才会继续执行后续代码。
当执行流遇到await functionXX(): Promise<any>
时,由于发生了函数调用,因此functionXX
会被压入执行栈,执行流会进入到函数内部,将return Promise
以外的代码先执行一遍,Promise
相关的异步操做会交由浏览器执行。
当咱们设定了一个 10s 的定时任务,浏览器定时触发线程中的计数器在 10s 后准时的将定时任务的回调函数添加到任务队列末尾。
到这一步都是很正常的,可是这个被添加进消息队列的回调函数何时会被读取呢?
只有当主线程执行完全部任务后才会依次读取任务队列中的任务。
也就是说虽然浏览器按时的将定时任务发送到了任务队列中,但真正被主线程读取的时间可能超过了设定的时间。这也就是为何有些定时任务被执行的时间和设定时间不一致的缘由。
本篇内容以理论为主,后续还会开一篇内容专门用代码作例子分析。本文的所有内容,纯本人理解后手敲的文字,有不对的地方,欢迎指出和纠正,感谢阅读,欢迎点赞 🦀🦀
本文使用 mdnice 排版