1.MVVM
MVVM 是指 Model - View - ViewModel,Model 是数据与业务逻辑,View 是视图,ViewModel 用于连接 View 和 Model
Model ---> View:将数据转化成所看到的页面,实现的方式:Data Bindings -- 数据绑定
View ---> Model:将所看到的页面转化成数据,实现的方式:DOM Listeners -- DOM 事件监听
当这两个方向的数据转换都实现时,称之为数据的双向绑定
2.双向绑定的原理
在初始化 data 数据时,会实例化一个 Observe 类,它对 data 数据中的每个属性进行递归遍历,并通过 Object.defineProperty() 给每个属性创建 getter 和 setter。在数据读取时,getter 负责依赖收集;在对属性赋值时,setter 会触发依赖更新。
这里主要是用到了数据劫持,核心就是 Object.defineProperty() 和数组的修改方法 push、pop、unshift、shift、slice、sort、reverse 等。
这里也会引出一个常遇到的面试题:
使用 Object.defineProperty() 来进行数据劫持有什么缺点?
监听不到数组的变化,从而不能触发组件的更新渲染。因此需要重写数组的修改方法,在拦截里面进行手动收集触发依赖更新。
在模板编译阶段,使用 Compile 解析 Vue 模板指令,将模板中的变量替换成数据,并初始化页面视图。每个指令对应的节点都绑定一个 Watcher(更新函数),并添加监听数据变化的订阅者,一旦数据有变动,订阅者收到通知,调用更新函数重新渲染视图。
每个组件实例都对应一个 Watcher 实例,Observer 和 Compile 通过 Watcher 通信,Watcher 主要负责订阅 Observer 中属性值变化的消息,当 Watcher 创建时,通过读取数据触发 getter,完成依赖收集,将当前 Watcher 添加到 Dep 中形成订阅关系。
Vue 通过 Dep 实现发布-订阅模式来管理 Watcher。当数据变化时,Dep 通知关联的 Watcher 调用 update 方法,将更新操作放入异步队列,避免频繁渲染,并在队列中批量执行更新。
解释一下 setter 和 getter:
在输出对象的属性时,控制台中对象的属性是由属性名 name,值 key,和其他特性(可读写性 writable,可枚举性 enumerable,可配置性 configurable)组成的。
从 ES5 开始,提供了 getter和 setter,可以将属性的获取和设置分别绑定到方法上,称之为“ 存取器 ”。通过 getter 和 setter 能够在属性值的变更和获取时实现一些操作。ES6 新增的 class 改变了构造对象的书写方式,也可以在 class 中定义 getter 和 setter。
3.v-model 语法糖
v-model 是 v-bind 数据绑定 与 v-on 处理函数绑定的语法糖
使用 v-model 双向数据绑定事件时,写法如下:
<input v-model = 'userName'>
将 v-model 分解为两个操作:v-bind 绑定一个 value 属性,v-on 指令给当前元素绑定 input 事件
<input v-bind:value="userName" v-on:input="userName=$event.target.value">
也就是说想实现 v-model 需要:先接收一个 value 属性,在 value 的值改变时,触发 input 事件
// v-model 示例
<template><input v-model="userName" />
</template><script lang="ts" setup>
import { ref } from 'vue'const userName = ref('');
</script>
// v-bind + v-on 示例
<template><input :value="userName" @input="handleChange" />
</template><script lang="ts" setup>
import { ref } from 'vue'const userName = ref('');const handleChange = (event: Event) => {const target = event.target as HTMLInputElement;userName.value = target.value;
};
</script>
4.Vue2 与 Vue3 生命周期对比
阶段 | Vue 2 生命周期钩子 | Vue 3 生命周期钩子 |
创建前 | beforeCreate | setup 中执行初始化 |
创建后 | created | setup 中执行初始化 |
挂载前 | beforeMount | onBeforeMount |
挂载后 | mounted | onMounted |
更新前 | beforeUpdate | onBeforeUpdate |
更新后 | updated | onUpdated |
卸载前 | beforeDestory | onBeforeUnmount |
卸载后 | destroyed | onUnmounted |
捕获错误 | errorCaptured | onErrorCaptured |
Vue2 中 created 和 mounted:
- created 在模板渲染成html前调用
- mounted 在模板渲染成html后调用
一般在 created 中进行异步数据请求
- 数据响应性:created 阶段已经完成了实例的初始化,数据和方法已经挂载到组件实例上,Vue 的响应式系统已经建立。这意味着在 created 中获取的数据可以立即进行响应式绑定。
- 渲染优化:在 created 中请求数据可以提前进行逻辑处理,避免在页面初次渲染时出现明显的空白,提升用户体验。
- 一致性:SSR 不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性
Vue 3 新的生命周期钩子
onRenderTracked
:在响应式依赖被追踪时触发,主要用于调试和监控依赖项。onRenderTriggered
:在组件重新渲染时触发,帮助追踪引发渲染的响应式依赖。
5.浏览器工作原理
当在浏览器的地址栏输入 URL 并按下回车后,浏览器会经历一系列步骤来完成页面的加载和渲染。以下是主要的流程:
1. URL 解析和 DNS 解析
URL 解析:浏览器解析用户输入的 URL,判断协议(例如 HTTP、HTTPS),提取域名和路径。
DNS 解析:浏览器向 DNS 服务器发送请求,将域名转换为服务器的 IP 地址。如果 DNS 记录缓存存在于本地(操作系统或浏览器缓存)则会直接使用,否则会从 DNS 服务器获取。
DNS 工作原理和主要解析过程:
1. 本地缓存查询:当用户在浏览器中输入一个域名时,操作系统首先会向本地 DNS 解析器发出查询请求。如果本地解析器没有缓存该域名对应的 IP 地址,则会进行递归查询。
2. 递归查询:本地 DNS 解析器(ISP 提供)向根域名服务器发送查询请求,根域名服务器返回顶级域名服务器的地址(如 .com 或 .net)。
3. 顶级域名解析:本地解析器向顶级域名服务器发送查询请求,顶级域名服务器返回该域名的权威域名服务器的地址。
4. 权威域名解析:本地解析器向权威域名服务器发送查询请求,权威域名服务器返回该域名对应的IP地址。
5. 结果返回:本地解析器将获取到的 IP 地址返回给操作系统,操作系统将其缓存,并将 IP 地址返回给浏览器,使浏览器可以与目标服务器建立连接。
2. 建立连接
TCP 连接:使用获得的 IP 地址,浏览器通过 TCP 三次握手与服务器建立连接。
TCP 三次握手过程
第一次握手(客户端 -> 服务器):
- 客户端向服务器发送一个 SYN(synchronize)标志位的请求报文,表示希望建立连接。
- 报文中包含一个随机初始序列号
seq = x
,用于标识数据包顺序。第二次握手(服务器 -> 客户端):
- 服务器接收到 SYN 请求后,同意建立连接,发送一个 SYN + ACK 报文。
- 其中,SYN 表示服务器同意连接,ACK(acknowledgment)表示对客户端 SYN 报文的确认。
- 同时,服务器生成一个新的初始序列号
seq = y
,并将确认号ack = x + 1
发送给客户端,确认接收到了客户端的请求。第三次握手(客户端 -> 服务器):
- 客户端接收到服务器的 SYN + ACK 报文后,向服务器发送一个 ACK 报文,表示确认已收到服务器的响应。
- 报文包含
ack = y + 1
,表示已确认服务器的序列号y
。- 完成此步骤后,客户端和服务器的连接正式建立,双方可以开始数据传输。
为什么需要三次握手?
- 确认双方发送和接收能力:第一次握手确保客户端能够发送数据,第二次握手确保服务器能够接收并发送数据,第三次握手则确保客户端能接收数据。
- 防止重复连接:三次握手避免了旧的连接请求被误解为新连接。三次握手中的序列号和确认号确保了连接的唯一性和有效性。
TLS/SSL 握手(HTTPS):如果 URL 使用 HTTPS,浏览器和服务器会通过 TLS/SSL 协议加密通信,确保数据传输的安全性。(在三次握手之后发生)
TLS/SSL 握手是指在客户端和服务器之间,通过 TLS(传输层安全协议)或其前身 SSL(安全套接字层)协议建立一个加密连接的过程,确保数据在传输过程中不会被第三方窃听或篡改。HTTPS 实际上就是 HTTP 在 TLS 加密之上的安全版本。
TLS/SSL 握手过程
1. 客户端 Hello:
- 浏览器(客户端)向服务器发送 ClientHello 消息,包含客户端支持的 TLS 版本、加密算法列表、随机数等信息。
- 这个阶段用来协商加密协议的具体参数。2. 服务器 Hello:
- 服务器接收 ClientHello 后,向客户端返回 ServerHello 消息,确认使用的 TLS 版本和加密算法。
- 服务器还会发送 数字证书,证明服务器的身份(通常是域名的所有权)。
- 数字证书由可信的证书颁发机构(CA)签发,并包含服务器的公钥。3. 验证证书:
- 客户端收到服务器的数字证书后,会验证其有效性,以确认连接到的是合法的服务器。
- 如果证书有效,客户端会生成一个会话密钥,并用服务器的公钥加密后发送给服务器。
- 服务器用私钥解密该密钥,从而双方获得相同的会话密钥。4. 会话密钥建立:
- 一旦双方共享了会话密钥,客户端和服务器开始使用对称加密(会话密钥)进行加密通信,这比之前的公钥加密更高效。5. 握手完成:
- 握手过程完成后,浏览器和服务器可以安全地交换 HTTP 数据(如请求和响应),以实现 HTTPS 的加密通信。TLS/SSL 握手的意义
- 加密:传输数据被加密,第三方无法窃听。
- 身份验证:客户端验证服务器的身份,防止中间人攻击。
- 数据完整性:传输数据的完整性得到保证,防止数据被篡改。
3.发送 HTTP 请求
在建立的 TCP 连接中,浏览器根据 HTTP 协议发送请求报文,要求服务器返回相应的网页资源。
4.服务器处理请求并返回
服务器接收请求后,处理相关逻辑并返回响应内容(如 HTML、CSS、JavaScript 文件等),将文件发送给浏览器。
5.浏览器渲染
浏览器渲染过程
- 解析 HTML 文件构建 DOM 树
- 解析 CSS 文件构建 CSSOM 树
- 根据执行顺序解析 JS 文件,可能会修改 DOM 和 CSSOM
- 将 DOM 和 CSSOM 合成渲染树 Render Tree
- 浏览器计算渲染树的布局,将页面内容绘制到屏幕上
6.断开连接
页面加载完成后,浏览器与服务器通过四次挥手,断开连接(针对 HTTP/1.1 及以下版本,HTTP/2 可保持连接)。
四次挥手过程
第一次挥手(客户端 -> 服务器):
- 客户端向服务器发送一个 FIN(Finish)报文,表示客户端已发送完数据,准备关闭连接。
- 这个操作表示客户端不再向服务器发送数据了,但仍然可以接收来自服务器的数据(进入半关闭状态)。
第二次挥手(服务器 -> 客户端):
- 服务器收到客户端的 FIN 后,发送一个 ACK 报文,确认收到客户端的关闭请求。
- 这时,服务器处于半关闭状态,仍然可以向客户端发送数据,但客户端不会再发出数据。
第三次挥手(服务器 -> 客户端):
- 服务器在发送完剩余的数据后,向客户端发送一个 FIN 报文,通知客户端服务器也要关闭连接了。
- 这表明服务器已经没有数据要发送,并准备关闭连接。
第四次挥手(客户端 -> 服务器):
- 客户端收到服务器的 FIN 后,发送一个 ACK 报文,确认服务器的关闭请求。
- 这个 ACK 报文发送后,客户端进入 TIME-WAIT 状态,等待一段时间(两个最大报文段生存时间:2MSL),确保服务器收到了 ACK 报文。如果在此期间没有收到服务器的重传请求,客户端才正式关闭连接。
为什么是 2MSL?
因为客户端不知道服务端是否能收到 ACK 应答数据包,服务端如果没有收到 ACK,会重传 FIN,考虑最坏的一种情况:第四次挥手的 ACK 包的最大生存时长(MSL)+服务端重传的FIN包的最大生存时长(MSL)=2MSL
确保被动关闭 TCP 连接的一端能收到第四次挥手 ACK,避免上一次 TCP 连接的数据包影响到下一次的 TCP 连接
注:MSL是报文最大生存时间。2MSL,即两个最大报文段生存时间。如果超过这个时间,这个TCP报文就会被丢弃。
为什么需要四次挥手?
四次挥手的设计是为了确保双方都能可靠地关闭连接
- TCP 是全双工协议,双方需要独立关闭发送通道。
- 在第一次和第二次挥手之间,客户端和服务器各自处于半关闭状态,仍然能够完成剩余数据的传输。
- TIME-WAIT 状态确保了最后的 ACK 报文能够成功到达服务器,防止连接重叠或数据丢失。
在 HTTP/2 中,连接保持时间由客户端和服务器协商决定,并不会立即关闭,而是可以用于多个请求-响应的传输。
HTTP/2 使用了
GOAWAY
帧来优雅地关闭连接,通知对方不再接收新的请求,但在传输层仍需通过四次挥手来确保连接的彻底关闭和资源的释放。HTTP/3 中,连接的建立依赖 QUIC 协议,与传统的 TCP 三次握手不同。QUIC 将传输层和加密层合并,能够在 单个往返(1-RTT)内完成连接建立和加密握手,极大减少了延迟。
QUIC 的连接建立流程
客户端发送初始数据包:客户端发送一个包含连接请求的 QUIC 数据包,其中包含加密握手的初始数据(ClientHello)。在这个阶段,客户端同时发送 HTTP/3 请求数据,以便在握手完成后立即得到响应。
服务器响应并完成握手:服务器接收客户端的请求包后,回应一个加密的握手响应数据包(ServerHello),其中包含服务器的证书和加密密钥信息,以便完成 TLS 握手。服务器还可以立即附加 HTTP/3 响应数据,减少延迟。
确认和数据传输:客户端接收服务器的 ServerHello 包,完成密钥协商并确认连接建立。从这一点开始,双方可以加密通信,HTTP/3 请求和响应的实际数据也可以开始传输。
QUIC 优化:0-RTT 连接恢复
0-RTT(Zero Round-Trip Time)
如果客户端与服务器之间已经建立过 QUIC 连接并缓存了相关信息,QUIC 支持 0-RTT 连接恢复,允许客户端在发送 ClientHello 时直接携带请求数据。这种模式能够在不等待任何返回包的情况下立刻发送数据,使得连接恢复的延迟接近于零。
QUIC 与 TCP 的区别
- 加密和握手合一:QUIC 将传输和加密层(TLS)合并,避免了 TCP 和 TLS 分别握手的冗余过程。
- 多路复用:在一个 QUIC 连接中可以处理多个 HTTP/3 请求,避免了 TCP 中的队头阻塞问题。
- UDP 协议基础:QUIC 基于 UDP,避免了 TCP 的慢启动和连接复用问题,使得连接更快、更高效。
0-RTT 的工作原理
0-RTT 的关键是利用客户端和服务器之间的先前会话信息。在客户端和服务器之间第一次通信时,经过正常的握手过程(如 TLS 或 QUIC 握手)后,客户端会缓存一份会话信息(包括会话密钥)。当客户端下次与同一服务器通信时,可以直接使用该会话信息来发起 0-RTT 连接。
具体步骤:
缓存会话信息:客户端第一次连接服务器后,双方会生成共享的会话密钥。客户端将此会话信息缓存,以备下次重用。
发送 0-RTT 数据:在第二次连接时,客户端可以立即发送加密的数据包,而不必等待握手完成。这些数据使用之前协商的会话密钥加密。
完成握手:服务器接收到客户端的 0-RTT 数据后,同时完成握手并验证会话信息的有效性。如果验证通过,服务器会处理 0-RTT 数据;如果失败,则会丢弃数据并重新协商。
6.如何解决SPA首屏加载速度慢
首屏时间是浏览器从响应用户输入网址地址,到首屏内容渲染完成的时间。此时整个网页不一定要全部渲染完成,但需要展示当前视窗需要的内容。
首屏加载速度慢主要是因为初次加载时,浏览器需要加载整个应用的 JavaScript、CSS 文件等资源,这会导致页面白屏时间长、用户体验下降。
解决方法:
1. 代码拆分和懒加载
- 代码拆分:使用 Webpack 等工具,将应用按页面或路由拆分成多个小文件,只在用户需要时才加载对应的文件。这样可以减少首屏加载的资源大小。
- 懒加载路由:将不同的路由模块设置为懒加载,仅在用户访问到特定路由时加载对应的模块。在 Vue 或 React 中可以使用动态 `import()` 实现懒加载。
2. 服务器端渲染(SSR)
- 使用服务器端渲染(SSR)技术,将首屏内容在服务器端预渲染后发送给客户端。这样可以减少浏览器渲染的工作量,首屏渲染时间显著减少。
3. 使用骨架屏
- 在首屏加载时显示骨架屏,让用户看到一个加载占位图,提升用户体验。骨架屏模拟了页面的结构,用户不会看到白屏,感觉上加载时间会缩短。
4. 资源优化
- 资源压缩:使用 Gzip 或 Brotli 压缩静态资源,包括 JS、CSS、HTML 文件,减小资源体积。
- 图片优化:通过压缩图片、懒加载图片等方式减少图片加载时间。
- 字体优化:减少 Web 字体使用,或仅加载页面必要的字体子集。
6. 使用 HTTP/2 和 CDN
- HTTP/2:HTTP/2 协议支持多路复用,可以在一个连接中并行加载多个资源,提高加载效率。
- 内容分发网络(CDN):将静态资源分发到离用户更近的服务器节点,减少传输延迟。
7. 开启缓存
- 缓存静态资源:为 JS、CSS、图片等设置合理的缓存时间,通过 `Cache-Control` 和 `ETag` 进行缓存管理,减少用户二次访问的加载时间。
8. 优化打包工具配置
- 开启 CSS 代码分离、JS 多线程打包等配置,进一步减少代码体积,提升打包和加载效率。