# 前端面试其他常见问题
# 1.URL的输入到看到页面发生了什么?
TIP
URL
(统一资源定位符,用于定位互联网上的资源)
scheme://host.domain:port/path/filename
scheme
:定义因特网服务类型。常见有http
、https
、file
、ftp
host
:定义域主机domain
:定义因特网域名port
:定义主机端口,http
默认端口为80
,https
为443
path
:定义服务器上的路径(如果省略代表文档在网站根目录中)filename
:定义文档/资源的名称
用户输入首先判断地址栏输入的关键字类别,是搜索内容还是请求的
URL
。如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的URL
。如果判断输入内容符合URL
规则,那么地址栏会根据规则把这段内容加上协议,合成完整的URL
。标签页上的图标进入加载状态。
浏览器进程会通过进程间通信把URL
请求发送到网络进程,网络进程接收到后会发起真正的URL
请求过程。
网络进程会查找本地缓存是否缓存了资源,如果有缓存则返回给浏览器进程,没有则进入网络请求进程。
一个递归过程
- 发送TCP连接
三次握手
- 发送
HTTP
请求
发送
HTTP
请求的过程就是构建HTTP
请求报文并通过TCP
协议中发送到服务器指定端口 请求报文由请求行,请求报头,请求正文组成
- 服务器处理请求并返回
HTTP
报文
TIP
- 发送
HTTP
请求:一旦TCP
连接建立成功,浏览器就可以向服务器发送HTTP
请求。该请求中包含了一些必要的信息,例如请求方法、URL
、请求头、请求体等。 - 服务器响应
HTTP
请求:服务器收到HTTP
请求后,会根据请求中的信息进行相应的处理,并生成一个HTTP
响应。该响应中包含了一些必要的信息,例如响应状态码、响应头、响应体等。 - 接收
HTTP
响应:一旦服务器生成了HTTP
响应,它就会通过TCP
连接发送给浏览器。浏览器接收到HTTP
响应后,会进行相应的处理,例如解析响应头、响应体等。
- 浏览器解析渲染页面
TIP
准备渲染进程,通常情况下打开新的页面会使用单独的渲染进程。如果从A页面打开B页面且在同一个站点下,那么B页面会复用A的渲染进程;如果是其他情况浏览器进程会为B创建一个新的渲染进程。渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数目在网络进程中,并没有提交给渲染进程,所以下一步进入提交文档的阶段。
提交文档,文档指URL
请求的响应体数据。提交文档的信息由浏览器进程发出,渲染进程收到提交文档的信息后,会和网络进程建立传输数据的管道。等文档数据传输完成之后,渲染进程会返回确认提交的信息给浏览器进程。浏览器进程在收到确认提交的信息后,会更新浏览器界面状态,包括了安全状态,地址栏的URL
,前进后退的状态,并更新页面。
- 连接结束
四次挥手
TIP
详细的网页加载过程
- 处理输入
UI thread
判断是关键词搜索还是URL
- 开始导航
回车后,
UI thread
将输入的内容交给网络线程Network thread
,此时UI线程使Tab
前图标展示为加载中状态,然后网络进程进行一系列诸如DNS
寻址,建立TLS
连接等操作进行资源请求,如果收到服务器的301
重定向响应,它就会告知UI线程进行重定向然后它会再次发起一个新的网络请求。
- 读取响应
network thread
接收到服务器的响应后,开始解析HTTP
响应报文,然后根据响应头中的Content-Type
字段来确定响应主体的媒体类型(MIME Type
),如果媒体类型是一个HTML
文件,则将响应数据交给渲染进程(renderer process
)来进行下一步的工作,如果是zip
文件或者其它文件,会把相关数据传输给下载管理器。与此同时,浏览器会进行
Safe Browsing
安全检查,如果域名或者请求内容匹配到已知的恶意站点,network thread
会展示一个警告页。除此之外,网络线程还会做CORB(Cross Origin Read Blocking)
检查来确定那些敏感的跨站数据不会被发送至渲染进程。
- 查找渲染进程
各种检查完毕以后,
network thread
确信浏览器可以导航到请求网页,network thread
会通知UI thread
数据已经准备好,UI thread
会查找到一个renderer process
进行网页的渲染。浏览器为了对查找渲染进程这一步骤进行优化,考虑到网络请求获取响应需要时间,所以在第二步开始,浏览器已经预先查找和启动了一个渲染进程,如果中间步骤一切顺利,当
network thread
接收到数据时,渲染进程已经准备好了,但是如果遇到重定向,这个准备好的渲染进程也许就不可用了,这个时候会重新启动一个渲染进程。
- 提交导航
到了这一步,数据和渲染进程都准备好了,
Browser Process
会向Renderer Process
发送IPC
消息来确认导航,此时,浏览器进程将准备好的数据发送给渲染进程,渲染进程接收到数据之后,又发送IPC
消息给浏览器进程,告诉浏览器进程导航已经提交了,页面开始加载。这个时候导航栏会更新,安全指示符更新(地址前面的小锁),访问历史列表(
history tab
)更新,即可以通过前进后退来切换该页面。
- 初始化加载完成
当导航提交完成后,渲染进程开始加载资源及渲染页面,当页面渲染完成后(页面及内部的
iframe
都触发了onload
事件),会向浏览器进程发送IPC
消息,告知浏览器进程,这个时候UI thread
会停止展示tab
中的加载中图标。
TIP
网页渲染原理:
- 渲染进程中的多个线程:
- 一个主线程
- 多个工作线程
- 一个合成器线程
- 多个光栅化线程
- 构建DOM
当渲染进程接受到导航的确认信息后,开始接受来自浏览器进程的数据,这个时候,主线程会解析数据转化为
DOM(Document Object Model)
对象。
- 子资源加载
在构建
DOM
的过程中,会解析到图片、CSS
、JavaScript
脚本等资源,这些资源是需要从网络或者缓存中获取的,主线程在构建DOM
过程中如果遇到了这些资源,逐一发起请求去获取,而为了提升效率,浏览器也会运行预加载扫描(preload scanner
)程序,如果HTML
中存在img
、link
等标签,预加载扫描程序会把这些请求传递给Browser Process
的network thread
进行资源下载。
- Javascript的下载与执行
构建DOM过程中,如果遇到
<script>
标签,渲染引擎会停止对HTML
的解析,而去加载执行JS代码,原因在于JS代码可能会改变DOM
的结构
- 样式计算
DOM
树只是我们页面的结构,我们要知道页面长什么样子,我们还需要知道DOM的每一个节点的样式。主线程在解析页面时,遇到<style>
标签或者<link>
标签的CSS
资源,会加载CSS
代码,根据CSS
代码确定每个DOM
节点的计算样式(computed style
)。计算样式是主线程根据
CSS
样式选择器(CSS selectors
)计算出的每个DOM
元素应该具备的具体样式,即使你的页面没有设置任何自定义的样式,浏览器也会提供其默认的样式。
- 布局
DOM
树和计算样式完成后,我们还需要知道每一个节点在页面上的位置,布局(Layout
)其实就是找到所有元素的几何关系的过程。主线程会遍历
DOM
及相关元素的计算样式,构建出包含每个元素的页面坐标信息及盒子模型大小的布局树(Render Tree
),遍历过程中,会跳过隐藏的元素(display: none
),另外,伪元素虽然在DOM
上不可见,但是在布局树上是可见的。
- 绘制
布局
layout
之后,我们知道了不同元素的结构,样式,几何关系,我们要绘制出一个页面,我们要需要知道每个元素的绘制先后顺序,在绘制阶段,主线程会遍历布局树(layout tree
),生成一系列的绘画记录(paint records
)。绘画记录可以看做是记录各元素绘制先后顺序的笔记。
- 合成
文档结构、元素的样式、元素的几何关系、绘画顺序,这些信息我们都有了,这个时候如果要绘制一个页面,我们需要做的是把这些信息转化为显示器中的像素,这个转化的过程,叫做光栅化(
rasterizing
)。
合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - **合成线程(
compositor thread
)**里面合并成一个页面的技术。当用户滚动页面时,由于页面各个层都已经被光栅化了,浏览器需要做的只是合成一个新的帧来展示滚动后的效果罢了。页面的动画效果实现也是类似,将页面上的层进行移动并构建出一个新的帧即可。
为了实现合成技术,我们需要对元素进行分层,确定哪些元素需要放置在哪一层,主线程需要遍历渲染树来创建一棵层次树(Layer Tree
),对于添加了 will-change
CSS 属性的元素,会被看做单独的一层,没有will-change
CSS属性的元素,浏览器会根据情况决定是否要把该元素放在单独的层。
一旦
Layer Tress
被创建,渲染顺序被确定,主线程会把这些信息通知给合成器线程,合成器线程开始对层次数的每一层进行光栅化。有的层的可以达到整个页面的大小,所以合成线程需要将它们切分为一块又一块的小图块(tiles
),之后将这些小图块分别进行发送给一系列光栅线程(raster threads
)进行光栅化,结束后光栅线程会将每个图块的光栅结果存在GPU Process
的内存中。
合成的好处在于这个过程没有涉及到主线程,所以合成线程不需要等待样式的计算以及JavaScript完成执行。这就是为什么合成器相关的动画最流畅,如果某个动画涉及到布局或者绘制的调整,就会涉及到主线程的重新计算,自然会慢很多。
- 浏览器对事件的处理
当页面渲染完毕以后,
TAB
内已经显示出了可交互的WEB
页面,用户可以进行移动鼠标、点击页面等操作了,而当这些事件发生时候,浏览器是如何处理这些事件的呢?以点击事件(
click event
)为例,让鼠标点击页面时候,首先接受到事件信息的是Browser Process
,但是Browser Process
只知道事件发生的类型和发生的位置,具体怎么对这个点击事件进行处理,还是由Tab
内的Renderer Process
进行的。Browser Process
接受到事件后,随后便把事件的信息传递给了渲染进程,渲染进程会找到根据事件发生的坐标,找到目标对象(target
),并且运行这个目标对象的点击事件绑定的监听函数(listener
)。
- 渲染进程中合成器线程接收事件
开发者需要注意
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
})
2
3
4
5
这一段代码给
body
元素绑定了事件监听器,也就意味着整个页面都被编辑为一个非快速滚动区域,这会使得即使你的页面的某些区域没有绑定任何事件,每次用户触发事件时,合成器线程也需要和主线程通信并等待反馈,流畅的合成器独立处理合成帧的模式就失效了。解决方式:只需要在事件监听时传递passtive
参数为true
,passtive
会告诉浏览器你既要绑定事件,又要让组合器线程直接跳过主线程的事件处理直接合成创建组合帧。
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true}); // 顺从浏览器默认行为,所以preventDefault失效
2
3
4
5
- 查找事件的目标对象(
event target
)
当合成器线程接收到事件信息,判定到事件发生不在非快速滚动区域后,合成器线程会向主线程发送这个时间信息,主线程获取到事件信息的第一件事就是通过命中测试(
hit test
)去找到事件的目标对象。具体的命中测试流程是遍历在绘制阶段生成的绘画记录(paint records
)来找到包含了事件发生坐标上的元素对象。
- 浏览器对事件的优化
一般我们屏幕的帧率是每秒
60
帧,也就是60fps
,但是某些事件触发的频率超过了这个数值,比如wheel,mousewheel,mousemove,pointermove,touchmove
,这些连续性的事件一般每秒会触发60~120
次,假如每一次触发事件都将事件发送到主线程处理,由于屏幕的刷新速率相对来说较低,这样使得主线程会触发过量的命中测试以及JS代码,使得性能有了没必要是损耗。
出于优化的目的,浏览器会合并这些连续的事件,延迟到下一帧渲染时执行,也就是
requestAnimationFrame
之前。而对于非连续性的事件,如keydown,keyup,mousedown,mouseup,touchstart,touchend
等,会直接派发给主线程去执行。
# 如何优化单页应用首屏加载速度慢的问题?
- 将公用的
JS
库通过script
标签外部引入,减小app.bundel
的大小,让浏览器并行下载资源文件,提高下载速度; - 在配置路由时,页面和组件使用懒加载的方式引入,进一步缩小
app.bundel
的体积,在调用某个组件时再加载对应的js
文件; - 加一个首屏
loading
图,提升用户体验;
# axios的特点
- 从浏览器中创建
XMLHttpRequests
; node.js
创建http
请求;
axios
可以用在浏览器和node.js
中是因为,它会自动判断当前环境是什么,如果是浏览器,就会基于XMLHttpRequests
实现axios
。如果是node.js
环境,就会基于node
内置核心模块http
实现axios
- 支持
Promise API
; - 拦截请求和响应;
- 转换请求数据和响应数据;
- 取消请求;
- 自动换成
json
。 axios
中的发送字段的参数是data
跟params
两个,两者的区别在于params
是跟请求地址一起发送的,data
的作为一个请求体进行发送params
一般适用于get
请求,data
一般适用于post put
请求。- 客户端支持防御
CSRF