有一道经典面试题:浏览器输入一个地址回车后都发生了什么?
这道题从前端角度上讲,主要考察了浏览器的一些进程和线程的知识,下面一一剖析之。
以google浏览器为例。
brower进程
当你打开一个tab窗口,其实就是建立了一个brower进程,这个可以打开任务管理器看到。然后你可以在地址栏输入一个url,回车,等待页面。
render渲染进程
其实我们打交道最多的就是这个内核渲染进程了。这个进程拥有好多线程给他干活。其中最重要的有三大线程:
1、GUI渲染线程。
负责绘制dom。
2、JS引擎线程。
负责解析js代码。
3、异步线程。
说异步线程并不准确,其实它是几个线程的综合体,分别是:
1)事件触发线程(click、onload等)
2)定时触发器线程(timeout)
3)异步http请求线程(ajax)
线程有个锁的概念,啥意思呢?
最形象的比喻就是上厕所。一哥们先进去,把门锁好,无论他在里面蹲多久,另一个哥们就是再急,他也不能破门而入,只有等里面的人舒服完了,他才能进去。
GUI渲染线程和JS引擎线程就是这种互斥关系,GUI线程执行时把门锁好,JS引擎只能等待,只有等人家完事了才能轮到它执行,反过来亦是如此。
为什么这么设计呢?
这是因为js可以控制dom,如果dom在渲染时不禁止js的执行,那就会出现状态不一致的现象。
我们可以把js引擎线程看作是同步线程。它只负责执行同步的代码,当遇到异步的代码,就会踢到异步线程里面执行。
用一张图加深一下印象哈:
那么,异步线程又是怎么跟JS引擎线程通信呢?这就引出了Event Loop事件循环机制。
我们知道,js是单线程的,这个单线程就是指JS引擎线程。
那js代码是如何执行的呢?大概可以分这么几步。
第一步:JS引擎线程执行同步代码,遇到异步代码后踢到异步线程,异步线程拿到代码后独立执行(这里以timeout为例)
第二步:dom渲染。
JS引擎线程维护了一个执行栈,当执行栈为空时,说明同步代码已经执行完毕,此时会将执行权交给GUI线程,GUI线程拿到执行权后,会进行dom渲染操作(dom渲染单独列一章讲解)
第三步:任务队列。
当GUI线程和JS引擎线程交互时,异步线程独立执行timeout,执行完毕后,它会将回调函数推入一个队列,这个队列我们称为任务队列。
任务队列里面全是待执行的回调函数,那什么时候执行呢?
第四步:JS引擎执行任务。
GUI线程执行完后将执行权交给JS引擎线程,此时执行栈是空的,然后JS引擎就会调取任务队列里面的回调函数来入栈执行。
这个任务分为两种,一种叫微任务,一种叫宏任务。
微任务有:nextTick、Promise.then、MutationObserver等;
宏任务有:setTimeout、setInterval、setImmediate、MessageChannel、postMessage等。
他们的执行顺序是:
先执行微任务,再执行宏任务。
第五步:一次EventLoop完毕。
当执行完任务队列后(队列为空),JS引擎线程的执行栈为空,此时将执行权交给GUI线程进行dom渲染,这称为一轮事件循环。
事实上,JS引擎线程是while(true)永久循环,会不断的检测任务队列,如果发现了再有新的任务消息,就会引发第二轮事件循环,如此类推。
最后放一张图加深印象: