# 前端面试其他常见问题

# 1.URL的输入到看到页面发生了什么?

TIP

URL(统一资源定位符,用于定位互联网上的资源)

  • scheme://host.domain:port/path/filename
    • scheme:定义因特网服务类型。常见有httphttpsfileftp
    • host:定义域主机
    • domain:定义因特网域名
    • port:定义主机端口,http默认端口为80https443
    • path:定义服务器上的路径(如果省略代表文档在网站根目录中)
    • filename:定义文档/资源的名称

用户输入首先判断地址栏输入的关键字类别,是搜索内容还是请求的URL。如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的URL。如果判断输入内容符合URL规则,那么地址栏会根据规则把这段内容加上协议,合成完整的URL。标签页上的图标进入加载状态。

浏览器进程会通过进程间通信把URL请求发送到网络进程,网络进程接收到后会发起真正的URL请求过程。

  1. 网络进程会查找本地缓存是否缓存了资源,如果有缓存则返回给浏览器进程,没有则进入网络请求进程。

  2. DNS解析(域名解析)

一个递归过程 img

  1. 发送TCP连接

三次握手

  1. 发送HTTP请求

发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议中发送到服务器指定端口 请求报文由请求行请求报头请求正文组成

  1. 服务器处理请求并返回HTTP报文

TIP

  • 发送 HTTP 请求:一旦 TCP 连接建立成功,浏览器就可以向服务器发送 HTTP 请求。该请求中包含了一些必要的信息,例如请求方法、URL、请求头、请求体等。
  • 服务器响应 HTTP 请求:服务器收到 HTTP 请求后,会根据请求中的信息进行相应的处理,并生成一个 HTTP 响应。该响应中包含了一些必要的信息,例如响应状态码、响应头、响应体等。
  • 接收 HTTP 响应:一旦服务器生成了 HTTP 响应,它就会通过 TCP 连接发送给浏览器。浏览器接收到 HTTP 响应后,会进行相应的处理,例如解析响应头、响应体等。
  1. 浏览器解析渲染页面

TIP

准备渲染进程,通常情况下打开新的页面会使用单独的渲染进程。如果从A页面打开B页面且在同一个站点下,那么B页面会复用A的渲染进程;如果是其他情况浏览器进程会为B创建一个新的渲染进程。渲染进程准备好之后,还不能立即进入文档解析状态,因为此时的文档数目在网络进程中,并没有提交给渲染进程,所以下一步进入提交文档的阶段。

提交文档,文档指URL请求的响应体数据。提交文档的信息由浏览器进程发出,渲染进程收到提交文档的信息后,会和网络进程建立传输数据的管道。等文档数据传输完成之后,渲染进程会返回确认提交的信息给浏览器进程。浏览器进程在收到确认提交的信息后,会更新浏览器界面状态,包括了安全状态,地址栏的URL,前进后退的状态,并更新页面。

  1. 连接结束

四次挥手

TIP

详细的网页加载过程

  1. 处理输入

UI thread判断是关键词搜索还是URL

  1. 开始导航

回车后,UI thread将输入的内容交给网络线程Network thread,此时UI线程使Tab前图标展示为加载中状态,然后网络进程进行一系列诸如DNS寻址,建立TLS连接等操作进行资源请求,如果收到服务器的301重定向响应,它就会告知UI线程进行重定向然后它会再次发起一个新的网络请求。

  1. 读取响应

network thread接收到服务器的响应后,开始解析HTTP响应报文,然后根据响应头中的Content-Type字段来确定响应主体的媒体类型(MIME Type),如果媒体类型是一个HTML文件,则将响应数据交给渲染进程(renderer process)来进行下一步的工作,如果是 zip 文件或者其它文件,会把相关数据传输给下载管理器。

与此同时,浏览器会进行 Safe Browsing 安全检查,如果域名或者请求内容匹配到已知的恶意站点,network thread 会展示一个警告页。除此之外,网络线程还会做 CORB(Cross Origin Read Blocking)检查来确定那些敏感的跨站数据不会被发送至渲染进程。

  1. 查找渲染进程

各种检查完毕以后,network thread 确信浏览器可以导航到请求网页,network thread 会通知 UI thread 数据已经准备好,UI thread 会查找到一个 renderer process 进行网页的渲染。

浏览器为了对查找渲染进程这一步骤进行优化,考虑到网络请求获取响应需要时间,所以在第二步开始,浏览器已经预先查找和启动了一个渲染进程,如果中间步骤一切顺利,当 network thread 接收到数据时,渲染进程已经准备好了,但是如果遇到重定向,这个准备好的渲染进程也许就不可用了,这个时候会重新启动一个渲染进程。

  1. 提交导航

到了这一步,数据和渲染进程都准备好了,Browser Process 会向 Renderer Process 发送IPC消息来确认导航,此时,浏览器进程将准备好的数据发送给渲染进程,渲染进程接收到数据之后,又发送IPC消息给浏览器进程,告诉浏览器进程导航已经提交了,页面开始加载。

这个时候导航栏会更新,安全指示符更新(地址前面的小锁),访问历史列表(history tab)更新,即可以通过前进后退来切换该页面。

  1. 初始化加载完成

当导航提交完成后,渲染进程开始加载资源及渲染页面,当页面渲染完成后(页面及内部的iframe都触发了onload事件),会向浏览器进程发送IPC消息,告知浏览器进程,这个时候UI thread会停止展示tab中的加载中图标。

TIP

网页渲染原理:

  • 渲染进程中的多个线程:
    • 一个主线程
    • 多个工作线程
    • 一个合成器线程
    • 多个光栅化线程
  1. 构建DOM

当渲染进程接受到导航的确认信息后,开始接受来自浏览器进程的数据,这个时候,主线程会解析数据转化为DOM(Document Object Model)对象。

  1. 子资源加载

在构建DOM的过程中,会解析到图片、CSSJavaScript脚本等资源,这些资源是需要从网络或者缓存中获取的,主线程在构建DOM过程中如果遇到了这些资源,逐一发起请求去获取,而为了提升效率,浏览器也会运行预加载扫描(preload scanner)程序,如果HTML中存在imglink等标签,预加载扫描程序会把这些请求传递给Browser Processnetwork thread进行资源下载。

  1. Javascript的下载与执行

构建DOM过程中,如果遇到<script>标签,渲染引擎会停止对HTML的解析,而去加载执行JS代码,原因在于JS代码可能会改变DOM的结构

  1. 样式计算

DOM树只是我们页面的结构,我们要知道页面长什么样子,我们还需要知道DOM的每一个节点的样式。主线程在解析页面时,遇到<style>标签或者<link>标签的CSS资源,会加载CSS代码,根据CSS代码确定每个DOM节点的计算样式(computed style)。

计算样式是主线程根据CSS样式选择器(CSS selectors)计算出的每个DOM元素应该具备的具体样式,即使你的页面没有设置任何自定义的样式,浏览器也会提供其默认的样式。

  1. 布局

DOM树和计算样式完成后,我们还需要知道每一个节点在页面上的位置,布局(Layout)其实就是找到所有元素的几何关系的过程。

主线程会遍历DOM 及相关元素的计算样式,构建出包含每个元素的页面坐标信息及盒子模型大小的布局树(Render Tree),遍历过程中,会跳过隐藏的元素(display: none),另外,伪元素虽然在DOM上不可见,但是在布局树上是可见的。

  1. 绘制

布局 layout 之后,我们知道了不同元素的结构,样式,几何关系,我们要绘制出一个页面,我们要需要知道每个元素的绘制先后顺序,在绘制阶段,主线程会遍历布局树(layout tree),生成一系列的绘画记录(paint records)。绘画记录可以看做是记录各元素绘制先后顺序的笔记。

  1. 合成

