本系列最开始是为了本身面试准备的.后来发现整理愈来愈多,差很少有十二万字符,最后决定仍是分享出来给你们.javascript
为了分享整理出来,花费了本身大量的时间,起码是只本身用的三倍时间.若是喜欢的话,欢迎收藏,关注我!谢谢!html
前端面试查漏补缺--Index篇(12万字符合集) 包含目前已写好的系列其余十几篇文章.后续新增值文章不会再在每篇添加连接,强烈建议议点赞,关注合集篇!!!!,谢谢!~前端
后续还会继续添加设计模式,前端工程化,项目流程,部署,闭环,vue常考知识点 等内容.若是以为内容不错的话欢迎收藏,关注我!谢谢!vue
目前本人也在准备跳槽,但愿各位大佬和HR小姐姐能够内推一份靠谱的武汉 前端岗位!邮箱:bupabuku@foxmail.com.谢谢啦!~java
相信你们若是对Event loop有必定了解的话,大概都会知道它的大概步骤是:面试
上面的步骤说得没错,很对,可是太浅了.如今的面试应该不会这么简单,起码得加上宏任务,微任务.再整上几个Promise,async await,让你判断.不然都很差意思叫面试题!ajax
为了不被面试官吊起来打的状况,咱们如今来详细地理一理Event Loop.算法
Javascript 有一个 主线程(main thread)和 调用栈(call-stack),全部的代码都要经过函数,放到调用栈(也被称为执行栈)中的任务等待主线程执行。设计模式
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。前端工程化
MDN的解释: Web 提供了各类各样的 API 来完成各类的任务。这些 API 能够用 JavaScript 来访问,令你能够作不少事儿,小到对任意 window 或者 element作小幅调整,大到使用诸如 WebGL 和 Web Audio 的 API 来生成复杂的图形和音效。
总结: 就是浏览器提供一些接口,让JavaScript能够调用,这样就能够把任务甩给浏览器了,这样就能够实现异步了!
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,"任务队列"上第一位的事件就自动进入主线程。可是,若是存在"定时器",主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
Javascript单线程任务被分为同步任务和异步任务.
在JavaScript
中,任务被分为两种,一种宏任务(MacroTask
)也叫Task
,一种叫微任务(MicroTask
)。
宏任务(MacroTask)
script(总体代码)
、setTimeout
、setInterval
、setImmediate
(浏览器暂时不支持,只有IE10支持,具体可见MDN
)、I/O
、UI Rendering
。微任务(MicroTask)
Process.nextTick(Node独有)
、Promise
、Object.observe(废弃)
、MutationObserver
(具体使用方式查看这里)首先宏观上是按照这样的顺序执行.也就是前面在"Event loop的初步理解"里讲到的过程
注意:
前面介绍宏任务的时候,提过script也属于其中.那么一段代码块就是一个宏任务。故全部通常执行代码块的时候,先执行的是宏任务script,也就是程序执行进入主线程了,主线程再会根据不一样的代码再分微任务和宏任务等待主线程执行完成后,不停地循环执行。
主线程(宏任务) => 微任务 => 宏任务 => 主线程
事件循环的顺序是从script开始第一次循环,随后全局上下文进入函数调用栈,碰到macro-task就将其交给处理它的模块处理完以后将回调函数放进macro-task的队列之中,碰到micro-task也是将其回调函数放进micro-task的队列之中。直到函数调用栈清空只剩全局执行上下文,而后开始执行全部的micro-task。当全部可执行的micro-task执行完毕以后。 接着浏览器会执行下必要的渲染 UI,而后循环再次执行macro-task中的一个任务队列,执行完以后再执行全部的micro-task,就这样一直循环。
注意: 经过上述的 Event loop 顺序可知,若是宏任务中的异步代码有大量的计算而且须要操做 DOM 的话,为了更快的 界面响应,咱们能够把操做 DOM 放入微任务中。
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
复制代码
这里须要先理解async/await
。
async/await
在底层转换成了 promise
和 then
回调函数。 也就是说,这是 promise
的语法糖。
每次咱们使用 await
, 解释器都建立一个 promise
对象,而后把剩下的 async
函数中的操做放到 then
回调函数中。
async/await
的实现,离不开 Promise
。从字面意思来理解,async
是“异步”的简写,而 await
是 async wait
的简写能够认为是等待异步方法执行完成。
promise1
和promise2
,再执行async1
。async1
再执行promise1
和promise2
。主要缘由是由于在谷歌(金丝雀)73版本中更改了规范,以下图所示:
RESOLVE(thenable)
和之间的区别Promise.resolve(thenable)
。await
的值被包裹在一个 Promise
中。而后,处理程序附加到这个包装的 Promise
,以便在 Promise
变为 fulfilled
后恢复该函数,而且暂停执行异步函数,一旦 promise
变为 fulfilled
,恢复异步函数的执行。await
引擎必须建立两个额外的 Promise(即便右侧已是一个 Promise
)而且它须要至少三个 microtask
队列 ticks
(tick
为系统的相对时间单位,也被称为系统的时基,来源于定时器的周期性中断(输出脉冲),一次中断表示一个tick
,也被称作一个“时钟滴答”、时标。)。async function f() {
await p
console.log('ok')
}
复制代码
简化理解为:
function f() {
return RESOLVE(p).then(() => {
console.log('ok')
})
}
复制代码
RESOLVE(p)
对于 p
为 promise
直接返回 p
的话,那么 p
的 then
方法就会被立刻调用,其回调就当即进入 job
队列。RESOLVE(p)
严格按照标准,应该是产生一个新的 promise
,尽管该 promise
肯定会 resolve
为 p
,但这个过程自己是异步的,也就是如今进入 job
队列的是新 promise
的 resolve
过程,因此该 promise
的 then
不会被当即调用,而要等到当前 job
队列执行到前述 resolve
过程才会被调用,而后其回调(也就是继续 await
以后的语句)才加入 job
队列,因此时序上就晚了。PromiseResolve
的调用来更改await
的语义,以减小在公共awaitPromise
状况下的转换次数。await
的值已是一个 Promise
,那么这种优化避免了再次建立 Promise
包装器,在这种状况下,咱们从最少三个 microtick
到只有一个 microtick
。73如下版本
script start
,调用async1()
时,返回一个Promise
,因此打印出来async2 end
。await
,会新产生一个promise
,但这个过程自己是异步的,因此该await
后面不会当即调用。Promise
和script end
,将then
函数放入微任务队列中等待执行。null
,而后按照先入先出规则,依次执行。promise1
,此时then
的回调函数返回undefinde
,此时又有then
的链式调用,又放入微任务队列中,再次打印promise2
。await
的位置执行返回的 Promise
的 resolve
函数,这又会把 resolve
丢到微任务队列中,打印async1 end
。setTimeout
。谷歌(金丝雀73版本)
await
的值已是一个 Promise
,那么这种优化避免了再次建立 Promise
包装器,在这种状况下,咱们从最少三个 microtick
到只有一个 microtick
。await
创造 throwaway Promise
- 在绝大部分时间。promise
指向了同一个 Promise
,因此这个步骤什么也不须要作。而后引擎继续像之前同样,建立 throwaway Promise
,安排 PromiseReactionJob
在 microtask
队列的下一个 tick
上恢复异步函数,暂停执行该函数,而后返回给调用者。具体详情查看(这里)。
Node.js的运行机制以下。
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不一样的任务分配给不一样的线程,造成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。
Node 的 Event loop 分为 6 个阶段,它们会按照顺序反复运行
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
复制代码
Node
的Event loop
一共分为6个阶段,每一个细节具体以下:timers
: 执行setTimeout
和setInterval
中到期的callback
。pending callback
: 上一轮循环中少数的callback
会放在这一阶段执行。idle, prepare
: 仅在内部使用。poll
: 最重要的阶段,执行pending callback
,在适当的状况下回阻塞在这个阶段。check
: 执行setImmediate
(setImmediate()
是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成以后当即执行setImmediate
指定的回调函数)的callback
。close callbacks
: 执行close
事件的callback
,例如socket.on('close'[,fn])
或者http.server.on('close, fn)
。关于Node.js的Event Loop更详细的过程能够参考这篇文章
进程是应用程序的执行实例,每个进程都是由私有的虚拟地址空间、代码、数据和其它系统资源所组成;进程在运行过程当中可以申请建立和使用系统资源(如独立的内存区域等),这些资源也会随着进程的终止而被销毁。
而线程则是进程内的一个独立执行单元,在不一样的线程之间是能够共享进程资源的,因此在多线程的状况下,须要特别注意对临界资源的访问控制。在系统建立进程以后就开始启动执行进程的主线程,而进程的生命周期和这个主线程的生命周期一致,主线程的退出也就意味着进程的终止和销毁。主线程是由系统进程所建立的,同时用户也能够自主建立其它线程,这一系列的线程都会并发地运行于同一个进程中。
一个进程比如是一个工厂,每一个工厂有它的独立资源(类比到计算机上就是系统分配的一块独立内存),并且每一个工厂之间是相互独立、没法进行通讯。
每一个工厂都有若干个工人(一个工人便是一个线程,一个进程由一个或多个线程组成),多个工人能够协做完成任务(即多个线程在进程中协做完成任务),固然每一个工人能够共享此工厂的空间和资源(即同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等))。
到此你应该能初步理解了进程和线程之间的关系,这将有助于咱们理解浏览器为何是多进程的,而JavaScript是单线程。
浏览器是多进程的(一个窗口就是一个进程),每一个进程包含多个线程.但JavaScript是单线程的.一个主线程(一个stack),多个子线程.
假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准? 因此,为了不复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,未来也不会改变。
js既然是单线程,那么确定是排队执行代码,那么怎么去排这个队,就是Event Loop。虽然JS是单线程,但浏览器不是单线程。浏览器中分为如下几个线程:
其中JS线程和UI线程相互互斥,也就是说,当UI线程在渲染的时候,JS线程会挂起,等待UI线程完成,再执行JS线程
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质。