网络
在浏览器地址栏输入URL并回车之后发生了什么
我的回答会聚焦于浏览器侧的行为,并向上游(网络、服务器)和下游(页面渲染、执行)进行扩展,形成一个完整且有层次的链路。
整个过程可以概括为两个主要阶段:导航阶段(Navigation) 和 渲染阶段(Rendering)。
第一阶段:导航阶段 —— 从请求到响应
这个阶段的目标是获取到目标页面资源。
1. URL 解析 (URL Parsing)
浏览器首先会解析我输入的 URL。它会识别出协议(https)、域名(www.example.com)、端口(如果指定,https默认为443)、路径(/path/to/resource)、查询参数(?key=value)和哈希值(#section1)等部分。
2. 缓存检查 (Cache Check) 这是体现性能优化的第一步,也是前端专家必须关注的地方。浏览器会按照以下顺序检查缓存:
- Service Worker 缓存:如果当前页面注册了 Service Worker,浏览器会把请求交给它。Service Worker 可以完全控制网络请求,它可以决定是从自己的缓存(Cache Storage)返回响应,还是直接发起网络请求。这是 PWA 实现离线体验的核心。
- 内存缓存 (Memory Cache):检查资源是否在内存中。这部分缓存是短暂的,关闭标签页就会消失。CSS、JS、图片等小文件很可能在这里被找到。
- 硬盘缓存 (Disk Cache):如果内存中没有,会检查硬盘。这里就要涉及到 HTTP 缓存策略了,浏览器会查看响应头中的
Cache-Control和Expires。- 强缓存:如果
Cache-Control的max-age未过期,或Expires的时间未到,浏览器会直接从硬盘读取资源,返回200 OK (from disk cache),根本不会发出HTTP请求。 - 协商缓存:如果强缓存失效(例如
max-age=0或no-cache),浏览器会带上If-None-Match(Etag)和If-Modified-Since(Last-Modified)这两个请求头去询问服务器。如果服务器判断资源未改变,会返回一个304 Not Modified的响应,浏览器随即从硬盘加载资源。如果改变了,服务器才会返回200 OK和最新的资源。
- 强缓存:如果
- Push Cache:HTTP/2 的服务器推送资源会存在这里。
3. DNS 解析 (DNS Lookup)
如果缓存中没有找到,说明需要发起网络请求了。首先要把域名(www.example.com)解析成服务器的 IP 地址。
- 浏览器会先查找自己的 DNS 缓存。
- 如果没有,会查找操作系统的
hosts文件和 DNS 缓存。 - 如果还没有,就会向本地配置的 LDNS (Local DNS Server) 发起递归查询请求。
- LDNS 会依次向根域名服务器、顶级域名服务器(.com)、权威域名服务器(example.com)发起迭代查询,最终获取到 IP 地址并返回给浏览器。
4. 建立 TCP 连接 (TCP Handshake) 拿到了 IP 地址,浏览器会与服务器通过三次握手建立一个 TCP 连接,确保通信的可靠性。
5. 发起 HTTPS 请求 (TLS Handshake & HTTP Request)
- 如果是 HTTPS 协议,在 TCP 连接之上,还需要进行 TLS 握手。这个过程涉及到客户端发送支持的加密套件、服务器返回证书、客户端验证证书、双方交换密钥等步骤,最终建立一个加密的通信通道。
- 握手完成后,浏览器终于可以构建并发送一个 HTTP 请求报文了。报文里包含了请求行(
GET /path HTTP/1.1)、请求头(Host,User-Agent,Cookie,Accept等)和请求体(如果是 POST)。
6. 服务器处理与响应 (Server Response)
服务器(如 Nginx)接收到请求后,可能会进行负载均衡、请求转发等操作,最终由后端应用程序处理。后端处理完毕后,会返回一个 HTTP 响应报文,包含状态码(200 OK)、响应头(Content-Type, Content-Length, Set-Cookie 等)和响应体(通常是 HTML 内容)。
7. 断开连接或保持 (Connection Close/Keep-Alive)
根据 Connection 响应头的值,决定是关闭 TCP 连接 (close) 还是保持连接 (keep-alive),后者可以用于后续请求的复用,减少握手开销。
至此,浏览器已经成功获取到了 HTML 文档。导航阶段结束。
第二阶段:渲染阶段 —— 从代码到像素
这是前端的主战场。浏览器拿到 HTML 后,会自上而下解析,并最终将内容呈现为像素。这个过程是渐进式的。
1. 构建 DOM 树 (DOM Tree)
浏览器会逐行解析 HTML 文本,构建一个树状的文档对象模型(DOM)。<html> 是根节点,<body>、<div> 等都是其子节点。
2. 构建 CSSOM 树 (CSSOM Tree)
在解析过程中,如果遇到 <link> 或 <style> 标签,浏览器会并行地下载并解析 CSS 文件,构建一个树状的CSS对象模型(CSSOM)。
3. JavaScript 的执行 (JavaScript Execution)
- 当解析器遇到
<script>标签时,DOM 的构建会暂停(除非脚本被标记为async或defer)。 - 浏览器会立即下载、解析并执行脚本。
- 为什么会阻塞? 因为 JavaScript 可能会通过
document.write()或其他 DOM API 来修改 DOM 结构,所以浏览器必须等待脚本执行完毕才能继续构建 DOM,以避免不一致。async:- 特点:脚本会异步下载并在下载完成后立即执行,执行时仍会阻塞 DOM 构建,但多个
async脚本之间不保证执行顺序。 - 场景:
async最适合那些完全独立、无任何依赖关系的脚本。这些脚本不依赖于其他脚本,也不需要操作 DOM。- 网站分析脚本:例如 Google Analytics,它独立收集数据,不需要与页面其他脚本或 DOM 元素交互。
- 广告脚本:第三方广告通常是自包含的,可以独立加载和展示。
- 某些不需要立即执行的工具库或SDK。
- 特点:脚本会异步下载并在下载完成后立即执行,执行时仍会阻塞 DOM 构建,但多个
defer:- 特点:脚本会异步下载,但会等到整个 DOM 构建完成后、
DOMContentLoaded事件触发前才按顺序执行。这是推荐的实践。 - 场景:
defer是现代 Web 开发中的最佳实践和首选方案,尤其适用于以下情况:- 需要操作 DOM 的脚本:由于
defer脚本在 DOM 完全构建后才执行,因此可以安全地访问和操作任何 DOM 元素。我们之前的 GSAP 视差滚动案例就是绝佳例子。 - 有依赖关系的脚本:当你需要确保一个库文件在主应用脚本之前加载时,
defer可以保证正确的执行顺序。
- 需要操作 DOM 的脚本:由于
- 特点:脚本会异步下载,但会等到整个 DOM 构建完成后、
4. 构建渲染树 (Render Tree)
DOM 树和 CSSOM 树构建完成后,浏览器会将它们合并成一个渲染树。渲染树只包含需要被显示的节点以及它们的样式信息。例如,display: none 的节点就不会出现在渲染树中。
5. 布局 (Layout / Reflow) 有了渲染树,浏览器就可以计算出每个节点在屏幕上的确切位置和尺寸。这个过程从根节点开始,递归地进行,输出一个包含了所有元素几何信息的“盒模型”。
6. 绘制 (Paint) 布局阶段完成后,浏览器会遍历渲染树,调用UI后端API,将每个节点的视觉内容(颜色、边框、阴影等)绘制成多个图层 (Layers)。
7. 合成 (Composite)
最后一步,合成器线程会将这些图层按照正确的层叠顺序(z-index)合并在一起,最终显示在屏幕上,完成了整个页面的呈现。对于有动画或滚动的情况,如果只改变 transform 或 opacity,浏览器可以跳过布局和绘制阶段,只进行合成,这就是所谓的硬件加速,性能极高。
网络通信模型(7层、4层和5层模型)
TCP三次握手和四次挥手
三次握手(Three-Way Handshake)是在建立TCP连接时使用的过程,具体步骤如下:
-
第一步(SYN):客户端向服务器发送一个带有SYN(同步)标志的TCP报文段,表示客户端请求建立连接。
-
第二步(SYN-ACK):服务器收到客户端的请求后,向客户端发送一个带有SYN和ACK(确认)标志的TCP报文段,表示服务器接受连接请求,并发送确认。
-
第三步(ACK):客户端收到服务器的确认后,向服务器发送一个带有ACK标志的TCP报文段,表示客户端确认连接已建立。
四次挥手(Four-Way Handshake)是在终止TCP连接时使用的过程,具体步骤如下:
-
第一步(FIN):当客户端想要关闭连接时,发送一个带有FIN(结束)标志的TCP报文段给服务器,表示客户端不再发送数据。
-
第二步(ACK):服务器收到客户端的结束请求后,发送一个带有ACK标志的TCP报文段给客户端,表示服务器收到了结束请求。
-
第三步(FIN):当服务器也想要关闭连接时,发送一个带有FIN标志的TCP报文段给客户端,表示服务器不再发送数据。
-
第四步(ACK):客户端收到服务器的结束请求后,发送一个带有ACK标志的TCP报文段给服务器,表示客户端收到了结束请求。
少一次可以吗
- TCP 的连接需要三次握手,三次握手可以保证可靠性并且避免浪费资源。
- TCP 的断开需要四次挥手,四次挥手可以保证全双工通信彻底断开。
TCP和UDP的区别
“它们最主要的区别在于 可靠性 和 速度 的权衡。”
“我可以从三个方面来阐述它们的区别:”
第一,从连接方式上看:
- TCP是面向连接的协议。 在发送数据之前,它必须通过“三次握手”来建立一个可靠的连接。就像打电话,必须先接通才能开始对话。
- UDP是无连接的协议。 它不需要建立连接,可以直接把数据包(数据报)发送出去。就像寄明信片,写完地址就直接扔进邮筒,不需要确认对方是否在。
第二,从可靠性和效率上看:
- TCP非常可靠。 它通过序列号、确认应答、超时重传等机制,保证了数据能够完整、有序地到达。但这些复杂的机制也带来了更大的开销和更慢的速度。
- UDP是不可靠的。 它只负责尽力发送,不保证数据是否到达、是否按顺序到达。正因为省去了所有可靠性检查,它的开销非常小,速度极快。
第三,从使用场景上看,它们的区别就非常清晰了:
- TCP用在要求高可靠性的场景,比如网页浏览(HTTP/HTTPS)、文件传输(FTP)和电子邮件。在这些场景下,我们绝不能容忍数据出错或丢失。
- UDP用在追求高效率和实时性的场景,比如视频直播、在线游戏、语音通话(VoIP)。在这些场景下,我们宁愿偶尔丢失一个数据包(比如画面卡一下),也无法接受为了等一个迟到的包而导致整个应用卡顿。
“所以总结来说,选择哪个协议,本质上是在 ‘可靠性’ 和 ‘速度’ 之间做权衡。需要可靠稳定就用TCP,需要快速高效就用UDP。”
HTTP与HTTPS的区别
“最核心的区别就在于 安全。”
-
“HTTP是明文传输的,不安全,数据在传输过程中可能会被窃听或篡改。而 HTTPS通过SSL/TLS协议对数据进行了加密,提供了数据传输的机密性、完整性和身份认证,是安全的。”
-
“具体来说,HTTPS比HTTP多了一个安全层(SSL/TLS)。这个安全层在建立连接时,会有一个握手过程。在这个过程中,服务器会向客户端出示它的数字证书来证明身份,然后双方通过非对称加密协商出一个对称密钥,之后的所有通信都用这个密钥进行加密。”
-
“所以,从技术上讲,HTTPS就是 HTTP + 加密 + 认证 + 完整性保护。另外,它们的默认端口也不同,HTTP是80,HTTPS是443。”
https、证书作用,浏览器依据什么判断证书合法性
1. HTTPS 是什么? HTTPS 是 HTTP 的安全版,通过 SSL/TLS 协议对数据进行加密和身份认证,保证传输的保密性、完整性和真实性。
2. 证书的作用是什么? SSL/TLS 证书主要有两个作用:
- 身份认证:证明网站是真实的、合法的,防止钓鱼网站。由受信任的 CA 机构签发。
- 提供公钥:用于在 SSL/TLS 握手时安全地协商后续通信要使用的对称加密密钥。
3. 浏览器如何判断证书合法性? 浏览器主要通过以下几点判断:
- 信任链验证:检查证书是否由浏览器信任的根 CA 机构签发,并验证整个证书链的签名。
- 有效期:检查证书是否在有效期内,未过期也未提前使用。
- 域名匹配:检查证书中的域名与当前访问的网站域名是否一致。
- 吊销状态:通过 CRL 或 OCSP 查询证书是否已被签发机构吊销。
HTTPS中间人攻击
“HTTPS中间人攻击的原理,就是攻击者把自己伪装成一个‘中间人’,同时欺骗客户端和服务器。”
-
“首先,攻击者会拦截客户端的请求,然后向客户端提供一个伪造的、假的数字证书。”
-
“浏览器的安全机制会立刻发现这个证书不是由受信任的CA颁发的,并会弹出严重的安全警告,提示用户连接不安全。”
-
“这个攻击只有在用户忽略浏览器警告、强制继续访问的情况下才能成功。”
-
“一旦用户选择继续,攻击者就能拿到用他的假公钥加密的会话密钥,从而解密客户端发来的所有信息。同时,他又作为正常客户端与服务器建立真正的HTTPS连接,实现对双向流量的窃听和篡改。”
“所以,HTTPS能防范中间人攻击,核心依赖两点:一是 CA证书的信任链机制,二是 用户必须信任并遵守浏览器的安全警告。”
HTTPS加密过程
- “首先,客户端和服务器先‘打个招呼’,商量好要用哪种加密方式。”
- “然后,服务器会把它由权威CA颁发的‘数字证书’发给客户端,这就像亮出身份证,上面带着一把‘公钥’,用来证明自己的身份。”
- “客户端验证‘身份证’没问题后,会生成一个随机的‘会话密钥’,然后用服务器给的那把‘公钥’把它锁起来,再发给服务器。这样只有服务器的‘私钥’才能解开,保证了安全。”
- “最后,服务器拿到会话密钥后,双方就用这个密钥进行对称加密通信,因为对称加密速度快,效率高。”
“整个过程的核心就是 ‘用非对称加密来安全地交换对称加密的密钥’,并且通过数字证书保证了对方不是冒牌货。”
长轮询与短轮询
短轮询:
客户端每隔一段时间向服务器发送http请求,服务器端在收到请求后,无论是否有数据更新,都直接进行响应。
长轮询:
客户端发送请求后不会立即返回数据,服务器端会阻塞请求,连接不会立即断开,直到服务器端有效更新或者是连接超时才返回,客户端才再次发出请求新建连接、如此反复从而获得最新数据。
WebSocket
1. WebSocket 是什么?
- WebSocket 是一种在单个TCP连接上进行全双工、双向通信的协议。它允许服务器主动向客户端推送数据,客户端也可以主动向服务器发送数据,实现真正的实时交互。
2. WebSocket 的连接过程?
- WebSocket 连接始于一次基于HTTP的“升级”请求。
- 客户端向服务器发送一个特殊的HTTP请求,头部包含
Upgrade: websocket和Connection: Upgrade以及一个Sec-WebSocket-Key。 - 服务器如果同意升级,会返回状态码
101 Switching Protocols,响应头也包含Upgrade: websocket和Connection: Upgrade,并根据客户端的Sec-WebSocket-Key计算出一个Sec-WebSocket-Accept值返回给客户端验证。 - 握手成功后,该TCP连接就从HTTP协议转换到了WebSocket协议,后续数据传输使用WebSocket的帧格式。
- 客户端向服务器发送一个特殊的HTTP请求,头部包含
3. WebSocket 相比 HTTP 的主要区别?
- 通信方式:HTTP是单向(请求-响应),WebSocket是双向实时的。
- 连接状态:HTTP通常是无状态的短连接或Keep-Alive下的复用,WebSocket是有状态的持久连接。
- 头部开销:HTTP每次请求都有头部,WebSocket握手后数据帧头部开销极小。
- 服务端推送:HTTP服务端推送麻烦(如长轮询),WebSocket原生支持服务端主动推送。
SSE
SSE (Server-Sent Events) 是一种允许服务器向客户端单向实时推送文本数据的技术。
它基于标准的 HTTP 协议,客户端通过 JavaScript 的 EventSource API 发起一个连接,服务器则保持这个 HTTP 连接打开,并可以持续地向客户端发送事件流。
核心特点是:
- 单向通信:数据只能从服务器流向客户端。
- 基于HTTP:易于实现和部署,兼容性好。
- 自动重连:浏览器会自动处理连接断开后的重连尝试。
- 文本数据:主要用于推送 UTF-8 编码的文本信息。
它非常适合那些只需要服务器向客户端推送更新的场景,比如新闻通知、股票行情更新等,并且实现起来比 WebSocket 更简单。
CDN
CDN,全称是内容分发网络(Content Delivery Network)。它的核心思想是将源站的内容分发到全球各地的缓存服务器上,当用户访问时,会从离用户地理位置最近的服务器上获取内容,从而达到加速的效果。
这就像是开连锁超市,而不是只在总部设一个大仓库。当你想买东西时,直接去家门口的连锁店,而不是大老远跑到总部仓库去取。
CDN的工作流程:
- 用户发起请求:用户在浏览器输入网址,比如
www.example.com。 - 智能DNS解析:用户的请求首先会被本地DNS系统解析,但这个解析请求会被CDN的DNS调度系统接管。
- 选择最优节点:CDN的调度系统会根据用户的IP地址、网络状况等因素,智能地选择一个离用户最近、负载最低、响应最快的边缘节点(Edge Node),并将其IP地址返回给用户。
- 内容获取:
- 用户的浏览器直接向这个最优的边缘节点发起请求。
- 如果这个节点有缓存用户想要的内容,就直接返回给用户,完成访问(这叫缓存命中)。
- 如果这个节点没有缓存,它就会向更上一层的区域节点或者直接向网站的源服务器去请求内容。
- 缓存并响应:边缘节点从源站获取到内容后,会在本地缓存一份(以便下次有附近的用户访问时能直接提供),然后再把内容发送给用户。
CDN加速的核心原理可以归结为三点:
-
地理位置优化(就近访问):
- 核心:通过将内容部署到用户附近,大大缩短了数据传输的物理距离,这是最主要、最有效的加速手段。
- 效果:显著降低网络延迟(Latency),减少数据包在公网上的跳转次数。
-
缓存机制(减少回源):
- 核心:边缘节点将热门内容缓存起来,大部分用户的请求都可以直接在边缘节点得到响应,而无需每次都回到源服务器(这个过程叫“回源”)。
- 效果:
- 减轻源站压力:源服务器不需要处理海量的用户请求,只需要响应少量边缘节点的回源请求即可,提高了网站的可用性和稳定性。
- 节省带宽成本:大大减少了源站的出口带宽消耗。
-
负载均衡:
- 核心:CDN的调度系统本身就是一个复杂的负载均衡系统,它能确保用户的请求被均匀地分配到各个健康的边缘节点上,避免了某个节点因访问量过大而崩溃。
- 效果:提高了整个服务的可靠性和高可用性。
CDN主要加速哪些内容?
CDN最适合加速静态资源,这些资源不经常变化,可以长时间缓存。例如:
- 图片、视频、音频文件
- CSS、JavaScript脚本文件
- 网站字体、App安装包等
对于动态内容(如每个用户看到的个性化推荐、购物车信息等),虽然传统CDN不直接缓存,但也可以通过动态加速技术(DSA),优化回源路径和协议,来提升访问速度。
总结来说,CDN通过 “空间换时间” 的策略,用遍布全球的服务器节点作为代价,换取了用户访问速度的极大提升和源站的稳定性。
Axios 拦截器一般做什么?
Axios 拦截器的核心作用是在请求发出前和收到响应后集中处理通用的逻辑,以简化代码、提高可维护性。
请求拦截器 (Request Interceptor) - 发送前
- 核心:统一认证。自动为每个请求的请求头(
headers)附上AuthorizationToken,无需在每个业务请求中手动添加。 - 辅助: 显示全局
Loading动画、添加通用参数(如版本号、平台标识)、请求日志记录。
响应拦截器 (Response Interceptor) - 收到后
- 核心:统一错误处理。
- 处理 HTTP 状态码:例如,捕获
401(未授权)并强制用户跳转到登录页;捕获5xx(服务器错误)并给出统一的友好提示。 - 处理业务码:检查后端返回的自定义业务状态码(如
code: 1001),统一处理业务逻辑上的失败情况。
- 处理 HTTP 状态码:例如,捕获
- 辅助:
- 数据预处理:后端返回的数据通常在
data字段中,拦截器可以预先处理,直接返回核心数据,简化业务代码。 - 关闭
Loading动画。
- 数据预处理:后端返回的数据通常在
hash和history路由模式的区别?路由跳转和 location href 跳转的区别?
第一部分:hash 模式 vs history 模式
这是单页应用(SPA)中前端路由的两种主要实现方式,它们都实现了在不刷新整个页面的前提下更新视图。
| 特性 | hash 模式 | history 模式 |
|---|---|---|
| URL 外观 | http://a.com/#/user (带 #,不美观) | http://a.com/user (标准 URL,美观) |
| 实现原理 | 利用 URL 中的 hash (锚点) 和 window.onhashchange 事件。hash 值的改变不会触发浏览器向服务器发送请求。 | 利用 HTML5 的 History API (pushState, replaceState) 和 window.onpopstate 事件。URL 的改变不会触发浏览器发送请求。 |
| 服务器配置 | 无需特殊配置。因为服务器收到的请求地址永远是 # 之前的部分 (如 http://a.com),所以只需返回主 index.html 即可。 | 需要后端配置支持。当用户直接访问或刷新 http://a.com/user 时,浏览器会真的向服务器请求 /user 这个路径。服务器必须配置一个“后备/兜底”规则,将所有前端路由的请求都指向主 index.html 文件,否则会返回 404。 |
| 兼容性 | 兼容所有浏览器。 | 兼容支持 HTML5 History API 的现代浏览器(IE10+)。 |
总结:history 模式是目前的主流选择,因为它提供了更美观、更标准的 URL。它唯一的代价是需要后端进行简单的路由重定向配置。如果项目不方便配置后端,或者需要兼容非常古老的浏览器,hash 模式则是一个简单可靠的选择。
第二部分:路由跳转 vs location.href
这涉及到页面是否会“刷新”的根本区别。
-
路由跳转 (如
router.push('/user')或<router-link to="/user">)- 本质:页面无刷新,是 SPA 内部的“视图切换”。
- 过程:它利用上面提到的
hash或history模式的原理来改变浏览器地址栏的 URL,但阻止了浏览器的默认跳转行为。然后,JavaScript 接着工作,卸载旧的组件,挂载新的组件,从而更新页面内容。 - 结果:应用的内存状态(如 Vuex/Pinia/Redux 中的数据)得以保留,用户体验流畅,快速。这是单页应用的核心交互方式。
-
location.href跳转 (如location.href = '/user')- 本质:页面会完全刷新,是传统的“页面加载”。
- 过程:它会触发浏览器向服务器发送一个真实的 HTTP 请求,然后服务器返回一个新的 HTML 文档,浏览器接收并重新渲染整个页面。
- 结果:当前页面的所有状态、JavaScript 变量、组件实例等都会丢失。整个应用会重新初始化。这等同于用户在地址栏输入新地址后按回车,或者点击一个普通的
<a>标签。
一句话总结:路由跳转是“假”跳转,是 SPA 在不刷新页面下的内容更新;location.href 是“真”跳转,会导致整个网页重载。
用框架(React、Vue)打包出来的东西,为什么相比于普通的 html 不利于网站的 SEO 呢?有什么方法能够提高网站的SEO?
第一部分:为什么 SPA (客户端渲染) 不利于 SEO?
根本原因在于,传统的搜索引擎爬虫(Crawler)主要依赖解析 HTML 文件的内容来理解网页,而 SPA 的工作模式与此相悖。
-
爬虫看到的是“空壳” HTML
- 传统网站:服务器直接返回一个包含所有文本、图片、链接的完整 HTML。爬虫可以立即解析并收录。
- SPA 应用:浏览器首次请求时,服务器只返回一个极简的 HTML 文件,其
<body>中通常只有一个空的挂载点(如<div id="app"></div>)和一堆<script>标签。 - 问题:页面的实际内容是通过 JavaScript 执行,然后异步请求数据,最后才动态渲染到页面上的。早期的爬虫不会执行 JavaScript,所以它看到的就是一个没有内容的空页面,自然无法进行有效的 SEO 收录。
-
内容呈现慢,即便现代爬虫也存在问题
- 虽然现在 Google 的爬虫已经具备了执行 JS 的能力,但依然存在以下问题:
- 延迟索引:Google 爬虫需要一个“渲染队列”来处理 JS。它会先收录原始的空壳 HTML,等待一段时间后才能完成渲染和二次收录,这导致新内容被搜索引擎发现的速度变慢。
- 渲染可能失败:如果 JS 代码复杂、有错误或网络请求超时,都可能导致爬虫渲染失败。
- 性能影响排名:SPA 需要先下载和执行大量 JS 才能展示内容,导致首次内容绘制 (FCP) 时间较长,而页面加载速度是搜索引擎排名的一个重要因素。
- 虽然现在 Google 的爬虫已经具备了执行 JS 的能力,但依然存在以下问题:
-
其他搜索引擎支持更弱
- 相比 Google,像 Bing、Baidu 等其他搜索引擎对 JavaScript 的处理能力要弱得多,因此对 SPA 的收录效果更差。
第二部分:如何提高 SPA 的 SEO?
核心思想是:让服务器直接返回带有完整内容的 HTML 文件。主要有以下三种方案:
-
服务器端渲染 (Server-Side Rendering, SSR)
- 原理:在服务器端(通常是 Node.js 环境)直接执行 Vue/React 代码,将要访问的页面渲染成一个完整的 HTML 字符串,再发送给浏览器。这样无论访问者是用户还是爬虫,都能立刻得到完整的页面内容。
- 优点:SEO 效果最好,同时能显著提升首屏加载速度。
- 缺点:项目配置复杂,服务器压力增加。
- 主流框架:Next.js (React) 和 Nuxt.js (Vue) 是目前最成熟的 SSR 解决方案,极大地简化了开发。
-
静态站点生成 (Static Site Generation, SSG)
- 原理:在项目构建打包时,就提前为每个页面生成一个对应的、包含完整内容的纯静态 HTML 文件。
- 优点:拥有 SSR 的所有 SEO 好处,同时性能达到极致,因为用户访问的只是静态文件,可以轻松部署在 CDN 上。
- 缺点:只适用于内容不经常变动的网站,如博客、文档、营销官网。不适用于内容需要实时更新或高度个性化的场景。
- 主流框架:Next.js, Gatsby (React) 和 Nuxt.js (Vue) 也都提供了强大的 SSG 功能。
-
预渲染 (Prerendering / Dynamic Rendering)
- 原理:一个折中方案。通过判断请求头的
User-Agent:如果是普通用户,返回原始的 SPA;如果是搜索引擎爬虫,则将请求转发到一个预渲染服务(如 Prerender.io),该服务会用无头浏览器把页面渲染好,再将结果返回给爬虫。 - 优点:对现有 SPA 项目的改造成本最低。
- 缺点:需要维护额外的服务,对爬虫的响应可能较慢。
- 原理:一个折中方案。通过判断请求头的
总结:对于需要良好 SEO 的新项目,强烈推荐使用 Next.js 或 Nuxt.js,并根据需求选择 SSR 或 SSG 模式。
操作系统
线程与进程
- 进程(Process) :操作系统分配资源的最小单位,拥有独立的内存空间。
- 线程(Thread) :CPU调度的最小单位,共享进程内的资源。
前端角度下:
- 浏览器
- Browser进程:管理其他进程
- Renderer进程:每个标签页对应一个进程,负责页面渲染、JS执行
- GPU进程:加速图形渲染。
- 插件进程:隔离第三方插件。
- 渲染线程:计算样式、布局、绘制、合成等
- js
- js单线程
- Web Worker:允许在浏览器中创建子线程,复杂计算
- Service Worker:
- 独立于页面生命周期,可拦截网络请求。
- 支持离线缓存(Cache API)、消息推送(Push API)
安全
同源策略的安全限制
安全限制主要分三个方面:
- DOM层面:同源策略限制了不同源的js对当前DOM对象的读写操作
- 数据层面:同源策略限制了不同源站点读取当前站点的Cookies、IndexDB、LocalStorage等数据
- 网络层面:同源策略限制了数据发送给非同源站点(比如XML HttpRequest、Fetch等无法请求不同源站点)
预检请求
- 定义:预检请求(Preflight Request) 是浏览器在发送某些跨域请求(CORS,跨源资源共享)之前,自动发起的一个OPTIONS请求。它的目的是确认服务器是否允许实际的跨域请求,从而避免潜在的安全风险
- 哪些情况会触发预检请求
- 使用非简单请求方法(如
PUT、DELETE、PATCH等,简单方法仅限GET、POST、HEAD)。 - 包含自定义请求头(如
Authorization、X-Custom-Header)。 Content-Type不是简单值(如application/json,而简单值包括text/plain、multipart/form-data、application/x-www-form-urlencoded)。
- 使用非简单请求方法(如
- 合理利用
Access-Control-Max-Age可减少预检请求次数,提升性能
解决跨域方案
- 通过jsonp跨域
- document.domain + iframe跨域(主子跨站)
- location.hash + iframe(a欲与b跨域相互通信,通过与a同域的中间页c来实现)
- window.name + iframe跨域(window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB))
- postMessage(data,origin)跨域
- 跨域资源共享(CORS)
response.setHeader("Access-Control-Allow-Origin", "www.domain1.com"); 7. nginx代理跨域
server {
listen 80;
server_name your-domain.com;
location / {
# 允许所有来源的跨域请求(生产环境建议限制为具体域名)
add_header 'Access-Control-Allow-Origin' '*';
# 允许的请求方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
# 允许的请求头
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
# 预检请求缓存时间(单位:秒)
add_header 'Access-Control-Max-Age' 1728000;
# 允许携带凭证(如 Cookie、Authorization)
add_header 'Access-Control-Allow-Credentials' 'true';
# 处理 OPTIONS 预检请求
if ($request_method = 'OPTIONS') {
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain; charset=utf-8';
return 204;
}
# 其他代理配置(如反向代理到后端服务)
proxy_pass http://backend-server;
}
}
8. webpack、vite代理跨域
9. WebSocket协议跨域
在握手阶段,浏览器会向服务器发送一个特殊的HTTP请求,这个请求包含了Origin头,用于告知服务器请求的来源。如果服务器响应中包含一个允许跨域的Access-Control-Allow-Origin头,那么WebSocket连接就可以建立
前端安全问题(XSS、CSRF)及防御措施
一、XSS (跨站脚本攻击)
- 是什么: 攻击者在网页中注入恶意 JavaScript 脚本,用户访问时脚本执行,可能导致窃取Cookie、DOM篡改等。
- 如何防:
- 输出转义/编码: 核心手段。对展示在HTML、JavaScript、CSS中的用户输入进行相应转义(如
<转为<)。 - CSP (内容安全策略): 通过HTTP头限制浏览器加载资源的来源,禁止内联脚本和
eval。 - HttpOnly Cookie: 防止脚本读取敏感Cookie。
- 输入验证: 作为辅助。
- 输出转义/编码: 核心手段。对展示在HTML、JavaScript、CSS中的用户输入进行相应转义(如
二、CSRF (跨站请求伪造)
- 是什么: 攻击者诱导已登录用户在不知情的情况下,向目标网站发送恶意请求(如转账、修改密码),利用的是浏览器会自动携带目标域Cookie的特性。
- 如何防:
- Anti-CSRF Token: 最有效。服务器生成随机Token,前端请求时携带,服务器验证Token。
- SameSite Cookie属性: 设置为
Strict或Lax,限制第三方Cookie发送。 - 验证Referer: 检查请求来源,可作为辅助。
- 关键操作二次验证: 如验证码、短信确认。
简单总结:
- XSS 是“代码注入”,防御核心是“不信任用户输入,对输出编码”。
- CSRF 是“身份盗用”,防御核心是“验证请求来源或意图”。