Source: 前端常见八股文
工程化
- Webpack 性能优化有哪些方法
- Webpack 的 loader 和 plugin 区别是什么?常用的 plugin 和 loader 有哪些
- Webpack 构建流程是什么
- 讲讲 tree-shaking 原理
性能优化
安全
网络通信
- 说说从输入 url 到页面展示出来的整个过程
- 什么是跨域?为什么会出现跨域?如何解决跨域问题?Jsonp 原理是什么
- Http 各版本的改进都是什么
- Https 原理是什么?为什么可以保证安全性
- Http 常见状态码有哪些
- Http 有哪些方法
- Get 和 post 区别是什么
- 讲讲 http 缓存机制
- Cdn 是什么?它的原理是什么
- 讲讲 304 协商缓存过程
- 浏览器有哪些缓存?LocalStorage、sessionStorage、cookie、session 的区别是什么
Webpack 性能优化有哪些方法
(你的答案)
Webpack 的 loader 和 plugin 区别是什么?常用的 plugin 和 loader 有哪些
Loaders 的核心任务是让 Webpack 能够处理 JavaScript 以外的文件; Plugins 的能力范围比 Loader 广得多,负责构建流程中的各种优化和自动化。
常见的Loader:
babel-loader
: 最核心的 Loader。使用 Babel 将 ES6/ES7+ 的现代 JavaScript 语法转换为向后兼容的 ES5 语法。ts-loader
: 将 TypeScript (.ts
文件) 转换为 JavaScript。style-loader
: 将css-loader
处理后的 CSS 内容,通过<style>
标签动态注入到 HTML 的<head>
中。css-loader
: 解析 CSS 文件,处理@import
和url()
等语法,但它只负责解析,不负责应用样式。sass-loader
/less-loader
: 分别将 Sass/SCSS 或 Less 文件编译成标准的 CSS。file-loader
/asset modules
(Webpack 5+): 处理图片、字体等文件。它会将文件复制到输出目录,并返回其公共 URL。url-loader
: 类似于file-loader
,但它可以将小于指定大小(limit)的文件转换为 Base64 编码的 Data URL,直接嵌入到代码中,从而减少 HTTP 请求。
常见的Plugin
HtmlWebpackPlugin
: 几乎是必备插件。它会自动生成一个 HTML 文件,并将 Webpack 打包后的所有 JS/CSS 文件(bundles)自动通过<script>
和<link>
标签注入进去。CleanWebpackPlugin
: 在每次成功构建前,自动清理output.path
目录(通常是dist
文件夹),防止旧的构建文件残留。MiniCssExtractPlugin
: 生产环境必备。它会将 CSS 从 JS 包中提取出来,生成一个独立的.css
文件。这与style-loader
(将 CSS 注入 JS)的做法相反,可以实现 CSS 和 JS 的并行加载,提升性能。TerserWebpackPlugin
: Webpack 5 内置的 JS 代码压缩插件(用于生产模式)。它使用 Terser 来移除注释、空格,并进行代码混淆,以减小最终包的体积。通常无需手动配置,在mode: 'production'
时会自动启用。DefinePlugin
: Webpack 内置插件。允许你创建可在编译时配置的全局常量,非常适合用于区分开发环境和生产环境(例如,定义process.env.NODE_ENV
)。
Webpack 构建流程是什么
(你的答案)
讲讲 tree-shaking 原理
Tree-Shaking 的核心原理
Tree-Shaking 的实现,强依赖于 ES Modules (ESM) 的静态模块结构。这是理解其原理的关键。
ES Modules (ESM) vs. CommonJS
- ES Modules (
import
/export
)- 静态分析:ESM 的导入导出关系在代码编译时就已经确定了,是静态的。你不能在
if
语句里import
,也不能把import
的路径写成一个变量。 - 特点:
import { a } from './utils.js'
这种语法,打包工具(如 Webpack, Rollup)在不运行代码的情况下,通过静态分析就能明确知道:“我只需要utils.js
模块里的a
,别的我都不需要。”
- 静态分析:ESM 的导入导出关系在代码编译时就已经确定了,是静态的。你不能在
- CommonJS (
require
/module.exports
)- 动态加载:CommonJS 的模块导入是运行时加载的,是动态的。
require
的路径可以是一个变量,它可以在任何代码逻辑中执行。 - 特点:
const myModule = require(myPath + '/utils.js')
。打包工具在构建时无法确定myPath
到底是什么,也就无法知道到底加载了什么,更无法判断模块里的哪些代码被使用了。require
返回的是一个包含所有exports
的完整对象。
- 动态加载:CommonJS 的模块导入是运行时加载的,是动态的。
Tree-Shaking 的工作流程
Tree-Shaking 的过程通常可以分为两个阶段,以 Webpack 为例:
阶段一:标记 (Marking)
- 构建依赖图:Webpack 从入口文件(entry point)开始,根据
import
语句遍历所有模块,构建出一个依赖图(Dependency Graph)。 - 标记“活”代码:Webpack 遍历依赖图,标记出哪些模块的哪些导出(export)被实际使用了。
- 所有被
import
并且在代码中被调用的导出,都会被标记为/* harmony export used */
。 - 所有只被
import
但未被使用的导出,不会被标记。 - 所有从未被
import
的模块和导出,自然也是未被标记的。
- 所有被
阶段二:摇掉 (Shaking / Sweeping)
- 代码压缩与删除:这个阶段通常由代码压缩工具(minifier)来完成,比如 Terser (Webpack 5 内置的压缩插件)。
- Terser 在压缩代码时,会检查到那些在第一阶段没有被标记为 “used” 的代码。它会认为这些代码是“死代码”(dead code),并在最终生成 bundle 文件时,将它们彻底地从代码中删除。
前端页面性能如何优化
一、 加载优化 (让资源更快到达浏览器)
这是性能优化的第一道关卡,目标是减少网络请求的数量和体积。
1. 减少资源体积
- 代码压缩 (Minification):使用工具(如 Terser、cssnano)移除 JS、CSS、HTML 中的空格、注释和不必要的字符。这是构建流程中的标配。
- 资源压缩 (Compression):在服务器端开启 Gzip 或 Brotli 压缩。Brotli 的压缩率更高,但需要浏览器和服务器都支持。这是性价比最高的优化手段之一。
- 图片优化:
- 选择合适的格式:
- WebP / AVIF:在同等画质下体积远小于 JPEG/PNG,是现代网页首选。使用
<picture>
标签做兼容性降级。 - SVG:用于图标和简单的矢量图形,无限缩放且体积小。
- WebP / AVIF:在同等画质下体积远小于 JPEG/PNG,是现代网页首选。使用
- 图片压缩:使用工具(如 TinyPNG、ImageOptim)或构建插件对图片进行有损或无损压缩。
- 响应式图片:使用
<img>
标签的srcset
和sizes
属性,让浏览器根据屏幕尺寸和分辨率加载最合适大小的图片,避免在小屏上加载大图。
- 选择合适的格式:
- 字体优化:
- 字体裁剪:只打包用到的字符(例如,一个网站可能只需要几百个常用汉字)。
- 使用 WOFF2 格式:这是目前压缩率最高的字体格式。
2. 减少请求数量
- 代码合并 (Bundling):使用 Webpack、Vite 等构建工具将多个 JS、CSS 文件合并成一个或几个文件,减少 HTTP 请求次数。
- 按需加载 (Code Splitting):这是现代框架的核心优化。
- 路由懒加载:只有当用户访问某个页面时,才加载该页面的 JS 和 CSS。
- 组件懒加载:对于非首屏或需要用户交互后才出现的组件(如弹窗、折叠面板),进行动态
import()
。
3. 加快请求速度
- 使用 CDN (Content Delivery Network):将静态资源(JS, CSS, 图片, 字体)部署到 CDN。CDN 会将资源缓存到离用户最近的服务器上,极大缩短网络延迟。
- 利用浏览器缓存:
- 强缓存 (
Cache-Control: max-age
,Expires
):资源在有效期内,浏览器直接从本地读取,不发请求。适用于不常变化的静态资源。 - 协商缓存 (
ETag
,Last-Modified
):资源过期后,浏览器向服务器发送请求,服务器判断资源是否变化,若未变则返回304 Not Modified
,浏览器继续使用本地缓存。
- 强缓存 (
二、 渲染优化 (让页面更快地绘制出来)
资源到达后,浏览器需要解析、计算并绘制页面。这个过程也需要优化。
- 懒加载 (Lazy Loading):
- 图片懒加载:对于非首屏的图片,使用
loading="lazy"
属性(浏览器原生支持)或第三方库,在图片进入视口时才开始加载。 - iframe 懒加载:同理,使用
loading="lazy"
。
- 图片懒加载:对于非首屏的图片,使用
三、 运行时优化 (让交互更流畅)
页面加载完成后,用户开始交互,此时的性能同样重要。
- 减少重绘 (Repaint) 和回流 (Reflow):
- 回流 (Reflow/Layout):当 DOM 元素的几何属性(如宽高、位置)发生变化,浏览器需要重新计算布局。回流非常耗性能。
- 重绘 (Repaint):当元素的视觉属性(如颜色、背景)发生变化,但不影响布局时,浏览器只需重新绘制。
- 优化策略:
- 批量修改 DOM:使用
DocumentFragment
或在display: none
的元素上进行多次操作,最后一次性添加到 DOM 中。 - 避免频繁读取布局属性:将读取(如
el.offsetWidth
)和写入操作分离,不要混在一起。 - 使用 CSS Transform 和 Opacity 实现动画:
transform
和opacity
的变化通常可以被 GPU 加速,不会触发回流和重绘,性能远高于使用top
/left
。
- 批量修改 DOM:使用
- 使用节流 (Throttle) 和防抖 (Debounce):
- 防抖 (Debounce):对于高频触发的事件(如输入框搜索),在用户停止操作一段时间后才执行一次回调。
- 节流 (Throttle):对于高频触发的事件(如
scroll
、resize
),保证在固定时间间隔内只执行一次回调。
- 使用 Web Workers:对于复杂的、计算密集型的任务(如数据处理、加解密),可以将其放到 Web Worker 中执行,避免阻塞主线程,从而保持 UI 的响应性。
- 虚拟列表 (Virtual List):对于需要渲染成千上万条数据的长列表,只渲染视口内和缓冲区内的列表项,极大提升渲染性能和滚动流畅度。
四、 工具与监控 (科学地度量和分析)
没有度量,就没有优化。
- 使用性能分析工具:
- Chrome DevTools:
- Lighthouse:提供全面的性能审计报告和优化建议,关注 Core Web Vitals。
- Performance 面板:录制页面运行时,生成火焰图,用于深入分析 JS 执行瓶颈、渲染性能等。
- Network 面板:分析资源加载情况,查看瀑布图、缓存状态等。
- Chrome DevTools:
- 关注核心 Web 指标 (Core Web Vitals):
- LCP (Largest Contentful Paint):最大内容绘制,衡量加载性能。目标是小于 2.5 秒。
- FID (First Input Delay) / INP (Interaction to Next Paint):首次输入延迟/下次绘制交互,衡量交互性。目标是小于 100 毫秒。
- CLS (Cumulative Layout Shift):累积布局偏移,衡量视觉稳定性。目标是小于 0.1。
讲讲回流和重绘的区别,如何避免回流和重绘
回流也被成为重排,以下用 重排 来称呼;
引用一下我觉得说的很好的一段话:
“重排和重绘是浏览器关键渲染路径上的两个节点, 浏览器的关键渲染路径就是 DOM 和 CSSOM 生成渲染树,然后根据渲染树通过一个布局(也叫 layout)步骤来确定页面上所有内容的大小和位置,确定布局后,将像素 绘制 (也叫 Paint)到屏幕上。 其中重排就是当元素的位置发生变动的时候,浏览器重新执行布局这个步骤,来重新确定页面上内容的大小和位置,确定完之后就会进行重新绘制到屏幕上,所以重排一定会导致重绘。 如果元素位置没有发生变动,仅仅只是样式发生变动,这个时候浏览器重新渲染的时候会跳过布局步骤,直接进入绘制步骤,这就是重绘,所以重绘不一定会导致重排。” from https://cloud.tencent.com/developer/article/1967594
触发回流 (Reflow) 的常见操作:
- 页面首次渲染:这是不可避免的一次。
- DOM 节点操作:添加、删除、修改 DOM 节点。
- 元素几何属性变化:修改
width
,height
,margin
,padding
,border
,left
,top
等。 - 元素内容变化:文本数量或图片大小的改变,导致元素尺寸变化。
- 浏览器窗口尺寸变化 (resize)。
- 获取某些布局属性:这是一个非常隐蔽的触发点。当你用 JavaScript 读取像
offsetWidth
,offsetHeight
,scrollTop
,clientWidth
,getComputedStyle()
等属性时,浏览器为了给你一个精确的、最新的值,必须立即执行一次回流操作,以确保所有布局都是最新的。
仅触发重绘 (Repaint) 的常见操作:
- 修改
color
,background-color
,outline
,box-shadow
,visibility
等不影响布局的样式。
如何避免或减少回流和重绘?
优化的核心思想是:减少回流的次数和范围。
1. CSS 层面
- 使用
transform
和opacity
实现动画- 这是最重要的优化手段。
transform
(位移、缩放、旋转) 和opacity
(透明度) 的变化不会触发回流和重绘。浏览器会将这些元素的渲染提升到一个独立的“合成层”(Compositor Layer),并交由 GPU 去处理。这被称为“硬件加速”。 - 反例:使用
top
/left
或margin
来实现位移动画,会在每一帧都触发回流,性能极差。
- 这是最重要的优化手段。
- 避免使用 table 布局
- 表格中一个微小的改动就可能导致整个表格的回流。Flexbox 和 Grid 布局在性能上表现更优。
- 将频繁回流的元素设置为
position: absolute
或fixed
- 这样可以将该元素脱离文档流,它的回流就不会影响到页面上的其他元素,从而限制了回流的范围。
2. JavaScript 层面
- 批量修改 DOM
- 对于复杂的动画,使用requestAnimationFrame
网络攻击有哪些?如何防御
1. XSS (Cross-Site Scripting, 跨站脚本攻击)
这是前端领域最常见、最知名的攻击。
-
攻击原理:
攻击者将恶意的 JavaScript 脚本注入到网页中,当其他用户访问这个网页时,这些脚本就会在用户的浏览器中执行。攻击者可以利用这些脚本窃取用户的 Cookie、Session、LocalStorage,或者伪装成用户进行操作(如发帖、转账)。- 存储型 XSS:恶意脚本被存储在服务器的数据库中(例如,在一篇博客文章或用户评论里),对所有访问该页面的用户造成影响。
- 反射型 XSS:恶意脚本存在于 URL 中(例如
https://example.com?q=<script>alert('xss')</script>
),用户点击这个恶意链接时,脚本被执行。通常用于钓鱼攻击。 - DOM 型 XSS:攻击不经过服务器,而是通过修改 URL 的片段,由前端 JavaScript 直接获取并执行,从而触发。
-
防御措施:
- 输出编码 (Output Encoding):这是最核心、最有效的防御手段。当需要将用户输入的内容展示在页面上时,对其进行 HTML 编码。将特殊字符(如
<
、>
、"
、'
)转换为 HTML 实体(<
、>
、"
、'
)。现代前端框架(如 React, Vue)默认都会进行输出编码。 - 输入验证 (Input Validation):对用户输入的数据格式进行校验,例如,年龄字段只能是数字,用户名只能是字母和数字。但这只能作为辅助手段,不能完全防止 XSS。
- 内容安全策略 (Content Security Policy, CSP):通过设置 HTTP 头部
Content-Security-Policy
,可以限制浏览器只加载和执行来自可信来源的脚本,有效防止未知脚本的执行。 - 设置 HttpOnly Cookie:给关键的 Cookie 设置
HttpOnly
属性,这样 JavaScript 就无法读取到该 Cookie,即使发生 XSS 攻击,攻击者也无法轻易窃取用户的会话信息。
- 输出编码 (Output Encoding):这是最核心、最有效的防御手段。当需要将用户输入的内容展示在页面上时,对其进行 HTML 编码。将特殊字符(如
2. CSRF (Cross-Site Request Forgery, 跨站请求伪造)
-
攻击原理:
攻击者诱导已登录的用户访问一个恶意网站(evil.com
)。在这个恶意网站上,攻击者放置了向你的网站(bank.com
)发送请求的代码(例如,一个隐藏的表单或一个<img>
标签)。因为用户在bank.com
是登录状态,浏览器会自动携带bank.com
的 Cookie 发送这个请求,从而在用户不知情的情况下,以用户的名义执行了恶意操作(如转账、修改密码)。核心是:攻击者利用了用户的登录状态,伪造了用户的请求。
-
防御措施:
- 使用 Anti-CSRF Token:在用户访问表单页面时,服务器生成一个随机的、不可预测的 Token,并将其放在表单的隐藏字段和用户的 Session 中。当用户提交表单时,服务器验证表单中的 Token 是否与 Session 中的一致。攻击者无法获取这个 Token,因此无法伪造请求。这是最传统的防御方式。
- 使用 SameSite Cookie 属性:这是更现代、更简单的防御方式。在设置 Cookie 时,将其
SameSite
属性设置为Strict
或Lax
。SameSite=Strict
:完全禁止第三方 Cookie,浏览器在任何跨站请求中都不会携带该 Cookie。SameSite=Lax
:在大多数跨站请求中不发送 Cookie,但在一些安全的导航操作(如点击链接跳转)中会发送。这足以防御大部分 CSRF 攻击。
- 验证 Referer/Origin 头:检查 HTTP 请求头中的
Referer
或Origin
字段,确保请求来自可信的源。但这个头部可以被伪造,所以只能作为辅助防御手段。
3. SQL 注入 (SQL Injection)
-
攻击原理:
当应用程序将用户输入的内容直接拼接到 SQL 查询语句中时,攻击者可以通过构造恶意的输入,来改变原始 SQL 语句的语义,从而执行非预期的数据库操作,如窃取数据、删除数据,甚至获取服务器的控制权。例如,一个登录查询:
SELECT * FROM users WHERE username = '
+ username +' AND password = '
+ password +'
。
如果攻击者输入用户名为admin' --
,密码随意,SQL 语句就变成了SELECT * FROM users WHERE username = 'admin' --' AND ...
,--
是 SQL 的注释符,直接绕过了密码验证。 -
防御措施:
- 使用参数化查询 (Parameterized Queries) 或预编译语句 (Prepared Statements):这是防止 SQL 注入的唯一可靠方法。它将 SQL 命令和用户数据分开发送给数据库。数据库引擎会把用户输入的数据当作纯粹的“数据”,而不是“命令”,从而从根本上杜绝了注入的可能。
- 使用 ORM (Object-Relational Mapping):现代的 ORM 框架(如 Sequelize, TypeORM, Hibernate)通常默认使用参数化查询,能有效防止 SQL 注入。
- 对输入进行转义:作为最后的防线,可以对用户输入的特殊字符进行转义,但这非常容易出错,不推荐作为主要防御手段。
4. DDoS (Distributed Denial of Service, 分布式拒绝服务攻击)
-
攻击原理:
攻击者控制大量的“僵尸网络”(被病毒感染的计算机),同时向目标服务器发送海量的无效或高负荷的请求,耗尽服务器的带宽、CPU、内存等资源,使其无法响应正常用户的请求,导致服务瘫痪。 -
防御措施:
DDoS 防御通常不是靠应用层代码能完全解决的,更多依赖于网络和基础设施层面。- 使用 CDN (Content Delivery Network):CDN 可以将流量分散到全球各地的节点,其巨大的带宽和分布式架构可以吸收和清洗大量的攻击流量。
- 使用 WAF (Web Application Firewall):专业的硬件或云 WAF 可以识别并拦截恶意的流量模式。
- 流量清洗服务:购买专业的 DDoS 防护服务,在流量到达你的服务器之前就进行清洗。
- 负载均衡:将流量分发到多个服务器,避免单点故障。
- 应用层限流 (Rate Limiting):限制单个 IP 地址在单位时间内的请求次数。
5. 点击劫持 (Clickjacking)
-
攻击原理:
攻击者创建一个透明的<iframe>
,覆盖在一个看似无害的网页上(例如,一个“点击领奖”的按钮)。<iframe>
中加载的是你的网站(例如,一个删除账户的页面)。当用户点击“领奖”按钮时,实际上点击的是透明<iframe>
中的“确认删除”按钮,从而在不知情的情况下执行了危险操作。 -
防御措施:
- 设置
X-Frame-Options
HTTP 头:这是一个专门用来防止点击劫持的头部。DENY
:禁止任何页面通过<iframe>
嵌入该页面。SAMEORIGIN
:只允许同源的页面嵌入。
- 使用 CSP 的
frame-ancestors
指令:这是X-Frame-Options
的现代替代品,功能更强大,可以指定允许嵌入的来源。Content-Security-Policy: frame-ancestors 'self' example.com;
- 设置
说说从输入 url 到页面展示出来的整个过程
整个过程可以概括为:
- URL 解析与检查:浏览器处理用户输入。
- 网络请求:浏览器通过网络协议栈向服务器发送请求。
- 服务器响应:服务器处理请求并返回数据。
- 浏览器渲染:浏览器将返回的数据渲染成用户可见的页面。
重点讲一下浏览器渲染,这也被称为关键渲染路径
- 解析 HTML,构建 DOM 树 (DOM Tree)
- 浏览器接收到 HTML 文件后,会自上而下地进行解析,将 HTML 标签转换成一个树状结构的 DOM (Document Object Model)。
- 解析 CSS,构建 CSSOM 树 (CSSOM Tree)
- 在解析 HTML 的过程中,如果遇到
<link rel="stylesheet">
或<style>
标签,浏览器会开始异步下载并解析 CSS 文件,构建一个树状的 CSSOM (CSS Object Model),它包含了所有元素的样式信息。
- 在解析 HTML 的过程中,如果遇到
- 执行 JavaScript
- 如果解析时遇到
<script>
标签(且没有async
或defer
属性),HTML 解析会暂停。浏览器会下载、解析并执行 JavaScript 代码。 - 为什么会暂停? 因为 JavaScript 可能会修改 DOM(例如
document.write
),所以浏览器必须先执行完 JS,才能确定后续的 DOM 结构。这就是为什么通常建议将<script>
放在<body>
底部的原因 - 如果把
<script>
放入<head>
中,最核心的影响就是,他会阻塞页面的渲染
- 如果解析时遇到
- 构建渲染树 (Render Tree)
- 当 DOM 树和 CSSOM 树都构建完毕后,浏览器会将它们结合起来,生成 渲染树 (Render Tree)。
- 渲染树只包含需要被渲染的节点(例如,
display: none
的节点不会在渲染树中)。它包含了每个节点的样式信息。
- 布局 (Layout / Reflow)
- 浏览器根据渲染树,计算出每个节点在屏幕上的确切位置和大小。这个过程称为布局或回流或重排
- 绘制 (Painting / Repaint)
- 布局完成后,浏览器会调用 GPU,将渲染树中的每个节点绘制到屏幕上,变成我们最终看到的像素。这个过程称为绘制或重绘。
- 后续加载与交互
- 主 HTML 文件渲染完成后,浏览器会继续加载页面中引用的其他资源,如图片、字体等。
- 当这些资源加载完成并显示出来,同时 JavaScript 也执行完毕(例如绑定了事件监听器),页面就变得完全可交互了。
什么是跨域?为什么会出现跨域?如何解决跨域问题?Jsonp 原理是什么
跨域(Cross-Origin) 指的是一个源(Origin) 的网页脚本试图去请求另一个源的资源,但被浏览器限制了。
要理解跨域,首先必须理解同源策略(Same-Origin Policy)。
同源策略是浏览器内置的一个核心安全功能。它规定,只有当两个页面的协议(Protocol)、域名(Domain)和端口(Port) 三者完全相同时,才算作“同源”。
URL | 是否同源 | 原因 |
---|---|---|
http://www.example.com/api/data | 是 | 协议、域名、端口都相同(80是http默认端口) |
https://www.example.com/api/data | 否 | 协议不同 (http vs https) |
http://api.example.com/api/data | 否 | 域名不同 (www.example.com vs api.example.com) |
http://www.example.com:8080/api/data | 否 | 端口不同 (80 vs 8080) |
http://www.another.com/api/data | 否 | 域名不同 |
如何解决跨域问题?
解决跨域问题的核心思想是:让目标服务器明确告知浏览器,我允许这个源来访问我的资源。 以下是几种主流的解决方案:
1. CORS (Cross-Origin Resource Sharing) - 跨域资源共享(主流推荐)
这是目前最标准、最主流的解决方案。它的核心在于服务器端的配置。
- 工作原理:浏览器在发送跨域请求时,会自动在请求头中添加一个
Origin
字段,表明请求来自哪个源。服务器收到请求后,根据这个Origin
值判断是否允许该请求。如果允许,服务器会在响应头(Response Header)中添加一个Access-Control-Allow-Origin
字段,其值为允许的源(或者是*
代表允许所有源)。浏览器收到响应后,会检查这个响应头,如果发现自己的源被允许,就正常处理响应;否则,就会拦截响应,并在控制台报错。
2. JSONP (JSON with Padding) - (历史悠久,但有局限)
这是早期常用的一种“投机取巧”的跨域方案
3. 代理服务器 (Proxy)
这是一种非常实用且灵活的方案,尤其在开发环境中。
-
工作原理:利用服务器之间通信不受同源策略限制的特点。
- 前端将请求发送到自己的同源服务器(这个服务器就是代理服务器)。
- 代理服务器再将这个请求转发给目标服务器。
- 目标服务器处理完请求,将响应返回给代理服务器。
- 代理服务器最后将响应返回给前端。
在整个过程中,前端与代理服务器是同源的,浏览器不会拦截。而服务器与服务器之间的通信不存在跨域问题。
-
实现:
- 开发环境:现代前端框架的脚手架(如 Vue CLI, Create React App)都内置了代理功能(devServer.proxy)。
- 生产环境:通过 Nginx 或其他 Web 服务器配置反向代理。
Http 各版本的改进都是什么
版本 | 核心改进 | 解决了什么问题 | 引入了什么新问题 |
---|---|---|---|
HTTP/1.0 | 引入头信息、状态码、多方法 | 实现了丰富的 Web 交互 | 短连接,性能低下 |
HTTP/1.1 | 持久连接、Host头、缓存 | 解决了短连接的开销问题 | 应用层队头阻塞 (HOL Blocking) |
HTTP/2 | 多路复用、二进制分帧、头部压缩,服务器推送 | 解决了应用层的队头阻塞 | TCP 层的队头阻塞 |
HTTP/3 | 基于 QUIC (UDP) | 彻底解决了 TCP 层的队头阻塞 | 推广和网络设备支持需要时间 |
Https 原理是什么?为什么可以保证安全性
HTTPS 的全称是 HyperText Transfer Protocol Secure,它本质上就是 HTTP + SSL/TLS。SSL/TLS 是一个安全层,负责对 HTTP 传输的数据进行加密和身份认证。
HTTPS 如何保证安全性?三大核心目标
HTTPS 主要通过解决以下三个问题来保证通信安全:
- 机密性 (Confidentiality):防止信息在传输过程中被第三方窃听。所有数据都经过加密,即使被截获,窃听者看到的也只是一堆无意义的乱码。
- 完整性 (Integrity):防止信息在传输过程中被篡改。通过数字签名等机制,接收方可以验证数据是否在途中被修改过。如果被改动,会立刻被发现。
- 身份认证 (Authentication):验证通信对方的身份,确保你正在访问的是真实的网站(如真实的银行网站),而不是一个伪造的“钓鱼”网站。
HTTPS 的工作原理:SSL/TLS 握手过程
HTTPS 的安全性核心在于其连接建立时的“握手 (Handshake)”过程。这个过程非常精妙,结合了非对称加密和对称加密的优点。
- 非对称加密:有公钥和私钥两把钥匙。公钥加密的数据只能用对应的私钥解密。优点是安全(私钥不暴露),缺点是计算复杂,速度慢。
- 对称加密:加密和解密用同一把钥匙。优点是速度快,缺点是如何安全地把这把钥匙交给对方是个难题。
第 1 步:客户端发起请求 (Client Hello)
你的浏览器(客户端)向服务器发起请求,并告诉服务器:
- 我支持哪些 SSL/TLS 版本。
- 我支持哪些加密算法(称为 Cipher Suite)。
- 我生成了一个随机数 (Client Random)。
第 2 步:服务器响应 (Server Hello)
服务器收到请求后,会回应:
- 确认使用的 SSL/TLS 版本和加密算法。
- 生成了另一个随机数 (Server Random)。
- 发送自己的数字证书 (Digital Certificate)。这是最关键的一步!
什么是数字证书?
这个证书由权威的 CA (Certificate Authority) 机构颁发,里面包含了:
- 网站的域名
- 证书的有效期
- 网站的公钥
- CA 机构的数字签名(用来证明这个证书是真的)
第 3 步:客户端验证与密钥交换
浏览器收到证书后,会进行验证:
- 验证证书真伪:
- 检查证书是否在有效期内。
- 检查证书的域名与正在访问的域名是否一致。
- 使用浏览器内置的、受信任的 CA 机构的公钥,去解密证书上的“CA 数字签名”。如果能成功解密,就证明这个证书确实是这个权威 CA 颁发的,而不是伪造的。
- 生成会话密钥:
- 如果证书验证通过,浏览器会再生成一个随机数 (Pre-Master Secret)。
- 然后,浏览器用从证书里拿到的服务器公钥,将这个
Pre-Master Secret
加密。 - 将加密后的内容发送给服务器。
第 4 步:服务器解密与生成会话密钥
- 服务器收到加密后的
Pre-Master Secret
后,用自己的私钥进行解密,获取到原始的Pre-Master Secret
。 - 现在,客户端和服务器同时拥有了三个相同的凭据:
Client Random
、Server Random
和Pre-Master Secret
。 - 双方使用约定好的算法,将这三个随机数混合在一起,生成一个完全相同的、用于后续通信的“会话密钥 (Session Key)”。这是一个对称密钥。
第 5 步:握手完成,开始加密通信
- 握手阶段结束。
- 从现在开始,客户端和服务器之间的所有 HTTP 数据,都使用这个刚刚生成的会话密钥进行对称加密后传输。因为对称加密速度非常快,所以不影响通信性能。
HTTPS 如何实现三大安全目标
- 身份认证:通过第 3 步的数字证书验证,浏览器确认了服务器的真实身份,防止了中间人伪装成目标服务器。
- 机密性:
- 在握手阶段,用于生成会话密钥的
Pre-Master Secret
是用服务器的公钥加密的,只有服务器的私钥能解开,保证了会话密钥本身的安全传递。 - 握手后,所有通信内容都由会话密钥进行对称加密,即使被截获也无法破解。
- 在握手阶段,用于生成会话密钥的
- 完整性:在发送加密数据时,会附带一个消息认证码 (MAC),这个 MAC 是由原始数据和会话密钥计算得出的。接收方在解密后,会用同样的方法计算 MAC,如果两个 MAC 一致,说明数据未被篡改。
Http 常见状态码有哪些
HTTP 状态码被分为五大类,通过第一个数字来区分:
- 1xx (信息性):表示服务器已接收请求,需要客户端继续执行操作。
- 2xx (成功):表示请求已成功被服务器接收、理解、并接受。
- 3xx (重定向):表示需要客户端采取进一步的操作才能完成请求。
- 4xx (客户端错误):表示客户端的请求有语法错误或请求无法实现。
- 5xx (服务器错误):表示服务器在处理请求的过程中发生了错误。
类别 | 常见代码 | 名称 | 简单描述 |
---|---|---|---|
2xx 成功 | 200 | OK | 请求成功 |
201 | Created | 已创建新资源 | |
204 | No Content | 成功,但无内容返回 | |
3xx 重定向 | 301 | Moved Permanently | 永久重定向 |
302 | Found | 临时重定向 | |
304 | Not Modified | 资源未修改,使用缓存 | |
4xx 客户端错误 | 400 | Bad Request | 请求语法错误 |
401 | Unauthorized | 未认证(需要登录) | |
403 | Forbidden | 已认证,但无权限 | |
404 | Not Found | 资源不存在 | |
5xx 服务器错误 | 500 | Internal Server Error | 服务器内部错误 |
502 | Bad Gateway | 网关错误(后端服务挂了) | |
504 | Gateway Timeout | 网关超时(后端服务响应慢) |
Http 有哪些方法
方法 | 主要用途 | 安全性 | 幂等性 | 常见场景 |
---|---|---|---|---|
GET | 查询资源 | ✅ 安全 | ✅ 幂等 | 浏览网页、获取数据 |
POST | 创建资源 | ❌ 不安全 | ❌ 不幂等 | 提交表单、上传文件 |
PUT | 整体更新或创建资源 | ❌ 不安全 | ✅ 幂等 | 更新完整的个人资料 |
DELETE | 删除资源 | ❌ 不安全 | ✅ 幂等 | 删除文章、注销账号 |
PATCH | 局部更新资源 | ❌ 不安全 | ❌ 不一定 | 只修改某个字段 |
HEAD | 获取资源的元信息(头部) | ✅ 安全 | ✅ 幂等 | 检查文件大小 |
OPTIONS | 查询服务器支持的方法 | ✅ 安全 | ✅ 幂等 | CORS 预检请求 |
Get 和 post 区别是什么
GET
(Get - 获取):其核心语义是从服务器获取资源。它是一个只读操作。POST
(Post - 提交):其核心语义是向服务器提交数据,以创建或更新资源。它会改变服务器的状态。
对比维度 | GET | POST |
---|---|---|
数据位置 | 参数附加在 URL 的查询字符串中,在 ? 之后。 | 参数放在 HTTP 请求体 (Request Body) 中。 |
数据可见性 | 可见。数据会显示在浏览器地址栏、浏览器历史记录和服务器日志中。 | 不可见。数据不会显示在地址栏,更适合传输敏感信息。 |
数据大小 | 有限制。因为 URL 长度受限于浏览器和服务器,通常在 2KB 到 8KB 之间。 | 无限制。理论上可以传输任意大小的数据(受服务器配置影响)。 |
安全性 (Safe) | 是。GET 请求不应该改变服务器上的任何资源。 | 否。POST 请求通常会改变服务器上的资源(如创建新用户)。 |
幂等性 (Idempotent) | 是。同一个 GET 请求执行多次,结果应该完全相同。 | 否。同一个 POST 请求执行多次,会创建多个资源(如多次提交订单)。 |
可缓存性 | 可缓存。浏览器可以缓存 GET 请求的响应,以提高性能。 | 不可缓存。默认情况下,POST 请求的响应不会被缓存。 |
浏览器行为 | - 可以被收藏为书签。 - 会被保存在浏览器历史记录中。 - 按后退/刷新按钮无害。 | - 不可被收藏为书签。 - 刷新页面时,浏览器会警告“是否重新提交表单”,防止重复提交。 |
主要用途 | 查询数据。如搜索、获取文章列表、查看商品详情。 | 提交数据。如用户登录、注册、提交订单、上传文件。 |
讲讲 http 缓存机制
(你的答案)
Cdn 是什么?它的原理是什么
CDN 的全称是 Content Delivery Network,中文翻译为“内容分发网络”。
举个生活中的例子:
假设你在上海,想买一本只在美国纽约一家出版社印刷的书。
- 没有 CDN 的情况:你下单后,书必须从纽约的仓库,通过飞机、海关、国内物流,长途跋涉送到你上海的家中。这个过程非常慢,而且运费昂贵。
- 有 CDN 的情况:这家出版社很聪明,它提前把热门书籍的库存,分发到了全球各大城市的“前置仓库”,比如在上海就有一个。你下单后,书直接从上海的仓库发货,第二天就能收到。
在这个例子里:
- 你 -> 互联网用户
- 纽约的出版社 -> 网站的源服务器 (Origin Server)
- 书籍 -> 网站的内容 (Content),如图片、视频、CSS、JS 文件等
- 全球的前置仓库 -> 就是 CDN 的节点服务器 (Edge Node)
所以,CDN 的本质是一个由分布在世界各地的服务器组成的、智能化的网络系统。它的核心目标是,让用户能够从离他们地理位置最近的服务器上获取所需内容,从而提高访问速度、减少延迟,并分担源服务器的压力。
工作流程
现在,一个在北京的用户想要访问 example.com
网站。
-
用户发起请求:用户的浏览器开始加载网页,当它需要请求一个静态资源时(比如
http://static.example.com/logo.png
),它需要先知道这个域名的 IP 地址。 -
智能 DNS 解析 (关键步骤):这是 CDN 的“大脑”。
- 用户的浏览器向本地 DNS 服务器发起对
static.example.com
的解析请求。 - 本地 DNS 服务器会把这个请求转发给 CDN 服务商的权威 DNS 服务器。
- CDN 的 DNS 服务器不是简单地返回一个固定的 IP 地址。它会进行智能判断:
- 判断请求来源:它会分析发起请求的 DNS 服务器的 IP 地址,从而大致判断出用户的地理位置(比如,这个请求来自北京的某个运营商)。
- 选择最佳节点:根据用户的位置、各个边缘节点的健康状况、负载情况以及网络拥堵情况,它会选择一个综合最优的边缘节点。对于北京的用户,它很可能会选择一个位于北京或天津的 CDN 节点。
- 返回 IP 地址:最后,它将这个最优节点的 IP 地址返回给用户的浏览器。
- 用户的浏览器向本地 DNS 服务器发起对
-
获取内容:
- 用户的浏览器拿到了这个最优节点的 IP 地址(比如,北京节点的 IP)。
- 浏览器直接向这个北京的 CDN 节点发起对
logo.png
的请求。
-
响应请求:
- 缓存命中 (Cache Hit):如果北京节点已经缓存了
logo.png
这个文件,它会立刻将文件返回给用户。这个过程极快,因为物理距离非常近。 - 缓存未命中 (Cache Miss):如果这是第一次有该区域的用户请求这个文件,北京节点上可能还没有缓存。这时,北京节点会向源服务器(
example.com
的主服务器)发起请求,获取logo.png
文件。拿到文件后,它会做两件事:- 将文件存储在自己的缓存中,以便下次该区域有用户请求时可以直接返回(变为 Cache Hit)。
- 将文件发送给当前请求的用户。
- 缓存命中 (Cache Hit):如果北京节点已经缓存了
讲讲 304 协商缓存过程
见下
浏览器有哪些缓存?LocalStorage、sessionStorage、cookie、session 的区别是什么
浏览器的缓存机制
广义上讲,浏览器的缓存是为了提升网站性能和用户体验,将已经获取过的资源(如 HTML、CSS、JS、图片等)或数据存储在本地,以便下次访问时可以更快地加载。
主要可以分为以下几类:
1. HTTP 缓存 (Browser Cache)
这是最主要的缓存类型,它根据 HTTP 响应头中的字段来决定如何缓存资源。它又分为两种:
- 强缓存 (Strong Cache):浏览器在请求资源时,会先检查本地缓存。如果缓存未过期,则不向服务器发送任何请求,直接从本地读取资源,HTTP 状态码为
200 (from memory cache)
或200 (from disk cache)
。- 控制字段:
Expires
(HTTP/1.0):一个绝对时间的 GMT 格式字符串,表示缓存的过期时间。Cache-Control: max-age=xxx
(HTTP/1.1):一个相对时间,单位是秒,表示资源在被缓存后的xxx
秒内有效。优先级高于Expires
。
- 控制字段:
- 协商缓存 (Negotiation Cache):当强缓存失效后(或者
Cache-Control
设置为no-cache
),浏览器会向服务器发送一个请求,但会带上本地缓存的标识。服务器根据这个标识判断资源是否有更新。- 如果无更新,服务器返回
304 Not Modified
状态码,并且响应体为空。浏览器继续使用本地缓存。 - 如果有更新,服务器返回
200 OK
状态码,并附带最新的资源。 - 控制字段(成对出现):
Last-Modified
/If-Modified-Since
:服务器在响应中告诉浏览器资源的最后修改时间 (Last-Modified
)。浏览器下次请求时会带上这个时间 (If-Modified-Since
),服务器进行比较。ETag
/If-None-Match
:服务器为资源生成一个唯一的标识符 (ETag
)。浏览器下次请求时会带上这个标识符 (If-None-Match
),服务器进行比较。ETag
的优先级高于Last-Modified
,因为它能解决文件修改时间变了但内容没变等问题。
- 如果无更新,服务器返回
2. Web Storage (HTML5)
这是 HTML5 提供的客户端数据存储方案,用于存储键值对数据。它解决了 Cookie 存储量小且会随请求发送的问题。
- LocalStorage:永久性存储,除非用户手动清除或代码主动删除,否则数据一直存在。
- SessionStorage:会话级存储,数据仅在当前浏览器标签页的生命周期内有效。关闭标签页或浏览器后,数据被清除。
3. Cookie
在 Web Storage 出现之前,Cookie 是主要的客户端存储方案。它主要用于服务器和客户端之间的通信。
4. 其他缓存
- Service Worker Cache:一个更现代、更强大的缓存机制。它允许开发者通过 JavaScript 精确控制缓存策略,拦截网络请求,实现离线应用(PWA 的核心技术)。
- IndexedDB:一个在客户端存储大量结构化数据的数据库,可以看作是浏览器端的 NoSQL 数据库。适用于复杂的应用数据存储。
- Memory Cache:内存中的缓存,速度极快但生命周期短。当标签页关闭时,缓存就会被清除。通常用于缓存解码后的图片、脚本等。
- Push Cache:HTTP/2 的特性,服务器可以主动将资源“推送”到浏览器缓存中。
LocalStorage、sessionStorage、Cookie、Session 的区别
其中,Session 是一个纯服务端的概念,但它通常依赖于客户端的 Cookie 来维持状态,所以我们放在一起比较。
特性 | Cookie | LocalStorage | SessionStorage | Session |
---|---|---|---|---|
存储位置 | 客户端 | 客户端 | 客户端 | 服务端 |
生命周期 | 可设置过期时间。若不设置,则随浏览器关闭而失效;若设置,则在过期时间前一直有效。 | 永久有效,除非用户手动清除或代码删除。 | 当前会话/标签页有效,关闭标签页或浏览器后即失效。 | 服务端控制,通常在用户关闭浏览器或一段时间无活动后失效(超时)。 |
存储大小 | 约 4KB,非常小。 | 约 5-10MB,较大。 | 约 5-10MB,较大。 | 无限制,取决于服务器的内存或硬盘。 |
与服务器通信 | 每次 HTTP 请求都会自动携带在请求头中,即使是请求图片等静态资源。 | 从不自动发送给服务器,需要通过代码手动发送。 | 从不自动发送给服务器,需要通过代码手动发送。 | 数据本身在服务端,仅通过一个 Session ID (通常存在 Cookie 中) 与客户端关联。 |
作用域 | 同一浏览器内,所有符合其 path 和 domain 规则的页面共享。 | 同一源(协议、域名、端口)下的所有标签页和窗口共享。 | 仅在单个标签页内有效,不同标签页之间不共享。 | 在服务端与特定用户(通过 Session ID)关联。 |
API 易用性 | 原生 API (document.cookie ) 比较简陋,需要自己封装。 | 原生 API (setItem , getItem , removeItem ) 非常简单易用。 | 原生 API 与 LocalStorage 相同。 | 客户端 JS 无法直接访问,由服务端代码操作。 |
安全性 | 容易受到 CSRF、XSS 攻击。可设置 HttpOnly 属性防止 JS 读取,提升安全性。 | 容易受到 XSS 攻击。 | 容易受到 XSS 攻击。 | 相对最安全,因为核心数据存储在服务端,客户端只存一个无意义的 ID。 |
总结与应用场景
- Cookie:
- 场景:主要用于维持用户登录状态。服务器验证用户后,生成一个 Session ID 存入 Cookie,之后客户端每次请求都带上这个 Cookie,服务器以此识别用户。也用于用户行为跟踪(如 Google Analytics)。
- 优点:兼容性好,可设置 HttpOnly 防止脚本读取。
- 缺点:大小限制,性能开销(每次请求都携带)。
- LocalStorage:
- 场景:需要长期在本地存储大量数据的场景,且这些数据不常与服务器交互。例如:网站主题偏好(夜间模式)、用户自定义配置、缓存不常变动的应用数据、存储 JWT Token 等。
- 优点:容量大,API 简单,不影响网络性能。
- 缺点:永久存储可能导致数据过时,需要手动管理。
- SessionStorage:
- 场景:临时存储单个标签页的数据,用完即走。例如:在多步骤表单中,临时保存用户已填写的信息,防止刷新页面后丢失;或者在单页应用中保存当前页面的某些临时状态。
- 优点:API 简单,数据隔离性好(不同标签页不干扰),关闭即焚,无需手动清理。
- 缺点:生命周期短,无法跨标签页共享。
- Session:
- 场景:存储敏感、重要的用户信息。例如:用户的登录状态、权限信息、购物车内容等。
- 优点:安全性高,数据存储在服务端,客户端无法篡改。
- 缺点:占用服务器资源,需要维护 Session ID 的传递(通常依赖 Cookie)。H)**:你输入的命令和服务器返回的结果,都必须准确无误。
选择 UDP 的场景 (实时性/速度优先)
当对实时性要求极高,可以容忍少量数据丢失时,UDP 是更好的选择。
- 在线视频/直播 (Streaming):如果为了等一个丢失的数据包(比如画面中的一帧)而让整个视频卡顿,用户体验会非常糟糕。宁愿丢失一帧(画面出现瞬间花屏),也要保证流畅播放。
- 网络电话 (VoIP):和视频同理,实时通话的流畅性远比偶尔丢失一个声音采样点更重要。
- 在线游戏:玩家的位置、动作等信息需要被快速发送。如果一个数据包延迟到达,它包含的已经是过时的信息,不如直接丢弃,使用最新的数据包。
- DNS (域名系统):查询一个域名的 IP 地址,这个过程要求非常快。通常只是一次简单的请求和响应,如果查询失败(数据包丢失),客户端简单地重试一次即可,使用 UDP 开销最小。
前端面试常见题-网络和工程化篇
https://www.yuque.com/baiyueguang-rfnbu/tr4d0i/rz15krMeorinLime 梦灵
2025 - 08 - 11