文档结构、元素的样式、元素的几何关系、绘画顺序,这些信息我们都有了,这个时候如果要绘制一个页面,我们需要做的是把这些信息转化为显示器中的像素,这个转化的过程,叫做光栅化(rasterizing

合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - **合成线程(compositor thread)**里面合并成一个页面的技术。当用户滚动页面时,由于页面各个层都已经被光栅化了,浏览器需要做的只是合成一个新的帧来展示滚动后的效果罢了。页面的动画效果实现也是类似,将页面上的层进行移动并构建出一个新的帧即可。

为了实现合成技术,我们需要对元素进行分层,确定哪些元素需要放置在哪一层,主线程需要遍历渲染树来创建一棵层次树(Layer Tree),对于添加了 will-changeCSS 属性的元素,会被看做单独的一层,没有will-change CSS属性的元素,浏览器会根据情况决定是否要把该元素放在单独的层。

一旦Layer Tress被创建,渲染顺序被确定,主线程会把这些信息通知给合成器线程,合成器线程开始对层次数的每一层进行光栅化。有的层的可以达到整个页面的大小,所以合成线程需要将它们切分为一块又一块的小图块(tiles),之后将这些小图块分别进行发送给一系列光栅线程(raster threads)进行光栅化,结束后光栅线程会将每个图块的光栅结果存在GPU Process的内存中。

img

合成的好处在于这个过程没有涉及到主线程,所以合成线程不需要等待样式的计算以及JavaScript完成执行。这就是为什么合成器相关的动画最流畅,如果某个动画涉及到布局或者绘制的调整,就会涉及到主线程的重新计算,自然会慢很多。

  1. 浏览器对事件的处理

当页面渲染完毕以后,TAB内已经显示出了可交互的WEB页面,用户可以进行移动鼠标、点击页面等操作了,而当这些事件发生时候,浏览器是如何处理这些事件的呢?

以点击事件(click event)为例,让鼠标点击页面时候,首先接受到事件信息的是Browser Process,但是Browser Process只知道事件发生的类型和发生的位置,具体怎么对这个点击事件进行处理,还是由Tab内的Renderer Process进行的。Browser Process接受到事件后,随后便把事件的信息传递给了渲染进程,渲染进程会找到根据事件发生的坐标,找到目标对象(target),并且运行这个目标对象的点击事件绑定的监听函数(listener)。

  1. 渲染进程中合成器线程接收事件 img

开发者需要注意

document.body.addEventListener('touchstart', event => {
  if (event.target === area) {
    event.preventDefault()
  }
})
1
2
3
4
5

这一段代码给body元素绑定了事件监听器,也就意味着整个页面都被编辑为一个非快速滚动区域,这会使得即使你的页面的某些区域没有绑定任何事件,每次用户触发事件时,合成器线程也需要和主线程通信并等待反馈,流畅的合成器独立处理合成帧的模式就失效了。解决方式:只需要在事件监听时传递passtive参数为 truepasstive会告诉浏览器你既要绑定事件,又要让组合器线程直接跳过主线程的事件处理直接合成创建组合帧。

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault() 
    }
 }, {passive: true}); // 顺从浏览器默认行为,所以preventDefault失效
1
2
3
4
5
  1. 查找事件的目标对象(event target

当合成器线程接收到事件信息,判定到事件发生不在非快速滚动区域后,合成器线程会向主线程发送这个时间信息,主线程获取到事件信息的第一件事就是通过命中测试(hit test)去找到事件的目标对象。具体的命中测试流程是遍历在绘制阶段生成的绘画记录(paint records)来找到包含了事件发生坐标上的元素对象。

  1. 浏览器对事件的优化

一般我们屏幕的帧率是每秒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中的发送字段的参数是dataparams两个,两者的区别在于params是跟请求地址一起发送的,data的作为一个请求体进行发送
  • params一般适用于get请求,data一般适用于post put 请求。
  • 客户端支持防御CSRF