前端面试真题 2025最新版
文章目录
- 写在前文
- CSS怪异盒模型
- JS闭包
- 闭包的形成
- 闭包注意点
- CSS选择器及优先级
- 优先级
- 说说flex布局及相关属性
- Flex 容器相关属性:
- Flex 项目相关属性
- 响应式布局如何实现
- 是否用过tailwindcss,有哪些好处
- 好处
- 缺点
- 说说对象的 prototype属性及原型
- 说说 promise
- Proxy 代理对象
- TS是怎么运行在浏览器的,tsc里面有哪些配置项
- 常见配置项
- 说说TS中 unknow和 any 区别
- any类型
- unknown 类型
- TS 泛型是什么
- 用过浏览器的哪些缓存
- 一、浏览器缓存(HTTP 缓存)
- 二、 Service Worker 缓存
- 三、LocalStorage / SessionStorage
- webpack和 vite有哪些区别
写在前文
这是一篇前端开发真实面试题,我会一直收集,持续更新。
CSS怪异盒模型
CSS 盒模型是指在网页布局时,元素的外部尺寸如何计算。
标准盒模型下,元素的 width 和 height 仅包括 内容区域,而不包括 内边距(padding)、边框(border)和 外边距(margin)。
怪异盒模型(border-box)是与标准盒模型相对的一种模型。在怪异盒模型下,元素的 width 和 height 包括了内容、内边距和边框,但不包括外边距。
标准盒模型:box-sizing: content-box,width 和 height 不包括 padding 和 border。
怪异盒模型:box-sizing: border-box,width 和 height 包括了 padding 和 border。
JS闭包
闭包(Closure)是指一个函数可以“记住”并访问其词法作用域中的变量,即使这个函数在词法作用域之外执行。
简而言之,闭包是一个 函数与其引用环境的组合。这个环境就是函数定义时所处的作用域,而不是调用时的作用域。
闭包的形成
- 当一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量时,这个内部函数就是闭包。
闭包注意点
- 内存问题:闭包会保持对外部作用域的引用,因此如果闭包长时间存在且包含大量数据,可能会导致内存泄漏。在使用闭包时,要特别注意不必要的内存占用。
- 性能问题:过多的闭包可能会影响性能,特别是在高频率的调用中。
CSS选择器及优先级
1.基本选择器:
- *(通配符,选择所有元素)
- element(元素选择器,选择指定元素)
- #id(ID选择器,选择特定ID的元素)
- .class(类选择器,选择特定类的元素)
2.组合选择器:
- element1, element2(组合选择器,选择多个元素)
- element > element(子元素选择器,选择直接子元素)
- element + element(相邻兄弟选择器,选择紧接在某元素后的同级元素)
- element ~ element(通用兄弟选择器,选择所有同级元素)
3.伪类选择器:
- :hover(当鼠标悬停在元素上时选择)
- :focus(元素获得焦点时选择)
- :nth-child(n)(选择父元素下第n个子元素)
4.伪元素选择器:
::before(在元素内容之前插入内容)
::after(在元素内容之后插入内容)
优先级
- 内联样式(style=“…”) > ID选择器 > 类选择器、伪类选择器 > 元素选择器、伪元素选择器。
- 合成选择器:如果有多个相同类型的选择器,优先级会相加。
- 重要性(!important):如果某条CSS规则声明了!important,无论其优先级如何,它的优先级都会高于其他规则。
说说flex布局及相关属性
Flexbox(弹性盒布局)是一种 CSS 布局模型,主要用于分配空间并对齐项目,特别适合于一维布局。它通过容器(称为“flex
容器”)和容器内的子项(称为“flex 项目”)来进行布局。
Flex 容器相关属性:
这些属性在父容器(即 Flex 容器)上设置,用于控制容器内元素的布局。
1.display: flex; 或 display: inline-flex
- flex:设置为 Flex 容器,所有直接子元素都成为 Flex 项目。
- inline-flex:设置为行内弹性容器,容器本身为 inline,但是其中的子项仍为弹性布局。
2.flex-direction: 控制项目的排列方向,决定主轴方向。
-
row:默认值,项目沿主轴水平排列(从左到右)。
-
row-reverse:项目沿主轴水平排列,但顺序反向(从右到左)。
-
column:项目沿副轴垂直排列(从上到下)。
-
column-reverse:项目沿副轴垂直排列,但顺序反向(从下到上)。
3.flex-wrap: 控制是否允许换行,如果项目超出容器的宽度或高度时,是否允许换行。
-
nowrap:默认值,所有项目都在一行内显示,不换行。
-
wrap:项目换行显示(从上到下或者从左到右)。
-
wrap-reverse:项目换行显示,换行的顺序与 wrap 相反。
4.justify-content: 控制主轴(横向或纵向)上的对齐方式。
-
flex-start:默认值,项目从主轴的起点开始排列。
-
flex-end:项目从主轴的终点开始排列。
-
center:项目在主轴上居中排列。
-
space-between:项目在主轴上均匀分布,第一个项目放在起点,最后一个项目放在终点,项目之间的空白平均分配。
-
space-around:项目在主轴上均匀分布,项目之间的空白相等,但两端的空白为项目之间空白的一半。
-
space-evenly:项目在主轴上均匀分布,项目之间的空白和两端的空白都相等。
5.align-items: 控制交叉轴(垂直于主轴方向)的对齐方式。
-
stretch:默认值,项目拉伸以填满容器(如果项目的高度未被指定)。
-
flex-start:项目在交叉轴的起点对齐。
-
flex-end:项目在交叉轴的终点对齐。
-
center:项目在交叉轴上居中对齐。
-
baseline:项目在基线对齐(基于文本行的基线)。
6.align-content: 控制多行项目的对齐方式,当存在多行时使用(与 align-items 不同,后者仅对单行项目起作用)。
-
stretch:默认值,行间距拉伸以填满容器。
-
flex-start:行集对齐到交叉轴的起点。
-
flex-end:行集对齐到交叉轴的终点。
-
center:行集居中对齐。
-
space-between:行间距均匀分布,第一行放在交叉轴的起点,最后一行放在终点,其他行平均分布。
-
space-around:行间距均匀分布,行之间的空白相等,行两端的空白为其他空白的一半。
Flex 项目相关属性
这些属性应用于 Flex 容器中的每个子项(即 Flex 项目)上,用于控制项目的排列和缩放。
1.flex-grow: 定义项目的放大比例,默认值为 0(即项目不放大)。如果所有项目的 flex-grow 都为 1,它们会平分可用空间。
- flex-grow: 1;:项目会放大以占据多余空间。
- flex-grow: 0;:项目不会放大。
2.flex-shrink: 定义项目的缩小比例,默认值为 1(即项目会缩小以适应容器)。如果容器空间不足,项目会按比例缩小。
flex-shrink: 1;:项目会缩小以适应容器。
flex-shrink: 0;:项目不会缩小。
3.flex-basis: 定义项目在主轴方向上的初始大小,默认值为 auto(即项目的本来大小)。
-
flex-basis: 100px;:项目的基础大小是 100px。
-
flex-basis: auto;:项目的基础大小是其内容的自然大小。
4.flex: 是 flex-grow、flex-shrink 和 flex-basis 的简写。
-
flex: none;:flex-grow: 0;,flex-shrink: 0;,flex-basis:
auto;(即不放大、不缩小,基础大小是内容大小)。 -
flex: 1;:flex-grow: 1;,flex-shrink: 1;,flex-basis:
0;(即项目可以放大,且占据可用空间)。
5.align-self: 控制单个项目在交叉轴上的对齐方式,覆盖 align-items。
-
auto:使用 align-items 的值。
-
flex-start:项目在交叉轴的起点对齐。
-
flex-end:项目在交叉轴的终点对齐。
-
center:项目在交叉轴上居中对齐。
-
baseline:项目在基线对齐。
-
stretch:项目拉伸以填满容器。
响应式布局如何实现
1.媒体查询(Media Queries)
媒体查询是实现响应式布局最常用的方法之一。 它根据不同的屏幕宽度、分辨率等条件应用不同的 CSS 样式。 通过 @media规则,开发者可以为不同设备条件定义不同的样式。
2.百分比布局
使用百分比单位来布局,可以使元素的宽度和高度相对于父容器自适应,从而实现响应式布局。
当容器宽度变化时,子元素会相应地调整自己的宽度,适应不同的设备和屏幕大小。
3.Flexbox 布局
Flexbox 是一种非常强大的布局方式,可以非常方便地实现响应式设计。使用 Flexbox,你可以轻松地控制子元素的大小、顺序和对齐方式,自动调整布局。
.container {display: flex;flex-wrap: wrap; /* 让元素换行 */
}.column {flex: 1; /* 每个子元素占据相等的空间 */min-width: 200px; /* 最小宽度,避免在小屏幕上太窄 */
}@media (max-width: 768px) {.column {flex: 0 0 100%; /* 在小屏幕上,每列占满一行 */}
}
4.CSS Grid 布局
CSS Grid 是一种更高级的布局技术,适用于更复杂的响应式布局。它可以让你定义网格(rows 和 columns),并控制内容如何在网格中排列,能够实现精确的响应式设计。
5.Viewport 单位
使用 vw(视口宽度)和 vh(视口高度)单位可以根据视口的尺寸动态调整元素的尺寸。例如,1vw 表示视口宽度的 1%,1vh 表示视口高度的 1%。
是否用过tailwindcss,有哪些好处
Tailwind CSS 是一种非常流行的 实用类(utility-first) CSS框架,旨在通过提供一组预定义的类,帮助开发者快速构建响应式、定制化的界面。与传统的 CSS 方法不同,Tailwind 通过直接在 HTML元素中使用小的类来设置样式,而不需要在外部定义复杂的 CSS 类。
好处
1.快速开发
实用类(Utility-first):Tailwind 提供了大量的小类,每个类实现一个特定的样式(如 text-center, bg-blue-500, p-4)。这些类可以组合起来快速构建布局和样式,而不需要写大量的自定义 CSS。
2. 高效的定制化
高度可定制:Tailwind 的配置文件 tailwind.config.js 允许你根据项目需求定制颜色、间距、字体、断点等。你可以轻松地扩展框架,创建符合设计规范的自定义类。
3.响应式设计的简便性
内置响应式类:Tailwind 提供了内置的响应式设计工具,使用非常简单。例如,使用 sm:, md:, lg: 等前缀来为不同的屏幕大小设置不同的样式。
4. 不冗余的 CSS
按需生成 CSS:Tailwind 使用工具,如 PurgeCSS,来删除未使用的 CSS 类,这样你最终打包的 CSS 文件会尽可能小。这个过程确保了即使你在 HTML 中使用了大量的类,也只会生成你实际使用的 CSS 样式,减少了冗余代码。
5. 提高可维护性
避免重复的样式:在传统 CSS 开发中,可能会出现多个类似的 CSS 类或者重复的样式规则,而 Tailwind 通过原子类的方式避免了这些重复。每个类仅做一件事,这样可以避免样式冲突和多余的代码。
6. 更高的可读性和可操作性
快速迭代:由于 Tailwind 让开发者直接在 HTML 中定义样式,开发者可以更直观地看到元素的外观和布局。你可以在编辑 HTML 的同时立即看到变化,这种方式有助于快速原型设计和快速迭代。
7. 社区支持和生态系统
庞大的社区:Tailwind 拥有一个活跃的社区,很多开发者共享资源、组件、工具和插件。你可以轻松找到现成的解决方案来快速实现复杂的布局和样式。
UI 组件库:像 Tailwind UI、DaisyUI 等组件库也为开发者提供了大量的预制 UI 组件,快速构建界面。
缺点
虽然 Tailwind 有很多优点,但它也有一些缺点:
- HTML 文件中类名过多:由于大量的样式类嵌套在 HTML 中,某些开发者可能会觉得代码过于冗长,尤其是在较复杂的页面中,类名的堆叠会让
HTML 变得不够简洁。 - 学习曲线:对于刚接触 Tailwind 的开发者,学习如何使用和组合这些类可能需要一些时间。特别是对于那些习惯了传统 CSS 或其他
CSS 框架的人来说,可能需要适应。 - 不适合小项目:如果是一个小型项目或者一个简单的静态页面,Tailwind 可能会显得有些过于庞大,可能不需要其全部功能。
- 无法直观看到样式:有些开发者认为直接在 HTML 中使用类并没有传统的 CSS 样式表那么直观,尤其是在大型项目中,很多样式都分散在不同的
HTML 元素中。
说说对象的 prototype属性及原型
在 JavaScript 中,每个对象都有一个内部属性 [[Prototype]],通常通过 proto 或 prototype 来访问。理解原型(prototype)是理解 JavaScript 面向对象编程的关键。
1. prototype 属性
定义: prototype 是一个对象,它是由构造函数创建的,且每个 JavaScript 函数(构造函数)都有一个 prototype属性。这个 prototype 对象会作为新对象的原型,赋给新对象的 [[Prototype]](即通过 __proto__可以访问到的对象)。 当你使用构造函数创建一个新对象时,新对象会继承该构造函数的 prototype 对象中的属性和方法。
2.原型(Prototype)
原型是 JavaScript 中实现继承的核心机制。每个对象都有一个原型(即 [[Prototype]],可以通过 __proto__来访问)。当你访问对象的某个属性或方法时,JavaScript 会首先查找该对象本身是否有该属性。如果没有,它会查找对象的原型(即[[Prototype]]),如果原型中也没有,就继续向原型链上查找,直到 null 为止。
原型链:
原型链是由一系列对象组成的链条,每个对象都有一个指向其原型的引用。原型链的终点是 null,它表示没有更多的原型了。
3.原型链的构造
JavaScript 中的每个对象都是由一个构造函数创建的。构造函数会通过 prototype 属性来为该对象的实例添加方法或属性。当你使用new 关键字时,会创建一个新对象,并将其 [[Prototype]] 设置为构造函数的 prototype 对象。
4.如何访问对象的原型
- proto:可以通过 proto 访问对象的原型链。注意,proto 是非标准的,但大多数现代浏览器都支持它。
- Object.getPrototypeOf():这是访问对象原型的标准方法,推荐使用。
说说 promise
Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个 异步操作的最终完成(或失败)及其结果值的表示。Promise
提供了更为优雅和可维护的方式来处理回调函数,避免了传统的回调地狱(callback hell)。
1. Promise 的状态
Promise 对象有三种状态:
- pending(待定):表示异步操作正在进行中,Promise 处于未完成的状态。
- fulfilled(已完成):表示异步操作已成功完成,Promise 已经解决(resolved),并且有一个结果值。
- rejected(已拒绝):表示异步操作失败,Promise 被拒绝(rejected),并且有一个错误原因。
状态转换:
pending → fulfilled(成功)
pending → rejected(失败)
一旦 Promise 从 pending 转换为 fulfilled 或 rejected,它的状态就不能再变化了。
2. 创建 Promise
Promise 对象通过构造函数创建,构造函数接受一个 executor(执行器) 函数作为参数。这个执行器函数有两个参数:resolve 和 reject,分别用于改变 Promise 的状态为 fulfilled 或 rejected。
let promise = new Promise((resolve, reject) => {let success = true;if (success) {resolve("操作成功");} else {reject("操作失败");}
});promise.then((value) => {console.log(value); // 如果 promise 状态为 fulfilled,输出:操作成功
}).catch((error) => {console.log(error); // 如果 promise 状态为 rejected,输出:操作失败
});// resolve(value):当异步操作成功时,调用 resolve,将状态从 pending 改为 fulfilled,并将结果值传递给 then() 方法。
// reject(error):当异步操作失败时,调用 reject,将状态从 pending 改为 rejected,并将错误原因传递给 catch() 方法。
3. Promise 的方法
Promise 提供了几个链式方法,用于处理异步操作的结果。
- then(onFulfilled, onRejected):该方法用于指定当 Promise 状态变为 fulfilled
时的回调函数(onFulfilled),以及当状态变为 rejected 时的回调函数(onRejected)。 - catch(onRejected):该方法是 .then(null, onRejected) 的别名,用于指定当 Promise被拒绝时的回调函数。
- finally(onFinally):无论 Promise 最终是成功还是失败,都会执行 finally
中的回调函数,通常用于执行清理操作(例如隐藏加载动画)。
Proxy 代理对象
Proxy 是 JavaScript 中用于创建一个对象的 代理,它可以通过拦截对象的基本操作(如访问属性、赋值、删除属性等)来增强或改变对象的行为。通过 Proxy,你可以控制对目标对象的访问、修改或者监控。
Proxy 是 ECMAScript 6(ES6)引入的,它是一个非常强大的工具,能够帮助你实现很多高级功能,比如数据验证、属性拦截、性能优化等。
1.Proxy 的特点
- 透明性:Proxy
使得目标对象的操作可以被拦截和自定义,但可以让外部调用看起来是透明的,也就是通过代理对象进行操作不会破坏目标对象的行为。 - 灵活性:Proxy 提供了非常多的拦截方法,几乎可以拦截对象的所有操作。你可以根据需要灵活地定制行为。
- 性能:虽然 Proxy 可以提供很多强大的功能,但它也会带来一定的性能开销,尤其是当你在大量操作上使用代理时。
2.创建一个 Proxy
Proxy 构造函数接受两个参数:
target:被代理的目标对象,它是你实际操作的对象。
handler:一个对象,定义了代理对象的行为(拦截操作)。
// 目标对象
let target = {message: "Hello, Proxy!"
};// handler 对象,定义代理的行为
let handler = {get: function(target, prop, receiver) {if (prop in target) {return target[prop];} else {return `Property "${prop}" does not exist on target`;}}
};// 创建 Proxy 对象
let proxy = new Proxy(target, handler);console.log(proxy.message); // 输出:Hello, Proxy!
console.log(proxy.nonExistentProperty); // 输出:Property "nonExistentProperty" does not exist on target
3.常见的拦截方法
Proxy 通过 handler 对象中的一系列方法来拦截对象的操作。每个方法代表了对不同操作的拦截。常用的拦截方法如下:
- get 用于拦截对目标对象属性的访问。
- set用于拦截对目标对象属性的赋值操作。
- deleteProperty用于拦截 delete 操作,即删除目标对象的属性。
- has用于拦截 in 操作,检查属性是否存在。
- ownKeys用于拦截 Object.keys()、Object.getOwnPropertyNames() 等方法,返回目标对象的所有键。
- apply(用于函数代理)用于拦截函数调用操作。当代理对象是一个函数时,apply 可以拦截函数调用。
- construct(用于构造函数代理)用于拦截构造函数调用。
TS是怎么运行在浏览器的,tsc里面有哪些配置项
TypeScript 代码本身不能直接在浏览器中运行。浏览器只能理解 JavaScript,而 TypeScript 是JavaScript 的超集,包含类型检查和其他一些功能。在浏览器中运行 TypeScript 代码的过程通常需要通过以下步骤:
1. TypeScript 编译为 JavaScript
使用 tsc(TypeScript Compiler)命令行工具或者构建工具(如 Webpack、Vite 等)将 TypeScript 代码编译成 JavaScript 代码。tsc 会读取你的 TypeScript 文件(.ts 或 .tsx)并生成相应的 JavaScript 文件(.js)。
2.在浏览器中运行 JavaScript
一旦 TypeScript 被编译为 JavaScript 文件,浏览器就能够直接执行这些 JavaScript 文件。你可以通过
3. 使用模块化工具
对于现代的前端开发,TypeScript 代码往往涉及模块化。你可以使用像 Webpack、Vite、Rollup 等构建工具,将多个 TypeScript 文件打包成浏览器可以理解的单个或多个 JavaScript 文件。
4.配置 tsconfig.json
为了控制 TypeScript 编译过程,通常会使用一个 tsconfig.json 配置文件来指定编译选项,确保 TypeScript 正确地编译成符合浏览器要求的 JavaScript 代码。
常见配置项
compilerOptions 是 tsconfig.json 中最重要的部分,它指定了 TypeScript 编译器的行为。常见的配置项包括:
1.target:指定编译后的 JavaScript 版本。可以设置为:
- ES3
- “ES5”
- “ES6” / “ES2015”
- “ES2016”, “ES2017”, “ES2018”, “ES2019”, “ES2020”, “ES2021”
- “ESNext”(最新的 ECMAScript 版本)
2.module:指定模块系统,默认是 “CommonJS”,可以设置为:
- “CommonJS”(用于 Node.js)
- “ESNext”(支持 ES6 模块)
- “AMD”, “System”, “UMD”, “ES6”, “ES2015”
3.moduleResolution:指定如何解析模块。可以设置为:
- “node”:基于 Node.js 的模块解析规则。
- “classic”:旧版的模块解析规则。
4.strict:启用所有严格的类型检查选项。这会提高代码的类型安全性。
5.esModuleInterop:启用 ECMAScript 模块与 CommonJS 模块的互操作性。允许使用 import 导入 CommonJS 模块。
6.outDir:指定输出目录。编译后的 JavaScript 文件将被输出到该目录。
7.sourceMap:生成源映射文件,用于调试,帮助浏览器调试时将编译后的代码映射回原始的 TypeScript 代码。
8.removeComments:在编译时移除代码中的注释。
9.lib:指定包含的库文件,如 “ES6”, “DOM”, “ESNext” 等。可以使用此选项来添加或移除 JavaScript 的内置类型定义。
include 和 exclude 配置项
1.include:指定要包含的文件或文件夹路径,可以使用通配符。默认情况下,TypeScript 会包括所有 .ts、.tsx 和 .d.ts 文件。
2.exclude:指定不包含的文件或文件夹路径。默认情况下,node_modules 会被排除。
说说TS中 unknow和 any 区别
在 TypeScript 中,unknown 和 any 都表示可以接受任何类型的值,但它们在使用上有一些重要的区别。理解它们的不同可以帮助我们更好地控制类型安全性,减少潜在的错误。
any类型
any 是 TypeScript 中最宽松的类型,它表示任意类型。变量一旦被声明为 any,就等于关闭了 TypeScript 的类型检查,所有的类型检查都被绕过。这意味着,赋予一个变量 any 类型后,TypeScript 不会检查该变量的类型,赋予该变量任何类型的值也不会报错。
特点:
- any 类型的变量可以被赋予任何类型的值。
- any 类型的变量可以调用任意方法,访问任意属性。
- any 类型不会进行类型检查,意味着你可以在 any 类型的变量上做任何操作。
缺点:
- 它取消了类型检查,可能会导致运行时错误。
- 大量使用 any 会失去 TypeScript 的类型检查优势,使得代码更容易出错。
unknown 类型
unknown 类型是 TypeScript 中引入的一个更安全的类型,它也是 “任何类型” 的意思,但与 any 不同的是,unknown 会要求开发者在使用该值时进行某种形式的类型检查或类型断言。
unknown 可以赋值为任何类型,但在使用之前,你必须确保你知道该值的具体类型。简单来说,unknown 是一种安全的 “未知类型”。
特点:
- unknown 类型的变量可以赋予任何类型的值。
- 在使用 unknown 类型的变量之前,必须做类型检查或类型断言。
- 与 any 不同,unknown 类型并不会绕过类型检查,它强制你明确指定或检查类型。
unknown 的优势: - unknown 提供了类型安全,强制要求开发者检查值的类型再使用它。
- 使用 unknown 可以减少运行时错误,因为 TypeScript 会进行检查,确保在不确定类型时不会进行不合法的操作。
TS 泛型是什么
在 TypeScript 中,泛型(Generics)是一种用于创建可复用组件或函数的工具,它允许你在定义时不指定具体的类型,而是在使用时再指定具体的类型。通过泛型,能够在保持类型安全的前提下,编写更加通用的代码。
简而言之,泛型使得你能够编写适用于多种类型的函数、类或接口,而不失去类型检查的能力。
为什么使用泛型
泛型可以帮助我们在代码中避免重复的类型声明,同时确保类型的安全性。它特别适用于处理不同数据类型的容器、函数、类等结构。
泛型的基本语法
在 TypeScript 中,使用尖括号(<>)来定义泛型类型,通常将类型参数表示为一个字母(如 T、U、K 等)。T 只是一个常见的命名习惯,实际上可以用任何名字。
一个最简单的泛型函数示例:
function identity<T>(arg: T): T {return arg;
}let result1 = identity(5); // 类型推导为 number
let result2 = identity("Hello"); // 类型推导为 string
- T 是泛型类型参数,可以接受任何类型。
- arg: T 表示传入的参数 arg 的类型是 T,返回值的类型也是 T。
用过浏览器的哪些缓存
在前端开发中,浏览器缓存是优化性能和减少请求延迟的重要手段。常见的浏览器缓存包括以下几种类型:
一、浏览器缓存(HTTP 缓存)
浏览器会自动缓存 HTTP 请求的响应数据,以减少对服务器的重复请求。常见的缓存机制包括:
1.强制缓存(Cache-Control)
强制缓存是浏览器在请求资源时,直接从本地缓存获取文件,而不发送请求到服务器。这通过 HTTP 头部的 Cache-Control、Expires 等字段来控制。
Cache-Control: 用来设置缓存策略。例如:
- Cache-Control: no-cache: 表示每次都需要重新验证缓存,缓存过期。
- Cache-Control: max-age=3600: 表示缓存可以存活 3600 秒。
- Cache-Control: public: 表示响应可以被任何缓存存储。
- Cache-Control: private: 表示响应只能被用户的浏览器缓存。
Expires: 这个字段设置一个具体的日期时间,当当前时间超过这个时间后,缓存就会失效。
- 例如:Expires: Thu, 01 Dec 2025 16:00:00 GMT
2. 协商缓存(ETag 和 Last-Modified)
当强制缓存失效时,浏览器会使用协商缓存机制来决定是否从缓存中读取数据。具体过程如下:
-
Last-Modified:服务器会在响应头中返回资源的最后修改时间。浏览器会将此时间与本地缓存的时间进行比较,如果相同,则返回缓存内容。如果不同,则重新请求服务器。
-
ETag:服务器可以通过 ETag响应头生成资源的唯一标识符,浏览器会在后续请求中带上该标识符,服务器会验证该标识符是否与当前资源一致。如果一致,返回 304状态码,表示资源没有修改,浏览器使用缓存。
3. 缓存控制字段的组合
在实际应用中,Cache-Control、Expires、ETag 和 Last-Modified 常常组合使用,提供更加细粒度的缓存控制。
二、 Service Worker 缓存
Service Worker 是一种浏览器端的 JavaScript 脚本,允许你控制和缓存页面的请求,并在离线时提供缓存内容。它通常用于实现离线功能,提供更强的缓存能力和更灵活的缓存策略。
- Cache API: Service Worker 使用 Cache API 来存储文件,通常会用来缓存 HTML、JS、CSS
和图片等资源。 - 缓存策略: 你可以自定义缓存策略,例如网络优先、缓存优先、或者缓存失败后从网络获取等。
三、LocalStorage / SessionStorage
LocalStorage 和 SessionStorage 是 HTML5 Web Storage 提供的客户端存储机制,用于存储小量数据。
- LocalStorage:存储的数据是持久化的,即使关闭浏览器窗口,数据依然存在,直到显式清除。
适用于存储用户设置、主题选择、认证信息等。 - SessionStorage:存储的数据只在浏览器窗口或标签页中有效,关闭浏览器窗口后,数据会被销毁。
适用于临时的数据存储,例如一次性表单数据、用户输入等。
webpack和 vite有哪些区别
Webpack 和 Vite
都是非常流行的前端构建工具,它们在功能上有很多相似之处,但在实现原理、开发体验和构建速度等方面存在一些显著的差异。下面是它们的主要区别:
1. 构建原理
Webpack
- 打包式构建:Webpack
采用传统的“打包”模式,在开发阶段和生产阶段都会将所有资源(JavaScript、CSS、图片等)打包成一个或多个文件,确保浏览器能够加载。 - 构建过程:
1.Webpack 在启动时会解析项目的依赖关系,遍历所有模块并打包。
2.通过 loader 和 plugin 执行各种转译和处理(例如 Babel 转译、CSS 处理、图片优化等)。
3.最终生成一组打包好的文件。 - 开发模式:Webpack 开发模式下会启动一个开发服务器(webpack-dev-server),并通过 Hot Module
Replacement (HMR) 来动态更新文件。
Vite
- 按需编译(Instant Server Start):Vite 使用了一种全新的构建方式,基于原生 ES
模块(ESM)。它并不是一次性将所有文件打包,而是在浏览器请求模块时实时地按需加载并编译。 - 构建过程:
1.Vite 会先启动一个开发服务器,并根据请求的模块动态编译(通过 esbuild)。
2.只编译被请求的文件,避免了不必要的打包,极大地提高了启动速度。
3.生产模式时,Vite 使用 Rollup 进行优化和打包,生成优化后的代码。
2. 开发速度
Webpack
- 构建速度较慢:Webpack
的传统打包方式需要对项目进行完整的依赖分析和打包,在大型项目中,打包和重新构建的速度相对较慢。即便是通过一些缓存和增量构建的优化,Webpack
仍然不如 Vite 快。 - 增量构建:Webpack 使用缓存和增量构建来加速开发,但仍然需要根据所有文件进行打包,导致大项目下的速度较慢。
Vite
- 极快的启动速度:Vite 使用基于原生 ESM 的开发模式,避免了打包过程,极大地提升了开发环境的启动速度。对于大多数项目,Vite
启动速度几乎是瞬时的。 - 按需编译:Vite 在开发时不需要整个项目都打包,而是通过 esbuild 对单个文件进行快速编译。只有浏览器需要哪个模块,Vite才会动态编译它,因此对于大项目,构建速度更快。
3. 编译和打包
Webpack
-
Babel/Loader 等插件:Webpack 需要通过 loader、plugin
等进行转译和处理,且配置灵活,可以定制许多细节。它支持的功能非常丰富,几乎可以实现任何需求。 -
打包优化:Webpack 有许多优化手段,比如 tree shaking、code splitting等,但需要更多的配置来实现最佳效果。
Vite
- esbuild:Vite 使用了 esbuild 来进行编译,esbuild 是一个用 Go 编写的构建工具,非常快速,比 Webpack
内置的 JavaScript 编译工具更高效。 - 生产模式:虽然 Vite 在开发时并不打包所有内容,但在生产模式下,它使用 Rollup 来进行优化和打包,Rollup是一个高度优化的构建工具,尤其擅长进行代码分割(code splitting)和 tree shaking。
4. 配置和插件
Webpack - 高度可配置:Webpack 提供了强大的配置能力,几乎可以控制所有的构建流程,插件和 loader
的生态也非常庞大,可以满足各种不同的需求。 - 配置复杂:但是,Webpack 的配置相对复杂,特别是在大型项目中,配置和调试可能会变得非常繁琐。尤其是对于刚入门的开发者,Webpack的配置可能有较高的学习成本。
Vite
- 更简单的配置:Vite 的默认配置就足够支持大多数项目,开箱即用的体验使得开发者能够快速上手。虽然也支持插件和自定义配置,但比起Webpack,Vite 的配置更加简洁和直观。
- 插件支持:Vite 的插件体系也很强大,尤其是与 Vue、React 等框架的集成非常流畅。同时,Vite 兼容了许多 Webpack插件,开发者可以根据需要进行扩展。
5. 热更新(HMR)
Webpack
- HMR(Hot Module Replacement):Webpack 支持
HMR,能够在不刷新页面的情况下替换修改的模块,这对于开发时快速查看效果非常有用。它会通过 WebSocket与浏览器进行通信,替换模块时保持应用的状态。 - 更新速度:虽然 Webpack 的 HMR 支持非常完善,但由于其打包过程较为复杂,HMR 的速度相对较慢。
Vite
- HMR 极快:Vite 的 HMR 基于原生的 ES 模块进行,更新速度非常快。由于 Vite
不需要整个文件重新打包,而是针对变动模块进行实时替换,因此 HMR 速度非常高,刷新页面的延迟极低。
6. 生产构建(Build)
Webpack - 生产构建复杂:Webpack 的生产构建过程可能比较复杂,需要处理多种资源,且配置时需要手动启用各类优化(如 treeshaking、minification、code splitting 等)。如果配置不当,可能会影响打包效果。
- 生成多个文件:Webpack 通常会生成多个文件(包括 JS、CSS、图片等),并通过 webpack.optimize
等手段来优化输出的文件大小。
Vite
- 简化的生产构建:Vite 在开发时不打包所有内容,但在生产时会通过 Rollup 进行优化,并自动开启 tree
shaking、代码分割等优化。大部分的构建优化都是开箱即用的,用户无需做过多配置。 - 性能优化:Vite 使用 Rollup 进行打包,Rollup 在构建性能、tree shaking 和代码分割方面表现非常优秀。