1、Pinia 和vuex对比
Pinia 和 Vuex 都是 Vue 生态中用于状态管理的库,Pinia 是 Vuex 的继任者(由 Vue 核心团队成员开发),在 Vue3 中被官方推荐。两者在设计理念和使用方式上有显著差异,以下从核心特性、语法、功能等方面对比分析:
一、核心差异概览
| 对比维度 | Vuex(以 Vuex 4 为例,适配 Vue3) | Pinia |
|---|---|---|
| 发布时间 | 2015 年(较早,基于 Options API 设计) | 2021 年(较新,专为 Composition API 设计) |
| 核心概念 | State、Mutation、Action、Getter、Module | State、Action、Getter(无 Mutation、Module) |
| TypeScript 支持 | 需手动定义类型,复杂且繁琐 | 原生支持,类型推导自动完成,更友好 |
| 语法风格 | 基于对象配置(Options 式) | 基于函数(Composition 式),更简洁 |
| 模块化 | 需通过 modules 嵌套,存在命名空间问题 | 每个 Store 本身就是模块,天然支持模块化 |
| 数据修改 | 必须通过 Mutation(同步)+ Action(异步) | 直接在 Action 中修改 State(同步 / 异步均可) |
| DevTools 支持 | 支持,但对 Vue3 兼容性一般 | 完美支持 Vue3,包含时间线、状态回溯等 |
| 官方推荐 | Vue2 推荐,Vue3 中被 Pinia 替代 | Vue3 官方推荐,是 Vuex 的继任者 |
2、ts的enum和array
适用场景
-
enum适用场景:- 表示固定的状态(如
Pending/Active)、类型(如Admin/User)、选项(如下拉菜单的可选值)。 - 替代魔法值(如用
Direction.Up替代"UP"或1),避免硬编码导致的错误。
- 表示固定的状态(如
-
array适用场景:- 存储多个同类型数据(如用户列表、商品列表)。
- 需要遍历、筛选、排序的动态数据集合(如从接口获取的列表数据)。
三、总结:关键区别
| 差异点 | enum(枚举) | array(数组) |
|---|---|---|
| 本质 | 自定义类型(约束取值范围) | 数据结构(存储多个元素) |
| 形态 | 编译后是对象(映射名称与值) | 编译后是数组(存储元素) |
| 作用 | 限制取值,增强代码可读性 | 存储和管理多个数据,支持动态操作 |
| 典型用法 | 状态码、类型标识、固定选项 | 列表数据、集合数据、批量处理的数据 |
简单说:enum 是 “规则”(规定能取哪些值),array 是 “容器”(装符合规则的值)。实际开发中,两者常结合使用(如 “枚举值数组”,用数组存储多个枚举成员)。
3、vue3中使用ts定义组件prop、接口、类型守卫
在 Vue3 中结合 TypeScript 使用时,定义组件 prop、接口(interface)和类型守卫(Type Guard)是提升代码类型安全性的核心操作。以下是具体实现方法和最佳实践:
一、定义组件 Prop(使用 TypeScript)
Vue3 中通过 defineProps 配合 TypeScript 类型注解或接口,实现对 prop 的类型约束。推荐使用 <script setup lang="ts"> 语法糖,简化类型定义。
1. 基础类型 Prop 定义
直接在 defineProps 中通过类型注解声明 prop 类型:
vue
<template>
<div>
<p>姓名:{{ name }}</p>
<p>年龄:{{ age }}</p>
<p>是否激活:{{ isActive }}</p>
</div>
</template>
<script setup lang="ts">
// 基础类型 Prop 定义
const props = defineProps<{
name: string; // 必选字符串
age?: number; // 可选数字(? 表示可选)
isActive: boolean; // 必选布尔值
}>();
// 使用 prop(自动提示类型)
console.log(props.name.length); // 正确:name 是 string 类型
</script>
2. 用接口(Interface)定义复杂 Prop
当 prop 结构复杂(如对象、数组)时,推荐用 interface 抽离类型,增强复用性:
vue
<template>
<div>
<p>用户:{{ user.name }}({{ user.age }}岁)</p>
<p>标签:{{ tags.join(',') }}</p>
</div>
</template>
<script setup lang="ts">
// 定义接口(描述复杂类型结构)
interface User {
name: string;
age: number;
email?: string; // 可选属性
}
// 用接口定义 Prop
const props = defineProps<{
user: User; // 对象类型(基于接口)
tags: string[]; // 字符串数组
status: 'active' | 'inactive' | 'pending'; // 联合类型(枚举的简化形式)
}>();
// 使用复杂类型 Prop
console.log(props.user.name); // 正确:user 符合 User 接口
console.log(props.tags[0].toUpperCase()); // 正确:tags 是 string[]
</script>
3. 带默认值的 Prop 定义
通过 withDefaults 工具函数为 prop 设置默认值,同时保留类型约束:
vue
<script setup lang="ts">
interface User {
name: string;
age?: number;
}
// 定义 Prop 并设置默认值
const props = withDefaults(
defineProps<{
user: User;
pageSize: number;
keywords?: string;
}>(),
{
// 默认值需符合类型(pageSize 是 number)
pageSize: 10,
// 可选属性的默认值(keywords 是 string | undefined)
keywords: '',
// 复杂对象的默认值(需返回新对象,避免引用共享)
user: () => ({ name: 'Guest' }),
}
);
// 使用带默认值的 Prop
console.log(props.pageSize); // 10(默认值)
console.log(props.user.name); // 'Guest'(默认值)
</script>
二、接口(Interface)的高级用法
interface 是 TypeScript 中描述对象结构的核心工具,在 Vue 组件中常用于:
- 定义
prop类型 - 定义 API 接口返回数据类型
- 定义组件内部状态类型
1. 接口继承(扩展类型)
通过 extends 实现接口复用,避免重复定义:
typescript
// 基础接口
interface BaseEntity {
id: string;
createdAt: string; // 时间戳字符串
}
// 继承 BaseEntity,扩展用户特有属性
interface User extends BaseEntity {
name: string;
role: 'admin' | 'user';
}
// 继承 BaseEntity,扩展商品特有属性
interface Product extends BaseEntity {
title: string;
price: number;
}
// 使用:User 和 Product 都包含 id 和 createdAt
const user: User = {
id: 'u123',
createdAt: '2023-01-01',
name: 'Alice',
role: 'admin'
};
2. 接口与联合类型结合
描述 “多种可能结构” 的对象(如 API 返回的不同类型数据):
typescript
// 成功响应接口
interface SuccessResponse<T> {
code: 200;
data: T; // 泛型:支持任意数据类型
message: 'success';
}
// 错误响应接口
interface ErrorResponse {
code: 400 | 500; // 可能的错误码
data: null;
message: string; // 错误信息
}
// 联合类型:API 响应可能是成功或错误
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
// 使用:根据 code 判断响应类型
function handleResponse(response: ApiResponse<User>) {
if (response.code === 200) {
// TypeScript 自动推断为 SuccessResponse<User>
console.log(response.data.name);
} else {
// 自动推断为 ErrorResponse
console.error(response.message);
}
}
三、类型守卫(Type Guard)
类型守卫用于在运行时判断变量的具体类型,帮助 TypeScript 缩小类型范围(类型收窄),常用于处理联合类型。
1. 基础类型守卫(typeof)
判断基本类型(string/number/boolean 等):
typescript
// 联合类型变量
let value: string | number | boolean;
// 类型守卫函数:判断是否为 string
function isString(val: unknown): val is string {
return typeof val === 'string';
}
// 使用类型守卫
if (isString(value)) {
// TypeScript 推断 value 为 string
console.log(value.length);
} else if (typeof value === 'number') {
// 直接用 typeof 作为类型守卫
console.log(value.toFixed(2));
}
2. 对象类型守卫(instanceof 或自定义属性判断)
判断对象类型(接口 / 类实例):
typescript
interface User {
type: 'user';
name: string;
}
interface Admin {
type: 'admin';
role: string;
}
// 联合类型:User 或 Admin
type Person = User | Admin;
// 自定义类型守卫:判断是否为 User
function isUser(person: Person): person is User {
return person.type === 'user';
}
// 自定义类型守卫:判断是否为 Admin
function isAdmin(person: Person): person is Admin {
return person.type === 'admin';
}
// 使用类型守卫
function handlePerson(person: Person) {
if (isUser(person)) {
// 推断为 User
console.log(person.name);
} else if (isAdmin(person)) {
// 推断为 Admin
console.log(person.role);
}
}
3. 在 Vue 组件中使用类型守卫
处理接口返回的动态数据(如区分不同类型的列表项):
vue
<template>
<div v-for="item in items" :key="item.id">
<template v-if="isUserItem(item)">
用户名:{{ item.name }}
</template>
<template v-else-if="isProductItem(item)">
商品名:{{ item.title }},价格:{{ item.price }}
</template>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// 定义两种列表项接口
interface UserItem {
id: string;
type: 'user';
name: string;
}
interface ProductItem {
id: string;
type: 'product';
title: string;
price: number;
}
// 联合类型列表
type ListItem = UserItem | ProductItem;
// 类型守卫函数
const isUserItem = (item: ListItem): item is UserItem => {
return item.type === 'user';
};
const isProductItem = (item: ListItem): item is ProductItem => {
return item.type === 'product';
};
// 模拟列表数据
const items = ref<ListItem[]>([
{ id: '1', type: 'user', name: 'Alice' },
{ id: '2', type: 'product', title: '手机', price: 3999 }
]);
</script>
四、总结
-
组件 Prop:
- 用
defineProps<{...}>定义基础类型,复杂类型结合interface。 - 带默认值用
withDefaults,确保默认值符合类型约束。
- 用
-
接口(Interface) :
- 用于描述对象结构,支持继承和泛型,提升类型复用性。
- 结合联合类型可描述 “多种可能的结构”。
-
类型守卫:
- 通过自定义函数(返回
val is Type)在运行时判断类型,帮助 TypeScript 做类型收窄。 - 常用于处理联合类型,避免
any类型,提升代码安全性。
- 通过自定义函数(返回
这些技巧能让 Vue3 + TypeScript 项目的类型约束更严谨,减少运行时错误,同时提升开发体验(自动提示、类型校验)。
3、tsconfig配置
tsconfig.json 是 TypeScript 项目的核心配置文件,用于指定编译选项、文件包含 / 排除规则、类型检查严格程度等。合理配置 tsconfig.json 能提升类型检查效率和代码质量。以下是常用配置项的详解及不同场景的示例配置:
一、核心配置项解析
1. 编译目标与模块(必选基础配置)
target:指定编译后的 JavaScript 版本(如ES5、ES6、ESNext),默认ES3。示例:"target": "ES6"(编译为 ES6 语法)。module:指定模块系统(如CommonJS、ES6、ESNext),默认根据target自动选择。前端项目常用ESNext或ES6;Node.js 项目常用CommonJS。
2. 模块解析与路径(路径别名配置)
-
moduleResolution:指定模块解析策略(node或classic),默认node(推荐,与 Node.js 解析规则一致)。 -
baseUrl:设置基础路径,用于解析非相对路径模块(配合paths使用)。 -
paths:配置路径别名(如将@/*映射到src/*),需配合baseUrl使用。示例:json
{ "baseUrl": ".", "paths": { "@/*": ["src/*"] // 别名 @ 指向 src 目录 } }
3. 类型检查严格程度(核心)
-
strict:开启所有严格类型检查选项(推荐true),等价于同时开启以下选项:noImplicitAny:禁止隐式any类型(变量未指定类型时不自动推断为any)。strictNullChecks:严格检查null/undefined(必须显式处理可能为null的值)。strictFunctionTypes:严格检查函数参数类型兼容性。strictBindCallApply:严格检查bind/call/apply的参数类型。strictPropertyInitialization:类的属性必须在构造函数中初始化或设为可选。noImplicitThis:禁止隐式this为any类型。
建议新手直接开启
strict: true,逐步适应严格模式。
4. 文件包含与排除
include:指定需要编译的文件 / 目录(支持通配符*/**),默认["**/*"]。示例:"include": ["src/**/*", "types/**/*"](包含 src 和 types 目录下的所有文件)。exclude:指定需要排除的文件 / 目录(优先级高于include),默认排除node_modules、outDir等。示例:"exclude": ["node_modules", "dist", "**/*.test.ts"](排除测试文件)。files:显式指定需要编译的单个文件(适用于少量文件的项目),与include互斥。
5. 输出与源映射
outDir:指定编译后文件的输出目录(如dist),默认输出到源文件同级目录。rootDir:指定源文件的根目录(outDir会保持rootDir的目录结构),默认自动推断。sourceMap:是否生成源映射文件(.map),便于调试(开发环境建议true,生产环境可选false)。
6. 其他常用选项
allowJs:是否允许编译 JavaScript 文件(true可混合 TS/JS 项目)。esModuleInterop:允许 ES 模块与 CommonJS 模块互操作(如import * as React可简写为import React),建议true。skipLibCheck:跳过对声明文件(.d.ts)的类型检查,提升编译速度(建议true)。forceConsistentCasingInFileNames:强制文件名大小写一致(避免跨系统问题),建议true。
二、不同场景的配置示例
1. 前端 Vue3/React 项目(推荐)
json
{
"compilerOptions": {
"target": "ESNext", // 编译为最新 ES 语法
"module": "ESNext", // 模块系统用 ESNext
"moduleResolution": "Node", // 按 Node 规则解析模块
"strict": true, // 开启严格模式
"jsx": "preserve", // React 项目用 "react-jsx",Vue 用 "preserve"
"sourceMap": true, // 生成源映射
"resolveJsonModule": true, // 允许导入 JSON 文件
"isolatedModules": true, // 确保每个文件可单独编译(配合 Vite 等工具)
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".", // 基础路径
"paths": {
"@/*": ["src/*"] // 路径别名 @ -> src
},
"lib": ["ESNext", "DOM"] // 引入 ES 标准库和 DOM 类型
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
}
2. Node.js 后端项目
json
{
"compilerOptions": {
"target": "ES2020", // 适配 Node.js 14+
"module": "CommonJS", // Node.js 用 CommonJS 模块
"moduleResolution": "Node",
"strict": true,
"sourceMap": true,
"outDir": "dist", // 输出到 dist 目录
"rootDir": "src", // 源文件根目录为 src
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ES2020"] // 无需 DOM 库
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
3. 宽松模式(适合 TS 入门)
如果严格模式初期难以适应,可降低严格程度:
json
{
"compilerOptions": {
"target": "ES6",
"module": "ES6",
"strict": false, // 关闭全量严格模式
"noImplicitAny": false, // 允许隐式 any
"strictNullChecks": false, // 不严格检查 null/undefined
"esModuleInterop": true
}
}
三、配置生效与调试
- 生效方式:
tsconfig.json需放在项目根目录,TypeScript 会自动识别。 - 验证配置:运行
tsc --showConfig可查看最终生效的配置(合并了默认值)。 - 调试编译:运行
tsc --watch监听文件变化并实时编译,查看是否有类型错误。
总结
tsconfig.json 的核心是平衡 “类型严格性” 和 “开发效率”:
- 新项目建议开启
strict: true,配合路径别名提升开发体验。 - 前端项目重点配置
target、module、jsx、paths。 - 后端项目重点配置
outDir、rootDir、module: CommonJS。
可根据项目规模和团队习惯逐步调整配置,核心目标是通过类型检查减少 bug,同时不降低开发效率。
4、原生小程序、uniapp、taro、flutter区别
Flutter 是 Google 推出的跨平台 UI 框架,与原生小程序、UniApp、Taro 相比,它的定位和特性有显著差异,尤其在跨端范围、渲染方式和适用场景上区别较大。以下是 Flutter 与前三者的核心对比:
Flutter 的核心特性
- 技术栈基于 Dart 语言,采用自绘 UI 引擎(而非依赖平台原生组件),通过一套代码直接渲染出跨平台一致的界面。语法风格接近 Java/JavaScript,需单独学习 Dart 语言和 Flutter 的 Widget 组件体系。
- 跨端能力支持 iOS、Android、Web、桌面端(Windows/macOS/Linux) ,也可通过第三方插件(如
flutter_wechat)间接支持小程序(但并非原生适配,体验较弱)。核心优势在 App 端,Web 和小程序支持相对次要。 - 性能自绘引擎跳过平台原生组件,直接操作 GPU 渲染,性能接近原生 App(iOS/Android),流畅度优于依赖 WebView 或平台组件的框架(如 UniApp 的 App 端)。但在小程序、Web 端性能略逊于原生或专门针对这些平台优化的框架(如 Taro)。
- UI 一致性跨平台 UI 表现高度统一,无需为不同平台单独适配样式(这是与 UniApp/Taro 最大的区别之一,后两者依赖平台原生组件,样式可能因平台而异)。
- 生态官方生态完善(Material Design/Cupertino 组件库),第三方插件丰富(但小程序相关插件较少),适合复杂 App 开发(如动画、交互密集型应用)。
与原生小程序、UniApp、Taro 的核心区别
| 维度 | 原生小程序 | UniApp | Taro | Flutter |
|---|---|---|---|---|
| 核心目标平台 | 单一小程序平台 | 多端(小程序 / H5/App) | 多端(小程序 / H5 为主) | App 端(iOS/Android)为主 |
| 渲染方式 | 平台原生渲染 | 依赖平台原生组件 | 编译为平台原生代码 | 自绘引擎(跨平台一致) |
| 技术栈 | 平台专属语法 | Vue | React/Vue/TS | Dart + Widget 体系 |
| 小程序支持 | 原生完美支持 | 编译适配,体验接近原生 | 编译适配,灵活度高 | 需第三方插件,体验一般 |
| UI 一致性 | 仅单平台一致 | 依赖平台组件,风格有差异 | 依赖平台组件,风格有差异 | 跨平台高度一致 |
| 学习成本 | 高(多平台重复学习) | 低(Vue 开发者友好) | 中(React/Vue 基础上适配) | 中高(需学 Dart 和 Widget) |
| 典型适用场景 | 单一平台高频小程序 | 多端轻应用(电商、资讯) | 多端复杂小程序 / H5 | 高性能 App(社交、工具、游戏) |
Flutter 的适用场景
- 核心需求是 App 端(iOS/Android),且需要跨平台统一 UI 和高性能(如社交、短视频、工具类 App)。
- 对 UI 动画、交互体验要求极高(自绘引擎优势明显)。
- 可接受小程序 / Web 端作为次要适配目标(需额外开发或依赖插件)。
总结:如何选择?
- 若主要开发 小程序,优先选原生(单平台)、UniApp/Taro(多平台)。
- 若主要开发 App 且需跨 iOS/Android,且追求 UI 一致和高性能,选 Flutter。
- 若需 小程序 + H5 + App 全端覆盖,且 App 端可接受中等性能,选 UniApp。
- 若团队技术栈是 React 且侧重小程序 / H5,选 Taro。
简单说:Flutter 是 App 跨端的最优解之一,但小程序并非其强项;而原生小程序、UniApp、Taro 是 小程序开发的专属 / 适配方案。
6、uniapp的rpx怎么计算,需要设置根元素吗
在 UniApp 中,rpx 是一种响应式像素单位,专门用于解决多端设备屏幕适配问题,其计算逻辑和使用方式与微信小程序的 rpx 一致,无需手动设置根元素(如 HTML 的 font-size),框架会自动处理适配。
1. rpx 的核心计算逻辑
rpx 的设计理念是:将所有设备的屏幕宽度统一视为 750rpx,无论设备实际宽度是多少,都会按比例进行转换。
-
计算公式:实际像素值(px) =
rpx 值×(设备实际宽度 ÷ 750)例如:
- 在宽度为 375px 的设备(如 iPhone SE)上,1rpx = 375px ÷ 750 = 0.5px → 100rpx = 50px。
- 在宽度为 750px 的设备(如 iPhone 6/7/8)上,1rpx = 1px → 100rpx = 100px。
- 在宽度为 1080px 的 Android 设备上,1rpx ≈ 1080 ÷ 750 = 1.44px → 100rpx ≈ 144px。
2. 为什么不需要设置根元素?
与 rem 单位(依赖根元素 font-size 计算)不同,rpx 是 UniApp 框架内置的适配单位,其转换规则由框架根据当前设备的屏幕宽度自动计算,无需开发者手动设置根元素样式(如 <html style="font-size: xxx">)。
框架会在编译或运行时,根据目标平台(小程序、H5、App)的屏幕宽度,自动将 rpx 转换为对应平台的原生单位(如小程序的 px、H5 的 px 等),开发者只需专注于使用 rpx 编写样式即可。
3. 使用建议
- 设计稿适配:如果设计稿宽度为 750px(主流设计标准),则设计稿上的 1px 可直接对应代码中的 1rpx,无需换算(例如设计稿上一个按钮宽度为 200px,代码中直接写
width: 200rpx)。 - 特殊场景:若设计稿宽度不是 750px(如 375px),则需按比例转换(例如设计稿 100px → 代码中 200rpx,因为 375px 设计稿对应 750rpx,比例为 2:1)。
- 注意限制:
rpx主要用于布局尺寸(宽、高、边距等),字体大小建议结合px或动态计算(部分场景下过大的rpx可能导致字体模糊)。
总结
rpx以「屏幕宽度 = 750rpx」为基准自动换算,无需手动设置根元素。- 设计稿为 750px 时,1px 对应 1rpx,直接使用即可实现多端适配。
- 相比
px更灵活,相比rem更省心,是 UniApp 中推荐的主流布局单位。
7、小程序跨度开发核心问题,包括环境差异、代码兼容性
小程序跨端开发(如同时适配微信、支付宝、抖音、百度等平台)的核心挑战集中在环境差异和代码兼容性上,这些问题源于不同平台的底层架构、API 规范、组件设计甚至审核规则的差异。以下是具体分析及解决方案:
一、核心问题:环境差异
不同小程序平台的底层环境存在本质区别,主要体现在以下方面:
1. API 规范差异
-
命名与参数不同:同一功能的 API 在不同平台可能有不同的命名或参数格式。例:
- 微信小程序获取用户信息:
wx.getUserInfo() - 支付宝小程序获取用户信息:
my.getAuthUserInfo() - 抖音小程序获取用户信息:
tt.getUserInfo()
- 微信小程序获取用户信息:
-
功能支持度不同:部分平台可能缺失特定 API(如微信的「订阅消息」、支付宝的「生活号联动」),或 API 功能范围不同(如支付、地图服务的平台限制)。
2. 组件与样式差异
-
内置组件差异:平台提供的基础组件(如
button、picker、navigator)的默认样式、属性、事件可能不同。例:- 微信小程序的
button支持open-type="getUserInfo",支付宝小程序需用onGetAuthorize事件。 - 抖音小程序的
scroll-view滚动事件名是scroll,百度小程序是scrolltoupper。
- 微信小程序的
-
样式渲染差异:虽然都支持
rpx和flex,但部分 CSS 属性(如position: fixed、z-index)的表现可能不一致,甚至部分平台不支持某些 CSS 选择器(如::before)。
3. 路由与配置差异
- 路由配置文件:微信 / 抖音用
app.json配置页面,支付宝用app.json但字段略有不同(如窗口配置windowvsdefaultTitle)。 - 页面跳转 API:微信用
wx.navigateTo(),支付宝用my.navigateTo(),且跳转参数的解析方式可能有细微差异。
4. 运行时环境差异
- JavaScript 引擎:不同平台基于不同的 JS 引擎(如微信用 V8,支付宝可能用自研引擎),对 ES6+ 语法的支持度不同(如部分平台不支持
Promise.finally、动态import)。 - 缓存机制:本地存储(
localStorage/setStorage)的容量限制、过期策略不同(如微信单个 key 最大 1MB,支付宝可能不同)。
二、核心问题:代码兼容性
即使逻辑一致,代码在多平台运行时也可能因上述环境差异导致兼容性问题:
1. API 调用失败
直接使用平台专属 API 会导致其他平台报错(如在支付宝小程序中调用 wx.xxx 会提示 wx is not defined)。
2. 组件渲染异常
使用平台特有组件属性(如微信的 button.open-type)在其他平台可能被忽略或报错,导致 UI 错乱。
3. 事件处理不一致
同一交互事件(如表单提交、滚动)的触发时机、参数格式不同,导致逻辑执行异常。
4. 第三方依赖不兼容
部分第三方库(如 UI 组件库、工具类)可能只适配单一平台,直接引入会导致跨端运行失败。
5. 审核规则差异
即使代码能运行,各平台的审核标准不同(如微信对支付流程的严格限制、支付宝对隐私权限的特殊要求),可能导致功能通过 A 平台审核但在 B 平台被拒。
三、解决方案:跨端适配策略
针对上述问题,可通过以下方式实现高效跨端开发:
1. 使用跨端框架统一语法
通过 UniApp、Taro 等框架屏蔽底层差异,用统一的 API 和组件语法开发,框架自动编译为各平台原生代码:
- 统一 API:框架封装了
uni.xxx(UniApp)或Taro.xxx(Taro),自动映射到各平台 API(如uni.request()对应wx.request/my.request)。 - 统一组件:框架提供的组件(如
<view>、<button>)会被编译为各平台的原生组件,自动适配属性和事件。 - 优势:减少手动适配成本,适合多平台快速迭代。
2. 条件编译隔离平台差异
当需要针对特定平台编写差异化代码时,使用框架的条件编译功能(本质是在编译时保留目标平台代码,删除其他平台代码):
-
UniApp 条件编译:
vue
<template> <!-- #ifdef MP-WEIXIN --> <button open-type="getUserInfo">微信授权</button> <!-- #endif --> <!-- #ifdef MP-ALIPAY --> <button @getAuthorize="onAuth">支付宝授权</button> <!-- #endif --> </template> <script> export default { methods: { // #ifdef MP-WEIXIN handleWechat() { /* 微信逻辑 */ }, // #endif // #ifdef MP-ALIPAY handleAlipay() { /* 支付宝逻辑 */ }, // #endif } } </script> -
Taro 条件编译:
jsx
{process.env.TARO_ENV === 'weapp' && <button open-type="getUserInfo" />} {process.env.TARO_ENV === 'alipay' && <button onGetAuthorize={this.onAuth} />}
3. 封装适配层统一接口
若不使用跨端框架,可手动封装适配层,将平台差异隔离在底层:
javascript
运行
// api/adapter.js
const platform = wx ? 'weapp' : my ? 'alipay' : 'tt'; // 判断平台
export const request = (options) => {
if (platform === 'weapp') {
return wx.request(options);
} else if (platform === 'alipay') {
return my.request(options);
}
// ...其他平台
};
export const getUserInfo = (callback) => {
if (platform === 'weapp') {
wx.getUserInfo({ success: callback });
} else if (platform === 'alipay') {
my.getAuthUserInfo({ success: callback });
}
};
业务代码中直接调用封装后的 request 或 getUserInfo,无需关心平台差异。
4. 统一样式与组件库
- 使用跨端 UI 库:选择支持多平台的 UI 组件库(如 UniApp 的
uni-ui、Taro 的taro-ui),避免重复开发适配样式。 - 避免平台特有样式:尽量使用标准 CSS 属性(如
flex布局),减少对::before、position: fixed等可能存在兼容性的属性的依赖。 - 全局样式重置:通过
app.wxss/app.acss统一设置基础样式(如margin: 0、padding: 0),减少默认样式差异。
5. 多平台测试与灰度发布
- 分平台调试:使用各平台官方开发者工具单独调试(如微信开发者工具、支付宝小程序开发者工具),重点测试 API 调用和组件渲染。
- 自动化测试:接入跨端测试工具(如微信的「云测试」、支付宝的「真机测试」),批量检测多机型兼容性。
- 灰度发布:先在一个平台发布验证功能,再逐步推广到其他平台,降低全量发布风险。
8、flexbox和grid布局
Flexbox(弹性盒布局)和 Grid(网格布局)是 CSS 中两种主流的布局模式,分别适用于不同的场景,各有其核心优势。以下是两者的详细对比和使用场景分析:
一、Flexbox(弹性盒布局)
Flexbox 是一种一维布局模型,专注于处理单行或单列的布局,通过弹性容器和弹性项之间的关系实现灵活的对齐、分布和重排。
核心特点
- 一维布局:只能沿主轴(默认水平) 或交叉轴(默认垂直) 单方向排列,适合处理线性布局(如导航栏、列表、卡片排列)。
- 灵活分配空间:自动分配容器剩余空间,支持弹性项的拉伸、收缩和对齐调整。
- 内容驱动:布局会根据内容的大小动态调整,适合内容不确定的场景(如自适应文本长度的按钮组)。
核心属性
| 容器属性(父元素) | 作用 |
|---|---|
display: flex | 定义弹性容器 |
flex-direction | 设置主轴方向(row/column 等) |
justify-content | 主轴上的对齐方式(居中、两端对齐等) |
align-items | 交叉轴上的对齐方式(居中、拉伸等) |
flex-wrap | 弹性项是否换行(nowrap/wrap) |
| 项目属性(子元素) | 作用 |
|---|---|
flex-grow | 剩余空间分配比例(默认 0,不拉伸) |
flex-shrink | 空间不足时的收缩比例(默认 1,可收缩) |
flex-basis | 项目默认尺寸(默认 auto) |
align-self | 单个项目的交叉轴对齐方式(覆盖容器设置) |
示例:水平居中的导航栏
css
.nav {
display: flex;
justify-content: center; /* 主轴(水平)居中 */
align-items: center; /* 交叉轴(垂直)居中 */
gap: 20px; /* 项目间距 */
padding: 10px;
background: #f5f5f5;
}
.nav-item {
padding: 8px 16px;
background: #fff;
border-radius: 4px;
}
二、Grid(网格布局)
Grid 是一种二维布局模型,专注于处理行和列组成的网格结构,适合整体页面布局或复杂的二维排列(如仪表盘、画廊、卡片网格)。
核心特点
- 二维布局:同时控制行和列,可以精确定义网格的行数、列宽、间距等,实现复杂的二维分布。
- 布局驱动:先定义网格结构,再将元素放入指定位置,适合布局结构固定的场景(如三栏布局、响应式卡片网格)。
- 精确控制:支持网格线定位、跨行列合并、自适应网格等高级功能,灵活性远超 Flexbox。
核心属性
| 容器属性(父元素) | 作用 |
|---|---|
display: grid | 定义网格容器 |
grid-template-columns | 定义列宽(如 1fr 1fr 1fr 三列等宽) |
grid-template-rows | 定义行高(如 100px auto 两行) |
gap | 网格间距(行间距 + 列间距) |
justify-items | 单元格内容在水平方向的对齐方式 |
align-items | 单元格内容在垂直方向的对齐方式 |
| 项目属性(子元素) | 作用 |
|---|---|
grid-column | 跨列(如 1 / 3 从第 1 列到第 3 列) |
grid-row | 跨行(如 2 / 4 从第 2 行到第 4 行) |
grid-area | 合并定义行和列(简化 grid-row/grid-column) |
示例:三列自适应卡片网格
css
.card-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); /* 自动适应列数,最小250px */
gap: 20px; /* 卡片间距 */
padding: 20px;
}
.card {
padding: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
三、核心区别与适用场景
| 维度 | Flexbox | Grid |
|---|---|---|
| 布局维度 | 一维(单行或单列) | 二维(行 + 列) |
| 适用场景 | 线性布局(导航、列表、按钮组) | 整体页面布局、网格结构(卡片、画廊) |
| 控制粒度 | 侧重项目在单方向的对齐和分布 | 侧重网格整体结构和项目定位 |
| 内容适应性 | 内容驱动(根据内容动态调整) | 布局驱动(先定义结构再填充内容) |
何时用 Flexbox?
- 需实现水平 / 垂直居中(最简单的方案)。
- 处理单行 / 单列的均匀分布(如导航栏、标签页)。
- 内容长度不确定,需要自动伸缩(如弹性卡片内的文本和按钮)。
何时用 Grid?
- 页面整体布局(如头部、侧边栏、主内容、底部的排列)。
- 二维网格结构(如相册、产品列表,需要控制行数和列数)。
- 复杂的跨行列布局(如某元素占 2 行 3 列的仪表盘)。
四、混合使用技巧
Flexbox 和 Grid 并非互斥,实际开发中常结合使用:
- Grid 作为整体框架:用 Grid 定义页面的大结构(如三栏布局)。
- Flexbox 处理内部细节:在 Grid 的单元格内,用 Flexbox 对齐内部元素(如卡片内的文字和按钮垂直居中)。
示例:
css
/* 外层用 Grid 定义三栏布局 */
.page {
display: grid;
grid-template-columns: 200px 1fr 200px; /* 左栏200px,中栏自适应,右栏200px */
gap: 10px;
height: 100vh;
}
/* 中间栏内部用 Flexbox 垂直排列内容 */
.main-content {
display: flex;
flex-direction: column; /* 垂直排列 */
justify-content: center; /* 垂直居中 */
align-items: center; /* 水平居中 */
}
总结
- Flexbox 是「一维弹性布局」,适合处理线性排列和对齐,简单直观。
- Grid 是「二维网格布局」,适合复杂的行列结构,控制力更强。
- 灵活结合两者可高效解决几乎所有 CSS 布局问题,关键在于根据场景选择合适的工具。
9、vite和webpack的基础配置与使用
Vite 和 Webpack 都是前端构建工具,但底层原理和使用方式有显著差异。Vite 基于浏览器原生 ES 模块(ESM)实现快速开发,Webpack 则通过打包所有模块为 bundle 运行。以下是两者的基础配置与使用对比:
一、Vite 基础配置与使用
Vite 主打「快速开发启动」和「按需编译」,开发环境下无需打包,直接通过 ESM 加载模块,生产环境则使用 Rollup 打包。
1. 安装与创建项目
bash
# 创建项目(支持 Vue/React/ vanilla JS 等模板)
npm create vite@latest my-vite-project
cd my-vite-project
# 安装依赖
npm install
# 开发环境启动(默认端口 5173)
npm run dev
# 生产环境打包
npm run build
# 预览打包结果
npm run preview
2. 核心配置文件:vite.config.js
Vite 配置文件为 ESM 格式(使用 export default),常用配置项:
javascript
运行
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' // Vue 插件(需安装 @vitejs/plugin-vue)
import path from 'path'
export default defineConfig({
// 项目根目录(默认 process.cwd())
root: process.cwd(),
// 开发服务器配置
server: {
port: 8080, // 端口
open: true, // 自动打开浏览器
proxy: { // 接口代理
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源目录
sourcemap: false // 是否生成 sourcemap
},
// 路径别名
resolve: {
alias: {
'@': path.resolve(__dirname, 'src') // 用 @ 代替 src 目录
}
},
// 插件
plugins: [vue()] // 引入 Vue 插件(开发 Vue 项目时需要)
})
3. 核心特性与使用场景
- 无需打包的开发环境:启动速度极快(毫秒级),修改代码后热更新(HMR)响应迅速。
- 原生支持 ESM:开发时直接加载
.js、.vue等模块,无需编译为 CommonJS。 - 零配置支持:默认支持 JSX、TypeScript、CSS 预处理器(如
.scss只需安装sass)。 - 适合场景:中小型项目、需要快速开发体验的场景(Vue 3 官方推荐)。
二、Webpack 基础配置与使用
Webpack 是一个「模块打包器」,通过递归解析所有依赖,将模块打包为一个或多个 bundle 文件,支持各种 loader 和 plugin 扩展功能。
1. 安装与创建项目
bash
# 初始化项目
mkdir my-webpack-project
cd my-webpack-project
npm init -y
# 安装核心依赖
npm install webpack webpack-cli --save-dev
# 安装开发服务器(可选)
npm install webpack-dev-server --save-dev
# 开发环境启动(需配置 script)
npm run dev
# 生产环境打包
npm run build
在 package.json 中添加脚本:
json
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production"
}
}
2. 核心配置文件:webpack.config.js
Webpack 配置文件为 CommonJS 格式(使用 module.exports),常用配置项:
javascript
运行
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin') // 生成 HTML(需安装)
module.exports = {
// 入口文件
entry: './src/index.js',
// 输出配置
output: {
path: path.resolve(__dirname, 'dist'), // 输出目录
filename: 'bundle.[contenthash].js', // 打包文件名(带哈希值防缓存)
clean: true // 每次打包前清空 dist 目录
},
// 开发工具(sourcemap)
devtool: 'inline-source-map',
// 开发服务器
devServer: {
static: './dist', // 静态资源目录
port: 8080,
open: true,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
},
// 模块解析规则
module: {
rules: [
// 处理 CSS
{
test: /.css$/i,
use: ['style-loader', 'css-loader'] // 从右到左执行:css-loader 解析 CSS,style-loader 注入到 DOM
},
// 处理图片
{
test: /.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource' // 输出为单独文件
},
// 处理 ES6+(需安装 babel-loader @babel/core @babel/preset-env)
{
test: /.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
// 插件
plugins: [
// 生成 HTML 并自动引入打包后的 JS
new HtmlWebpackPlugin({
template: './src/index.html' // 基于此模板生成
})
]
}
3. 核心特性与使用场景
- 强大的打包能力:支持各种模块类型(JS、CSS、图片等),通过 loader 转换非 JS 资源。
- 丰富的插件生态:可通过插件实现代码分割、压缩、环境变量注入等功能。
- 适合场景:大型项目、需要复杂打包逻辑的场景(如多入口、代码分割、定制化构建流程)。
三、核心区别对比
| 维度 | Vite | Webpack |
|---|---|---|
| 开发模式 | 基于 ESM 按需加载,无需打包 | 打包所有模块为 bundle |
| 启动速度 | 极快(毫秒级) | 较慢(随项目体积增长变慢) |
| 热更新(HMR) | 基于模块依赖,更新速度快 | 需重新打包部分模块,速度较慢 |
| 配置复杂度 | 零配置起步,配置简单 | 配置繁琐,需手动配置 loader/plugin |
| 生态成熟度 | 较新,生态正在完善 | 成熟,插件 /loader 丰富 |
| 适用项目规模 | 中小型项目、快速开发 | 大型项目、复杂构建需求 |
四、总结
- Vite:优先考虑开发体验,适合追求快速启动和热更新的中小型项目,尤其适合 Vue/React 等现代框架。
- Webpack:适合需要复杂打包逻辑(如多入口、代码分割、深度定制)的大型项目,生态更成熟但配置较繁琐。
选择时可根据项目规模、团队熟悉度和构建需求决定,两者均支持主流前端框架(Vue、React、Angular 等)。
10、vite为什么开发环境下无需打包,直接通过 ESM 加载模块,生产环境则使用 Rollup 打包
Vite 在开发环境和生产环境采用不同处理方式的核心原因是场景目标不同:开发环境追求极致的启动速度和热更新效率,而生产环境需要优化的打包产物(更小体积、更好兼容性)。这种设计源于对现代浏览器原生能力的利用和对传统打包工具痛点的解决。
一、开发环境:为什么无需打包,直接用 ESM 加载?
传统打包工具(如 Webpack)在开发环境中需要提前递归解析所有依赖,打包成一个或多个 bundle 才能运行,这会导致两个核心问题:
- 启动慢:项目越大,依赖越多,打包时间越长(可能需要几十秒甚至几分钟)。
- 热更新慢:修改一个文件后,需要重新打包相关依赖,更新反馈滞后。
Vite 则利用了现代浏览器原生支持 ES 模块(ESM) 的特性(浏览器可直接通过 <script type="module"> 加载模块化 JS),彻底避免了开发环境的打包过程:
-
原生 ESM 按需加载
- 开发时,Vite 仅作为一个静态文件服务器,不提前打包任何代码。
- 浏览器请求入口文件(如
main.js)时,Vite 直接返回该文件,文件中的import语句会触发浏览器继续请求依赖模块(如vue、utils.js等)。 - 所有模块通过 HTTP 请求按需加载,只加载当前页面需要的模块,避免了全量打包的开销。
-
即时编译
- 对于非原生 ESM 的文件(如
.vue、.ts、.scss等),Vite 会在浏览器请求时实时编译(通过 esbuild 处理,速度是 Webpack 的 10-100 倍),并返回编译后的 ESM 代码。 - 例如:请求
App.vue时,Vite 会即时将其编译为 JS 模块(解析模板、样式、逻辑),再返回给浏览器。
- 对于非原生 ESM 的文件(如
-
高效热更新(HMR)
- 基于 ESM 的依赖关系,Vite 能精确判断修改的模块及其依赖范围,只更新受影响的模块,无需重新加载整个页面。
- 热更新时,仅通过 HTTP 推送少量变更代码,响应速度通常在毫秒级。
二、生产环境:为什么用 Rollup 打包?
虽然 ESM 加载在开发环境高效,但直接用于生产环境存在明显缺陷,因此 Vite 选择用 Rollup 进行生产打包:
-
浏览器兼容性问题
- 原生 ESM 虽已被现代浏览器支持,但部分旧浏览器(如 IE)完全不支持,且即使是现代浏览器,对 ESM 的解析效率也略低于打包后的 bundle(减少 HTTP 请求次数)。
-
性能优化需求
- 生产环境需要代码压缩、Tree-Shaking(删除未使用代码)、作用域提升(减少变量声明) 等优化,以减小文件体积、提升加载速度。
- Rollup 天生擅长处理 ESM 的静态分析,Tree-Shaking 效果比 Webpack 更彻底,输出的代码更简洁,适合库和应用打包。
-
减少网络请求
- 开发环境的按需加载会产生大量 HTTP 请求(每个模块一个请求),在生产环境中会显著增加网络开销(建立连接、传输延迟)。
- Rollup 将所有依赖打包成少量 bundle,大幅减少请求次数,提升实际运行时的加载效率。
-
生态适配
- Rollup 支持丰富的插件(如处理 CSS、图片、压缩代码等),能满足生产环境的各种打包需求,且配置简单,与 Vite 的「极简配置」理念契合。
三、总结:Vite 的设计逻辑
Vite 本质上是 「开发环境利用浏览器原生 ESM 规避打包开销,生产环境用成熟工具链优化产物」 的混合方案:
- 开发环境:以「快速反馈」为目标,借助浏览器 ESM 和 esbuild 即时编译,实现毫秒级启动和热更新。
- 生产环境:以「优化产物」为目标,借助 Rollup 的高效打包能力,确保代码体积小、兼容性好、运行高效。
这种设计既解决了传统打包工具的开发体验痛点,又保证了生产环境的产物质量,是对前端构建工具的一次重要优化。
10、vite和webpack的热更新原理
Vite 和 Webpack 的热更新(HMR,Hot Module Replacement)核心目标一致 —— 修改代码后无需刷新页面即可更新内容,但两者的实现原理因底层架构差异(ESM 原生加载 vs 打包构建)而完全不同,效率和流程也有显著区别。
一、Vite 的热更新原理:基于 ESM 原生加载 + 精准模块替换
Vite 在开发环境下基于 浏览器原生 ES 模块(ESM) 运行,无需提前打包,因此热更新流程更轻量、精准,响应速度通常在毫秒级。
核心流程
-
开发环境的 ESM 模块依赖图谱Vite 启动时会扫描项目的 ESM 依赖(通过
import语句),构建出一份模块依赖图谱(记录每个模块的父依赖、子依赖)。例如:main.js依赖App.vue,App.vue依赖utils.js,图谱会清晰记录这种关联。 -
文件监听与变更通知
- Vite 用
chokidar库监听本地文件变化(如修改.vue、.js文件)。 - 当文件变更时,Vite 首先确定变更的「目标模块」(如
utils.js),并通过依赖图谱找到所有「受影响的模块」(如依赖utils.js的App.vue、main.js)。
- Vite 用
-
即时编译变更模块
- 对于非原生 ESM 文件(如
.vue、.ts、.scss),Vite 会用 esbuild(比 Babel 快 10-100 倍)即时编译变更模块,生成浏览器可识别的 ESM 代码。 - 例如:修改
App.vue后,Vite 会实时解析其模板、样式、逻辑,编译为 JS 模块。
- 对于非原生 ESM 文件(如
-
WebSocket 推送更新指令
- Vite 客户端(注入到页面的
client.js)与服务端通过 WebSocket 长连接 通信。 - 服务端将「变更模块的路径 + 编译后的代码」封装成更新指令,通过 WebSocket 推送给客户端。
- Vite 客户端(注入到页面的
-
客户端精准替换模块
- 客户端接收更新指令后,根据依赖图谱找到需要更新的模块,用新代码替换内存中的旧模块(不刷新页面)。
- 若模块是组件(如 Vue 组件),Vite 会调用框架专属的 HMR 接口(如 Vue 的
reloadComponent),仅重新渲染该组件,不影响其他部分。 - 示例:修改
utils.js后,仅更新依赖它的App.vue组件,页面其他组件(如导航栏)保持不变。
核心优势
- 无打包开销:无需重新打包整个 bundle,仅处理变更模块,速度极快。
- 精准更新:基于 ESM 依赖图谱,只更新受影响的模块,避免全量刷新。
- 框架原生适配:与 Vue/React 等框架深度集成,支持组件级别的细粒度更新。
二、Webpack 的热更新原理:基于打包 Bundle + 模块运行时替换
Webpack 是「模块打包器」,开发环境下也会先将所有模块打包成 bundle.js,因此热更新需要基于打包后的产物和运行时环境实现,流程更复杂,速度受项目体积影响较大。
核心流程
-
开发环境打包:注入 HMR 运行时
- Webpack 开发模式(
mode: development)下,会在打包时自动注入 HMR 运行时(runtime) 代码(包含模块替换逻辑、WebSocket 客户端)和 模块热更新中间件(webpack-dev-middleware) 。 - 同时,会为每个模块分配唯一的
moduleId,并构建打包后的「模块依赖图谱」(记录在bundle.js中)。
- Webpack 开发模式(
-
文件监听与增量打包
- Webpack 用内置的
watch模式监听文件变化,或通过webpack-dev-server增强监听能力。 - 当文件变更时,Webpack 不会重新打包所有模块,而是进行 增量打包:仅重新编译变更模块及其依赖的模块,生成一个「更新 chunk(小块代码)」(如
0.hot-update.js),并生成一个「更新清单(manifest)」(如0.hot-update.json,记录更新模块的moduleId)。
- Webpack 用内置的
-
WebSocket 推送更新清单
- Webpack-dev-server 与客户端通过 WebSocket 通信,当增量打包完成后,服务端将「更新清单的 URL」推送给客户端。
-
客户端加载更新 chunk 并替换模块
-
客户端(HMR 运行时)接收到更新清单后,先请求并加载对应的「更新 chunk」(如
0.hot-update.js)。 -
根据更新清单中的
moduleId,在内存中的「模块缓存」里找到旧模块,用新 chunk 中的代码替换旧模块的导出内容。 -
调用开发者定义的
module.hot.accept()回调(需手动配置或框架自动注入),执行自定义更新逻辑(如重新渲染组件、更新样式)。 -
若模块未配置 HMR 回调(或更新失败),Webpack 会降级为「刷新整个页面」。
- 示例:修改
App.jsx后,Webpack 增量打包App.jsx及其依赖,生成0.hot-update.js,客户端加载后替换App模块,React 框架调用render重新渲染该组件。
-
核心痛点
- 增量打包开销:即使是增量打包,项目体积越大(如 1000+ 模块),重新编译的时间越长。
- 依赖链越长越慢:若变更模块位于依赖链顶端(如
utils.js被 100 个组件依赖),需重新编译 101 个模块,速度显著下降。 - 配置复杂:需手动配置
module.hot.accept()或依赖框架插件(如react-refresh-webpack-plugin),否则可能降级为页面刷新。
三、核心区别对比
| 维度 | Vite HMR | Webpack HMR |
|---|---|---|
| 底层依赖 | 浏览器原生 ESM 加载 | 打包后的 Bundle + HMR 运行时 |
| 变更处理方式 | 即时编译单个 / 少量变更模块 | 增量打包变更模块及其依赖 |
| 速度关键影响因素 | 变更模块数量(与项目体积无关) | 项目总模块数 + 依赖链长度 |
| 模块替换粒度 | 细粒度(模块级 / 组件级) | 中等粒度(chunk 级,需手动配置细粒度) |
| 配置复杂度 | 零配置(框架自动适配) | 需配置 HMR 回调或框架插件 |
| 典型响应时间 | 毫秒级(10-100ms) | 百毫秒级(100-500ms,大型项目更久) |
总结
- Vite 的 HMR 是「基于 ESM 原生生态的精准更新」:利用浏览器能力规避打包开销,只处理变更模块,速度快、粒度细,适合现代前端项目。
- Webpack 的 HMR 是「基于打包产物的增量更新」:依赖打包流程和运行时替换,受项目体积影响大,配置复杂,但生态成熟,适合需要复杂打包逻辑的项目。
两者的差异本质是「原生 ESM 加载」与「传统打包」两种架构的体现,也是 Vite 能在开发体验上超越 Webpack 的核心原因之一。
11、npm yarn pnpm对比
npm、Yarn、pnpm 是前端开发中最常用的包管理工具,均基于 Node.js 生态,用于安装、管理项目依赖,但在依赖管理方式、性能、安全性等方面有显著差异。以下是三者的核心对比:
一、核心差异:依赖安装与存储方式
三者的核心区别在于 依赖的存储方式 和 项目中 node_modules 的结构,这直接影响安装速度、磁盘占用和依赖安全性。
1. npm(v3+)
-
依赖存储:采用「扁平式依赖 + 嵌套回退」策略。
- 优先将依赖平铺到
node_modules根目录(减少重复安装)。 - 若不同依赖需要同一包的不同版本,则在自身目录下嵌套安装该版本(可能导致深层嵌套)。
- 优先将依赖平铺到
-
node_modules结构:非确定性(相同package.json可能生成不同结构),存在依赖冗余(同一包的不同版本可能被多次安装)。 -
示例:若 A 依赖 lodash@4.17.0,B 依赖 lodash@3.10.0,则
node_modules中会有:plaintext
node_modules/ ├── lodash/ (4.17.0,供 A 使用) ├── A/ └── B/ └── node_modules/ └── lodash/ (3.10.0,供 B 使用)
2. Yarn(经典版)
-
依赖存储:与 npm v3+ 类似,采用扁平式依赖,但通过
yarn.lock保证node_modules结构的确定性(相同package.json+yarn.lock必生成相同结构)。 -
改进:
- 引入
yarn.lock解决 npm 早期版本依赖结构不确定的问题。 - 支持并行安装(比 npm 早期版本快),但本质仍是复制依赖到项目
node_modules,存在冗余。
- 引入
3. pnpm
-
依赖存储:采用「内容寻址存储 + 符号链接(symlink)」策略,彻底解决冗余问题。
- 全局存储:所有依赖会被安装到一个全局共享目录(如
~/.pnpm-store),按内容哈希存储(相同内容只存一次)。 - 项目链接:项目
node_modules中不直接存储依赖文件,而是通过符号链接指向全局存储中的对应版本,同时用「虚拟目录」处理依赖的依赖(避免嵌套)。
- 全局存储:所有依赖会被安装到一个全局共享目录(如
-
node_modules结构:确定性强,无冗余(同一包的同一版本全局只存一次),且依赖隔离严格(无法访问未声明的依赖)。 -
示例:无论多少项目依赖 lodash@4.17.0,全局存储中只存一份,项目中通过链接引用:
plaintext
node_modules/ ├── lodash -> .pnpm/lodash@4.17.0/node_modules/lodash (符号链接) └── .pnpm/ (虚拟目录,管理所有依赖的链接) └── lodash@4.17.0/ └── node_modules/ └── lodash/ (实际文件,指向全局存储)
二、关键特性对比
| 维度 | npm (v8+) | Yarn (经典版) | pnpm |
|---|---|---|---|
| 安装速度 | 较快(支持并行安装) | 较快(早期比 npm 快,现在差距缩小) | 极快(无复制,仅创建链接) |
| 磁盘占用 | 高(依赖重复存储) | 高(同 npm) | 极低(全局共享存储) |
| 依赖确定性 | 支持(通过 package-lock.json) | 支持(通过 yarn.lock) | 支持(通过 pnpm-lock.yaml) |
| 依赖隔离 | 弱(可访问未声明的依赖) | 弱(同 npm) | 强(只能访问声明的依赖) |
| 工作区(Monorepo) | 支持(v7+) | 支持 | 原生支持(体验最佳) |
| 安全性 | 中(可能存在依赖劫持风险) | 中(同 npm) | 高(符号链接隔离,避免依赖逃逸) |
| 生态兼容性 | 100%(官方工具,兼容性最好) | 99%(兼容 npm 生态) | 95%+(大部分场景兼容,少数需适配) |
| 常用命令 | npm install npm run | yarn add yarn run | pnpm add pnpm run |
三、适用场景
1. npm
-
优势:Node.js 内置,无需额外安装;生态兼容性最佳,无适配成本;v8+ 版本已支持并行安装、工作区等特性。
-
适用场景:
- 简单项目或新手入门(零学习成本)。
- 对兼容性要求极高的场景(如老旧项目依赖特殊包)。
2. Yarn
-
优势:早期解决了 npm 的诸多痛点(如锁文件确定性、并行安装),社区稳定;支持
yarn workspace管理 Monorepo。 -
适用场景:
- 团队习惯 Yarn 命令和工作流。
- 依赖较多的中型项目(比 npm 早期版本体验好)。
3. pnpm
-
优势:安装速度最快、磁盘占用最少;依赖隔离严格,安全性高;原生支持 Monorepo(比 npm/Yarn 更高效)。
-
适用场景:
- 大型项目或 Monorepo 架构(如多包管理的组件库、工具链)。
- 对安装速度和磁盘空间敏感的场景(如 CI/CD 环境、频繁重建的项目)。
- 重视依赖安全性的项目(避免意外引入未声明的依赖)。
四、总结
- 追求兼容性和零配置:选 npm(尤其简单项目)。
- 习惯 Yarn 生态和工作流:选 Yarn(经典版或 Yarn 2+,但 Yarn 2+ 兼容性略差)。
- 追求性能、安全性和 Monorepo 体验:选 pnpm(现代项目的首选,尤其是中大型项目)。
三者核心功能(安装依赖、运行脚本)一致,命令也大同小异(如 install/add、run),迁移成本较低,可根据项目需求灵活选择。
12、webpack性能优化
Webpack 作为主流的前端构建工具,在处理大型项目时容易出现构建速度慢、打包产物体积大等问题。性能优化需从「构建速度」和「产物优化」两方面入手,结合工具特性和工程实践,以下是具体方案:
一、构建速度优化(开发 / 生产环境通用)
1. 缩小打包范围
-
限制
loader作用范围:避免loader处理不必要的文件(如node_modules),通过testincludeexclude精准匹配。javascript
运行
// webpack.config.js module.exports = { module: { rules: [ { test: /.js$/, use: 'babel-loader', include: path.resolve(__dirname, 'src'), // 只处理 src 目录 exclude: /node_modules/ // 排除 node_modules } ] } } -
减少
resolve解析开销:明确模块查找规则,避免全量扫描。javascript
运行
resolve: { extensions: ['.js', '.vue', '.json'], // 只解析指定后缀(减少尝试) alias: { // 别名缩短查找路径 '@': path.resolve(__dirname, 'src'), 'components': path.resolve(__dirname, 'src/components') }, modules: [path.resolve(__dirname, 'node_modules')] // 限定 node_modules 路径 } -
忽略不需要的依赖:对某些库(如
moment包含大量语言包),用IgnorePlugin排除冗余文件。javascript
运行
const webpack = require('webpack'); module.exports = { plugins: [ new webpack.IgnorePlugin({ resourceRegExp: /^./locale$/, // 忽略 moment 的 locale 目录 contextRegExp: /moment$/ }) ] }
2. 使用缓存提升二次构建速度
-
cache缓存编译结果:Webpack 5+ 内置缓存,可缓存loader编译和模块解析结果。javascript
运行
module.exports = { cache: { type: 'filesystem', // 用文件系统缓存(默认内存缓存,重启失效) buildDependencies: { config: [__filename] // 配置文件变更时,缓存失效 } } } -
loader缓存:部分loader(如babel-loader)支持单独缓存,避免重复编译。javascript
运行
{ test: /.js$/, use: [ { loader: 'babel-loader', options: { cacheDirectory: true } // 缓存到 node_modules/.cache/babel-loader } ] }
3. 并行处理任务
-
thread-loader多进程编译:将耗时的loader(如babel-loader、ts-loader)放入单独进程处理(避免阻塞主进程)。bash
npm install thread-loader --save-devjavascript
运行
module.exports = { module: { rules: [ { test: /.js$/, use: [ 'thread-loader', // 放在耗时 loader 前,开启多进程 'babel-loader' ] } ] } } -
HappyPack(Webpack 4 常用,5 推荐thread-loader) :原理类似,通过多进程加速loader处理。
4. 替换高效工具链
-
用
swc-loader替代babel-loader:SWC 是 Rust 编写的编译器,编译速度比 Babel 快 10 倍以上(兼容性略差,需测试)。bash
npm install swc-loader @swc/core --save-devjavascript
运行
{ test: /.js$/, use: 'swc-loader' } -
用
esbuild-loader压缩代码:在生产环境用esbuild替代terser-webpack-plugin,压缩速度提升 5-10 倍。bash
npm install esbuild-loader --save-devjavascript
运行
optimization: { minimizer: [ new EsbuildPlugin({ target: 'es2015' // 压缩目标语法 }) ] }
二、产物优化(生产环境)
1. 减小产物体积
-
Tree-Shaking 清除无用代码:只保留被引用的代码(需满足:ES 模块 +
mode: production)。javascript
运行
module.exports = { mode: 'production', // 自动开启 Tree-Shaking optimization: { usedExports: true // 标记未使用的导出,配合 Terser 删除 } } -
代码分割(Code Splitting) :将代码拆分为多个 chunk,实现按需加载(减少初始加载体积)。
-
拆分第三方库:将
node_modules中的依赖单独打包(利用浏览器缓存)。javascript
运行
optimization: { splitChunks: { chunks: 'all', // 对所有 chunk(包括异步)生效 cacheGroups: { vendor: { // 第三方库单独打包 test: /[\/]node_modules[\/]/, name: 'vendors', chunks: 'all' } } } } -
异步加载路由 / 组件:通过
import()动态导入,Webpack 自动拆分 chunk。javascript
运行
// 路由懒加载示例(Vue/React 通用) const Home = () => import(/* webpackChunkName: "home" */ './pages/Home');
-
-
压缩代码与资源:
-
JS 压缩:Webpack 5 生产环境默认用
terser-webpack-plugin,可配置删除注释、压缩变量名。 -
CSS 压缩:用
css-minimizer-webpack-plugin压缩 CSS(需配合mini-css-extract-plugin提取 CSS)。bash
npm install css-minimizer-webpack-plugin mini-css-extract-plugin --save-devjavascript
运行
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { module: { rules: [ { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] // 提取 CSS 为单独文件 } ] }, plugins: [new MiniCssExtractPlugin()], optimization: { minimizer: [new CssMinimizerPlugin()] // 压缩 CSS } } -
图片压缩:用
image-webpack-loader压缩图片(配合url-loader或file-loader)。
-
2. 优化缓存策略
-
chunk 文件名添加哈希:内容变更时哈希值变化,避免浏览器缓存旧文件。
javascript
运行
output: { filename: 'js/[name].[contenthash:8].js', // JS 文件 chunkFilename: 'js/[name].[contenthash:8].chunk.js', // 异步 chunk assetModuleFilename: 'assets/[hash:8][ext]' // 图片等资源 } -
** runtimeChunk 抽离 **:将 Webpack 运行时代码(记录模块映射关系)单独打包,避免其变更导致其他 chunk 缓存失效。
javascript
运行
optimization: { runtimeChunk: { name: 'runtime' // 生成 runtime.[contenthash].js } }
3. 预加载与预连接
-
PrefetchPlugin预加载:提前加载可能需要的资源(如用户即将点击的路由)。javascript
运行
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin'); // Vue 生态常用 plugins: [ new PreloadWebpackPlugin({ rel: 'prefetch', // 空闲时预加载 include: 'asyncChunks' // 预加载所有异步 chunk }) ]
三、其他实用技巧
-
使用 Webpack 5:相比 Webpack 4,内置持久化缓存、更好的 Tree-Shaking、模块联邦等特性,性能提升显著。
-
分析打包结果:用
webpack-bundle-analyzer可视化产物结构,定位大文件或冗余依赖。bash
npm install webpack-bundle-analyzer --save-devjavascript
运行
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [new BundleAnalyzerPlugin()] }; // 运行时自动打开分析页面 -
减少
plugin数量:部分插件(如webpack.ProvidePlugin)会增加编译开销,只保留必要插件。 -
开发环境关闭不必要的优化:如
minimize: false、devtool: 'eval-cheap-module-source-map'(更快的 sourcemap)。
总结
Webpack 性能优化的核心思路是:
- 构建速度:缩小处理范围、利用缓存、并行处理、替换高效工具。
- 产物优化:减小体积(Tree-Shaking、代码分割、压缩)、优化缓存(哈希命名)、提升加载效率(预加载)。
13、前端静态资源压缩
前端静态资源压缩是提升页面加载速度的关键手段,通过减小资源体积(JS、CSS、图片、字体等)来减少网络传输时间。不同类型的资源有不同的压缩策略,以下是详细方案:
一、JavaScript 压缩
JS 是前端资源的核心,压缩可显著减小体积(通常能压缩 30%-60%),主要通过删除冗余代码和混淆变量名实现。
1. 构建工具自动压缩(推荐)
-
Webpack:生产环境默认通过
terser-webpack-plugin压缩(Webpack 5 内置),可配置更严格的压缩规则:javascript
运行
// webpack.config.js const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({ parallel: true, // 多进程压缩(加快速度) terserOptions: { compress: { drop_console: true, // 删除 console 语句 drop_debugger: true, // 删除 debugger pure_funcs: ['console.log'] // 彻底移除 console.log }, mangle: true // 混淆变量名(默认开启) } }) ] } }; -
Vite:生产环境默认用
esbuild压缩(速度比 Terser 快 10-100 倍),可通过build.minify配置:javascript
运行
// vite.config.js export default defineConfig({ build: { minify: 'esbuild', // 可选 'terser'(压缩率更高但速度慢) esbuild: { drop: ['console', 'debugger'] // 删除 console 和 debugger } } });
2. 手动压缩工具
-
Terser:独立使用时,可通过 CLI 压缩单个文件:
bash
npx terser input.js -o output.min.js --compress --mangle -
UglifyJS:适合 ES5 代码(不支持 ES6+,逐渐被 Terser 替代)。
二、CSS 压缩
CSS 压缩主要通过删除空格、注释、合并重复规则、简化属性值(如 margin: 0px → margin:0)实现。
1. 构建工具集成
-
Webpack:用
css-minimizer-webpack-plugin配合mini-css-extract-plugin(提取 CSS 为单独文件):bash
npm install css-minimizer-webpack-plugin mini-css-extract-plugin --save-devjavascript
运行
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); module.exports = { module: { rules: [ { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] // 提取 CSS } ] }, plugins: [new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' })], optimization: { minimizer: [new CssMinimizerPlugin()] // 压缩 CSS } }; -
Vite:默认用
esbuild压缩 CSS,无需额外配置,也可通过css.minify控制:javascript
运行
// vite.config.js export default defineConfig({ css: { minify: true // 生产环境默认开启 } });
2. 手动压缩工具
-
csso:压缩率高于普通工具,支持合并重复规则和简化选择器:
bash
npx csso input.css -o output.min.css -
cssnano:PostCSS 插件,可集成到构建流程中。
三、图片压缩
图片通常是页面体积最大的资源,压缩优先级最高,需在画质损失可接受的前提下减小体积。
1. 按图片类型优化
-
位图(JPEG/PNG) :
- JPEG:适合照片类图片,可通过降低「质量参数」(如 80%)压缩,肉眼几乎无差异。
- PNG:适合图标、透明图,可转为 PNG-8(256 色)替代 PNG-24(真彩色),体积可减少 50%+。
- WebP/AVIF:现代图片格式,同等画质下比 JPEG 小 30%+,比 PNG 小 50%+,推荐优先使用(需兼容处理)。
-
矢量图(SVG) :
-
去除冗余代码(注释、空格、无效属性),可通过
svgo工具压缩:bash
npx svgo input.svg -o output.min.svg
-
2. 构建工具自动压缩
-
Webpack:用
image-webpack-loader压缩图片(需配合file-loader或url-loader):bash
npm install image-webpack-loader --save-devjavascript
运行
module.exports = { module: { rules: [ { test: /.(png|jpe?g|gif|svg)$/i, use: [ 'file-loader', // 输出图片 { loader: 'image-webpack-loader', options: { mozjpeg: { quality: 80 }, // JPEG 质量 optipng: { enabled: false }, // 禁用 PNG 压缩(可选) pngquant: { quality: [0.6, 0.8] }, // PNG 质量范围 gifsicle: { interlaced: false }, // GIF 优化 webp: { quality: 80 } // 转为 WebP(可选) } } ] } ] } }; -
Vite:用
vite-plugin-imagemin插件:bash
npm install vite-plugin-imagemin --save-devjavascript
运行
import viteImagemin from 'vite-plugin-imagemin'; export default defineConfig({ plugins: [ viteImagemin({ gifsicle: { optimizationLevel: 7 }, mozjpeg: { quality: 80 }, pngquant: { quality: [0.6, 0.8] } }) ] });
3. CDN 与在线工具
- 上传图片到支持自动优化的 CDN(如 Cloudinary、七牛云、阿里云 OSS),自动生成 WebP/AVIF 格式并压缩。
- 在线工具:TinyPNG(压缩 PNG/JPEG)、Squoosh(Google 出品,支持多格式转换)。
四、字体压缩
字体文件(如 *.ttf、*.woff)体积较大,尤其是中文字体(常达几 MB),需针对性优化。
-
使用现代字体格式:
-
优先用
WOFF2(比 TTF 小 30%+),其次WOFF,兼容旧浏览器时降级为 TTF。 -
示例:CSS 中声明字体格式优先级:
css
@font-face { font-family: 'MyFont'; src: url('myfont.woff2') format('woff2'), url('myfont.woff') format('woff'), url('myfont.ttf') format('truetype'); }
-
-
提取子集字体:
-
只保留项目中用到的字符(如仅保留页面文字、图标符号),用
font-spider(字蛛)工具:bash
npm install font-spider -g font-spider ./src/*.html # 分析 HTML 并提取用到的字符
-
五、HTML 压缩
HTML 压缩主要删除空格、注释、空行,以及简化属性(如 class=" a b " → class="a b")。
-
构建工具集成:
-
Webpack:用
html-webpack-plugin配合html-minifier-terser:javascript
运行
const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', minify: { collapseWhitespace: true, // 折叠空格 removeComments: true, // 移除注释 removeRedundantAttributes: true, // 移除冗余属性(如 input 的 type="text") removeScriptTypeAttributes: true, // 移除 script 的 type="text/javascript" removeStyleLinkTypeAttributes: true // 移除 link 的 type="text/css" } }) ] }; -
Vite:用
vite-plugin-html插件:javascript
运行
import { createHtmlPlugin } from 'vite-plugin-html'; export default defineConfig({ plugins: [ createHtmlPlugin({ minify: true // 生产环境自动压缩 }) ] });
-
六、压缩效果与注意事项
-
效果参考:
- JS/CSS:压缩后体积减少 30%-60%。
- 图片:WebP 比 JPEG 小 30%+,PNG 转 WebP 小 50%+。
- 字体:子集化后体积可减少 80%+(中文字体尤其明显)。
-
注意事项:
-
压缩需在生产环境进行,开发环境保留原始文件便于调试。
-
图片压缩注意平衡「体积」和「画质」,避免过度压缩导致模糊。
-
现代浏览器对 WebP/AVIF 支持良好(覆盖 95%+ 全球用户),但需做好降级处理(如通过
<picture>标签提供 fallback):html
预览
<picture> <source srcset="image.avif" type="image/avif"> <source srcset="image.webp" type="image/webp"> <img src="image.jpg" alt="示例图片"> </picture>
-
总结
前端静态资源压缩的核心是 「按类型针对性优化」:
- JS/CSS/HTML:通过构建工具自动压缩,删除冗余代码。
- 图片:优先使用现代格式(WebP/AVIF),配合质量参数压缩。
- 字体:用 WOFF2 格式 + 子集化,减少无效字符。
结合 CDN 缓存和 HTTP 压缩(如 Gzip/Brotli),可进一步提升资源加载速度。
14、vue3新特性
Vue 3 带来了多项重要更新,在性能、开发体验和可维护性上有显著提升,以下是核心新特性:
1. Composition API(组合式 API)
-
替代传统 Options API(data、methods、computed 等),通过
setup()函数或<script setup>语法,按逻辑关注点组织代码,而非固定选项。 -
核心函数:
ref(包装基础类型响应式数据)、reactive(包装对象响应式数据)、computed(计算属性)、watch/watchEffect(监听)、onMounted等生命周期钩子。 -
示例:
vue
<script setup> import { ref, onMounted } from 'vue' const count = ref(0) const increment = () => count.value++ onMounted(() => console.log('组件挂载完成')) </script>
2. 更高效的响应式系统
-
基于 Proxy 实现(替代 Vue 2 的 Object.defineProperty),支持:
- 监听对象属性的新增 / 删除。
- 监听数组索引和
length变化。 - 支持 Map、Set 等原生集合类型。
-
响应式触发更精准,性能提升显著。
3. Fragment(片段)
-
组件模板不再要求单一根节点,可直接返回多个同级元素,避免不必要的 wrapper 标签。
-
示例:
vue
<template> <div>元素1</div> <div>元素2</div> </template>
4. Teleport(瞬移)
-
允许将组件的部分内容渲染到 DOM 中的指定位置(如 body 下),解决嵌套组件样式隔离(如模态框、弹窗)问题。
-
示例:
vue
<teleport to="body"> <div class="modal">这是弹窗</div> </teleport>
5. Suspense(实验性,需谨慎使用)
-
用于等待异步组件或异步操作(如 API 请求)完成后再渲染,提供加载状态 fallback。
-
示例:
vue
<Suspense> <template #default> <AsyncComponent /> <!-- 异步组件 --> </template> <template #fallback>加载中...</template> </Suspense>
6. 更好的 TypeScript 支持
- 源码使用 TypeScript 重写,类型定义更完善,Composition API 天然支持类型推断,开发时类型提示更友好。
7. 生命周期钩子调整
- 与 Composition API 配合,钩子函数前缀改为
on(如onMounted、onUpdated),并需从 Vue 中导入使用。 - 移除
beforeCreate和created,其逻辑可直接写在setup()中。
8. v-model 语法简化
-
支持在自定义组件上直接使用
v-model,默认绑定modelValue属性和update:modelValue事件,且可通过参数指定绑定名(如v-model:title)。 -
示例:
vue
<ChildComponent v-model:name="username" v-model:age="userAge" />
9. 异步组件优化
-
通过
defineAsyncComponent定义,支持更灵活的加载状态和错误处理。 -
示例:
javascript
运行
import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent({ loader: () => import('./Comp.vue'), loadingComponent: Loading, errorComponent: Error, delay: 200 })
10. 性能优化
- 编译时优化:通过静态分析标记静态节点,减少虚拟 DOM 比对开销。
- 更小的体积:按需引入 API,Tree-shaking 更彻底,生产环境包体积比 Vue 2 更小。
这些特性让 Vue 3 更适合大型应用开发,同时保持了 Vue 易学易用的特点。
Vue 3 是一次从内到外的全面进化。Composition API 是其灵魂,解决了大型项目的代码组织问题;基于 Proxy 的响应式系统和各种编译时优化带来了显著的性能提升;更好的 TS 支持和新的内置组件(Fragment, Teleport) 则极大地提升了开发体验和灵活性。
14、TS新特性
15、volar插件,live server插件
Volar 和 Live Server 是前端开发中常用的两款 VS Code 插件,分别专注于 Vue 开发支持和本地服务器调试,以下是它们的详细介绍:
1. Volar 插件
核心功能:专为 Vue 3 设计的官方推荐 IDE 工具,提供语法高亮、类型检查、代码提示等全方位支持,替代了 Vue 2 时代的 Vetur 插件。
主要特性:
- Vue 3 完全支持:完美适配
<script setup>、Composition API、TypeScript 集成等 Vue 3 新特性,类型推断精准。 - 语法高亮与格式化:对模板(Template)、脚本(Script)、样式(Style)分别提供针对性高亮,支持 Prettier 等格式化工具集成。
- 智能代码提示:模板中自动提示组件属性、指令(如
v-model、v-if),脚本中提示 Vue API(如ref、onMounted)及自定义变量 / 方法。 - TypeScript 深度整合:在 Vue 组件中使用 TS 时,提供类型检查、接口提示、错误诊断,支持
.vue文件作为模块导入的类型推断。 - 高效的虚拟 DOM 分析:通过分析组件结构,提供模板中变量引用的跳转(Go to Definition)、重命名(Rename)等功能。
优势:
- 性能优于 Vetur,对大型 Vue 项目的响应速度更快。
- 原生支持 Vue 3 的单文件组件(SFC)特性,无需额外配置。
使用场景:
Vue 3 项目开发(尤其是结合 TypeScript 时),替代 Vetur 获得更优体验。
2. Live Server 插件
核心功能:快速启动一个本地开发服务器,支持静态文件(HTML/CSS/JS)的实时预览,当文件修改时自动刷新浏览器,提升前端调试效率。
主要特性:
- 一键启动服务器:右键点击 HTML 文件选择「Open with Live Server」,自动在默认端口(通常是 5500)启动服务器,并在浏览器打开页面。
- 实时刷新:当 HTML、CSS、JS 等文件保存时,浏览器会自动重新加载页面,无需手动刷新。
- 跨设备调试:支持通过本地 IP 访问服务器(如
http://192.168.x.x:5500),方便在手机、平板等设备上预览效果。 - 自定义配置:可修改端口号、默认浏览器、根目录等(通过 VS Code 设置或项目根目录的
.live-server.json配置)。 - 支持 HTTPS:可配置启用 HTTPS 协议,模拟生产环境的安全域名场景。
优势:
- 轻量易用,无需手动配置 Nginx 或其他服务器,适合快速原型开发和静态页面调试。
- 实时刷新功能大幅减少重复操作,提升开发效率。
使用场景:
- 静态网站开发(纯 HTML/CSS/JS 项目)。
- 前端页面独立调试(不依赖后端服务时)。
- 多设备兼容性测试。
总结
- Volar 是 Vue 3 开发的必备工具,专注于提供精准的类型支持和 Vue 语法增强。
- Live Server 是通用的前端调试辅助工具,通过本地服务器和实时刷新简化静态页面开发流程。
两者可配合使用(例如在 Vue 项目中调试静态页面,或独立开发 Vue 组件时预览效果),提升整体开发效率。
16、vscode、cursor常用插件,前端开发
- 核心必备:Volar(Vue)、ES7+ React snippets(React)、Prettier、ESLint、Live Server。
- 效率提升:Auto Import、Path IntelliSense、GitHub Copilot、GitLens。
- 体验优化:Material Icon Theme、One Dark Pro、Indent Rainbow。
这些插件覆盖了前端开发的核心场景(语法支持、格式化、调试、协作),可根据项目类型(Vue/React/ 原生 JS)灵活搭配使用。
17、http协议、websocket HTTP 协议和 WebSocket 都是用于客户端与服务器通信的网络协议,但核心设计目标和通信方式差异显著,HTTP 侧重单向请求响应,WebSocket 侧重双向实时通信。
一、HTTP 协议
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网中最基础的应用层协议,主要用于浏览器与服务器之间传输网页、数据等内容。
核心特点
- 请求 - 响应模式通信由客户端主动发起请求(如 GET/POST),服务器接收后返回响应,响应完成后连接立即关闭(HTTP/1.1 支持长连接
Keep-Alive,但本质仍是 “请求 - 响应” 循环)。 - 无状态服务器不保存客户端的历史通信状态,每次请求都是独立的,需通过 Cookie、Session 等机制实现 “状态保持”。
- 单向通信只能由客户端向服务器发送请求,服务器无法主动向客户端推送数据(需客户端轮询、长轮询等方式模拟 “推送”,效率低)。
- 基于 TCP 传输底层依赖 TCP 协议建立可靠连接,确保数据有序、完整传输。
常见版本与差异
| 版本 | 核心改进 | 缺点 |
|---|---|---|
| HTTP/1.0 | 基础请求 - 响应,每次请求新建 TCP 连接 | 连接开销大,效率低 |
| HTTP/1.1 | 支持 Keep-Alive 长连接、管道化请求 | 队头阻塞(同一连接中前一个请求阻塞后续请求) |
| HTTP/2 | 二进制帧传输、多路复用(同一连接并行处理多个请求)、服务器推送 | 仍存在 TCP 层队头阻塞 |
| HTTP/3 | 基于 QUIC 协议(UDP 之上),解决 TCP 队头阻塞,提升弱网性能 | 生态支持尚未完全普及 |
典型使用场景
- 网页加载(浏览器请求 HTML/CSS/JS)。
- 接口调用(客户端通过 GET/POST 请求获取 / 提交数据,如登录、列表查询)。
二、WebSocket 协议
WebSocket 是一种全双工(双向)通信协议,基于 HTTP 协议握手建立连接后,可实现客户端与服务器的实时双向数据传输。
核心特点
- 握手建立连接首次通信需通过 HTTP 协议发起 “升级请求”(请求头包含
Upgrade: websocket和Connection: Upgrade),服务器同意后,连接从 HTTP 转为 WebSocket 协议,后续通信不再依赖 HTTP。 - 全双工通信连接建立后,客户端和服务器可随时向对方发送数据(如服务器主动推送消息给客户端),无需等待请求。
- 持久连接连接一旦建立会长期保持(除非主动关闭或网络异常),避免频繁建立连接的开销。
- 轻量传输数据帧格式简单(仅包含操作码、长度和数据),比 HTTP 请求头更节省带宽。
通信流程
-
客户端发送 HTTP 升级请求:
http
GET /ws HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Version: 13 -
服务器返回 101 状态码,确认协议升级:
http
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= -
连接建立,客户端与服务器通过 WebSocket 帧双向传输数据。
典型使用场景
- 实时聊天(如微信网页版、在线客服)。
- 实时数据展示(如股票行情、监控数据、直播弹幕)。
- 协同编辑(如多人在线文档)。
三、HTTP 与 WebSocket 核心差异
| 对比维度 | HTTP 协议 | WebSocket 协议 |
|---|---|---|
| 通信方式 | 单向(请求 - 响应) | 双向(全双工) |
| 连接状态 | 短连接(或长连接但依赖请求循环) | 持久连接 |
| 服务器推送 | 不支持(需轮询模拟) | 原生支持 |
| 数据开销 | 每次请求带完整 HTTP 头,开销大 | 仅握手时带 HTTP 头,后续帧轻量 |
| 适用场景 | 非实时数据交互(如接口调用、网页加载) | 实时双向通信(如聊天、直播) |
总结
- 若需求是 “客户端主动获取数据”(如查询列表、提交表单),选择 HTTP 协议,生态成熟、实现简单。
- 若需求是 “实时双向通信”(如服务器主动推送消息),选择 WebSocket 协议,避免轮询开销,提升实时性。
实际开发中,两者也可结合使用(如用 HTTP 实现登录认证,用 WebSocket 实现后续实时消息)。
要不要我帮你整理一份 HTTP 与 WebSocket 对比表格,方便你快速查阅核心差异?
17、前端安全
前端安全核心是防范浏览器端的攻击风险,主要围绕 XSS、CSRF、敏感数据泄露 等场景展开,以下是关键风险点及对应的防护方案:
一、核心安全风险与防护方案
1. XSS(跨站脚本攻击):最常见的前端攻击
原理:攻击者将恶意脚本(如 <script> 标签、onclick 事件)注入到页面中,当用户访问时脚本执行,窃取 Cookie、伪造操作等。分类:
- 存储型 XSS:恶意脚本存入服务器(如评论区、用户资料),所有访问该页面的用户都会触发。
- 反射型 XSS:恶意脚本通过 URL 参数注入(如
?name=<script>...</script>),仅当用户点击特定链接时触发。 - DOM 型 XSS:通过操作 DOM 注入脚本(如
document.write(location.hash)),无需服务器参与,直接在客户端执行。
防护措施:
-
输入输出过滤:对用户输入的内容(如评论、表单)进行转义(将
<转成<、>转成>),避免脚本被解析。- 前端可使用
DOMPurify库自动过滤恶意标签和属性。 - 后端需同步过滤,避免前端过滤被绕过(如抓包修改请求)。
- 前端可使用
-
使用安全的 DOM API:避免使用
document.write、innerHTML,优先用textContent(仅渲染文本,不解析 HTML)。 -
开启 CSP(内容安全策略):通过 HTTP 响应头
Content-Security-Policy限制脚本加载源(如仅允许加载自家域名的 JS),禁止内联脚本和eval。- 示例:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com。
- 示例:
-
为 Cookie 设置
HttpOnly属性:禁止 JS 读取 Cookie,防止 XSS 窃取(但无法防护 CSRF)。
2. CSRF(跨站请求伪造):伪造用户操作
原理:攻击者诱导用户在已登录的状态下,访问恶意页面或点击链接,触发向目标服务器的 “伪造请求”(如转账、修改密码),服务器误以为是用户主动操作。核心条件:
- 用户已登录目标网站,持有有效的 Cookie 或 Session。
- 攻击者知道目标接口的请求格式(如请求地址、参数)。
防护措施:
-
验证 Referer:服务器检查请求头
Referer(记录请求来源页面),仅允许来自自家域名的请求(但Referer可被篡改,需配合其他方案)。 -
使用 CSRF Token:
- 服务器在用户登录后,生成随机的
CSRF Token并返回给前端(如存入页面隐藏域、LocalStorage)。 - 前端发起请求时,需在参数或请求头中携带该 Token,服务器验证 Token 有效性,无效则拒绝请求(Token 随机且一次性,攻击者无法获取)。
- 服务器在用户登录后,生成随机的
-
SameSite Cookie:为 Cookie 设置
SameSite属性,限制 Cookie 仅在 “同站请求” 中携带(即仅当请求来自同一域名时,Cookie 才会发送)。
- 取值:
Strict(完全禁止跨站携带)、Lax(允许部分跨站请求,如点击链接,禁止表单提交)、None(需配合Secure,允许跨站携带,用于第三方登录等场景)。
3. 敏感数据泄露:前端数据暴露风险
常见场景:
- 前端代码中硬编码密钥(如 API 密钥、OSS 访问密钥),通过查看源码可直接获取。
- 接口返回敏感数据(如用户手机号、身份证号),未做脱敏处理,直接渲染在页面(或存在控制台日志中)。
- 本地存储(LocalStorage、SessionStorage)存储敏感信息(如 Token),易被 JS 读取(尤其是存在 XSS 风险时)。
防护措施:
-
避免前端硬编码密钥:敏感密钥需放在后端,前端通过接口动态获取临时凭证(如后端生成签名后的请求参数,前端直接使用)。
-
数据脱敏:接口返回敏感数据时,前端仅展示部分内容(如手机号显示为
138****5678),避免完整信息暴露。 -
谨慎使用本地存储:
- Token 优先用
HttpOnly Cookie或SessionStorage(会话结束后清除),避免用LocalStorage(长期存储且易被 JS 读取)。 - 敏感数据(如支付信息)不存本地,用完即删。
- Token 优先用
-
加密传输:所有接口使用 HTTPS,防止网络传输中数据被抓包窃取(HTTPS 会对数据加密,且验证服务器身份,避免中间人攻击)。
4. 其他安全风险
- 点击劫持(ClickJacking) :攻击者用透明 iframe 嵌套目标页面,诱导用户点击 “伪装按钮”(如用户以为点击 “领取红包”,实际点击了目标页面的 “转账” 按钮)。防护:前端通过
X-Frame-Options响应头禁止页面被 iframe 嵌套(取值DENY完全禁止,SAMEORIGIN仅允许同域名嵌套)。 - URL 跳转漏洞:若前端通过
location.href = 目标地址实现跳转,且目标地址由用户输入(如?redirect=http://malicious.com),可能被诱导到钓鱼网站。防护:校验跳转地址的域名,仅允许跳转至白名单域名(如仅允许https://example.com及其子域名)。 - 依赖包漏洞:前端项目依赖的 npm 包(如
lodash、vue)可能存在安全漏洞(如旧版本lodash有原型污染风险),被攻击者利用。防护:定期用npm audit或snyk扫描依赖包漏洞,及时升级到安全版本;避免引入不必要的依赖。
二、前端安全开发规范
- 所有用户输入必须 “过滤 + 校验”:无论是表单、URL 参数还是评论,都不能直接渲染或传给后端,需先处理特殊字符。
- 优先使用成熟的安全库:如用
DOMPurify防 XSS、js-cookie管理 Cookie(自动处理HttpOnly、SameSite),避免自己实现复杂逻辑导致漏洞。 - 避免使用危险语法:禁用
eval、with、Function构造函数(可能执行恶意代码),少用innerHTML、outerHTML。 - 定期做安全测试:上线前用工具扫描 XSS、CSRF 漏洞(如
Burp Suite),模拟攻击者视角排查风险。
18、microapp使用
一、什么是 MicroApp?
MicroApp 是一款基于 Web Component 的微前端框架,它具备以下特点:
- 零依赖:框架本身无额外依赖
- 简单易用:接入成本极低
- 技术栈无关:支持 React、Vue、Angular、jQuery 等任何技术栈
- 功能丰富:支持静态资源补全、JS 沙箱、样式隔离、数据通信等
二、核心概念
1. 基座应用 (主应用)
负责整合各个微应用的整体框架应用
2. 微应用 (子应用)
独立开发、独立部署的业务应用
三、快速开始
1. 安装
bash
复制下载
# 在基座应用中安装
npm install @micro-zoe/microapp
2. 基座应用配置
方法一:全局引入(推荐)
javascript
复制下载
// main.js
import microApp from '@micro-zoe/microapp'
microApp.start()
方法二:按需引入
javascript
复制下载
// main.js
import microApp from '@micro-zoe/microapp'
microApp.start({
// 全局配置
'router-mode': 'native', // 路由模式:native / abstract
'disable-memory-router': false, // 是否禁用虚拟路由系统
'disable-patch-request': false, // 是否禁用请求补全
})
3. 在基座中使用微应用
vue
复制下载
<template>
<div>
<h1>基座应用</h1>
<!-- 使用 micro-app 标签加载微应用 -->
<micro-app
name="my-app"
url="http://localhost:3001/"
baseroute="/my-app"
></micro-app>
</div>
</template>
<script setup>
// 不需要额外引入,micro-app 是全局可用的自定义元素
</script>
四、详细配置说明
1. 微应用属性配置
vue
复制下载
<micro-app
name="app1"
url="http://localhost:3001"
baseroute="/app1"
:data="microAppData"
keep-alive
disable-memory-router
disable-sandbox
inline
disable-scopecss
shadow-dom
></micro-app>
常用属性说明:
| 属性 | 说明 | 默认值 |
|---|---|---|
name | 应用名称,必填且唯一 | - |
url | 应用地址,必填 | - |
baseroute | 基座应用分配给微应用的基础路由 | - |
data | 传递给微应用的数据 | - |
keep-alive | 是否开启缓存 | false |
disable-sandbox | 是否禁用 JS 沙箱 | false |
inline | 是否使用内联脚本 | false |
disable-scopecss | 是否禁用样式隔离 | false |
shadow-dom | 是否使用 Shadow DOM | false |
五、数据通信
1. 基座向微应用传递数据
方式一:通过 data 属性
vue
复制下载
<template>
<micro-app
name="my-app"
url="http://localhost:3001"
:data="appData"
></micro-app>
</template>
<script setup>
import { ref } from 'vue'
const appData = ref({
user: { name: '张三', age: 25 },
permissions: ['read', 'write']
})
</script>
方式二:通过 setData 方法
javascript
复制下载
import microApp from '@micro-zoe/microapp'
// 设置数据
microApp.setData('my-app', {
type: '数据更新',
data: { message: 'Hello from base app' }
})
2. 微应用获取基座数据
javascript
复制下载
// 在微应用中
// 直接获取数据
const data = window.microApp?.getData()
// 监听数据变化
window.microApp?.addDataListener((newData) => {
console.log('来自基座应用的数据:', newData)
})
// 解除监听
window.microApp?.removeDataListener(callback)
3. 微应用向基座发送数据
javascript
复制下载
// 在微应用中
// 发送数据给基座
window.microApp?.dispatch({
type: '来自微应用的数据',
data: { message: 'Hello from micro app' }
})
4. 基座监听微应用数据
javascript
复制下载
// 在基座应用中
import microApp from '@micro-zoe/microapp'
// 监听指定应用的数据
microApp.addDataListener('my-app', (data) => {
console.log('来自微应用的数据:', data)
})
// 全局监听所有应用
microApp.addGlobalDataListener((data) => {
console.log('全局数据监听:', data)
})
六、路由配置
1. 基座路由配置 (Vue Router)
javascript
复制下载
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/app1/*',
name: 'App1',
component: () => import('@/views/MicroAppContainer.vue')
},
{
path: '/app2/*',
name: 'App2',
component: () => import('@/views/MicroAppContainer.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2. 微应用路由配置
微应用需要设置 baseroute:
javascript
复制下载
// 在微应用中 (React 示例)
import { BrowserRouter as Router } from 'react-router-dom'
// 获取基座应用下发的 baseroute
const baseroute = window.__MICRO_APP_BASE_ROUTE__ || '/'
function App() {
return (
<Router basename={baseroute}>
{/* 路由配置 */}
</Router>
)
}
javascript
复制下载
// 在微应用中 (Vue Router 示例)
import { createRouter, createWebHistory } from 'vue-router'
const baseroute = window.__MICRO_APP_BASE_ROUTE__ || '/'
const router = createRouter({
history: createWebHistory(baseroute),
routes: [
// 路由配置
]
})
七、微应用适配
1. 静态资源路径补全
如果微应用静态资源使用相对路径,需要配置 publicPath:
javascript
复制下载
// webpack 配置
module.exports = {
output: {
publicPath: process.env.NODE_ENV === 'production'
? 'https://your-cdn.com'
: 'http://localhost:3001',
}
}
或者使用 __MICRO_APP_PUBLIC_PATH__:
javascript
复制下载
// 动态设置 publicPath
if (window.__MICRO_APP_PUBLIC_PATH__) {
__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}
2. 生命周期适配
javascript
复制下载
// 在微应用中
const appName = 'your-app-name'
// 监听挂载
window.addEventListener(`micro-app-${appName}-mount`, () => {
console.log('微应用已挂载')
// 执行初始化操作
})
// 监听卸载
window.addEventListener(`micro-app-${appName}-unmount`, () => {
console.log('微应用已卸载')
// 执行清理操作
})
19、防抖和节流的实现,和使用场景
一、概念与核心区别
- 防抖:触发事件后,延迟
n毫秒执行函数。若n毫秒内再次触发,则重新计时。目的是「合并多次操作为最后一次」。 - 节流:触发事件后,每隔
n毫秒最多执行一次函数。目的是「限制执行频率,固定间隔执行」。
二、实现方式
1. 防抖(Debounce)
javascript
运行
// 基础版:延迟执行,重复触发则重置计时器
function debounce(fn, delay) {
let timer = null;
return function(...args) {
// 清除之前的计时器
if (timer) clearTimeout(timer);
// 重新计时
timer = setTimeout(() => {
fn.apply(this, args); // 绑定this和参数
timer = null;
}, delay);
};
}
// 立即执行版:首次触发立即执行,之后延迟重置
function debounceImmediate(fn, delay) {
let timer = null;
return function(...args) {
const isImmediate = !timer; // 首次触发时timer为null
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null; // 延迟后允许下次立即执行
}, delay);
if (isImmediate) {
fn.apply(this, args);
}
};
}
2. 节流(Throttle)
javascript
运行
// 时间戳版:首次触发立即执行,之后每隔delay执行一次
function throttleTimestamp(fn, delay) {
let lastTime = 0; // 上次执行时间
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now; // 更新上次执行时间
}
};
}
// 计时器版:延迟执行,保证最后一次触发会执行
function throttleTimer(fn, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null; // 执行后释放计时器
}, delay);
}
};
}
// 综合版:同时支持立即执行和延迟执行最后一次
function throttle(fn, delay) {
let lastTime = 0;
let timer = null;
return function(...args) {
const now = Date.now();
// 剩余时间 = 延迟时间 - (当前时间 - 上次执行时间)
const remaining = delay - (now - lastTime);
if (remaining <= 0) {
// 立即执行(清除可能存在的延迟计时器)
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
lastTime = now;
} else if (!timer) {
// 延迟执行最后一次(避免遗漏)
timer = setTimeout(() => {
fn.apply(this, args);
lastTime = Date.now();
timer = null;
}, remaining);
}
};
}
三、使用场景
防抖(Debounce)适用场景:
- 搜索框输入联想:用户连续输入时,等待输入停止后再发送请求(避免频繁接口调用)。
- 窗口 resize 事件:窗口调整大小时,等待调整结束后再计算布局(减少计算次数)。
- 按钮点击防重复提交:防止用户快速多次点击按钮触发重复操作(如表单提交)。
- 滚动加载(停止滚动后加载) :滚动停止后再加载更多内容。
节流(Throttle)适用场景:
- 滚动事件监听:如监听滚动位置显示 / 隐藏回到顶部按钮(固定间隔检测,避免高频触发)。
- 高频点击事件:如游戏中的射击按钮(限制每秒触发次数)。
- 视频 / 音频进度条拖动:拖动时每隔固定时间更新进度(避免连续触发更新)。
- 鼠标移动跟踪:如拖拽元素时更新位置(限制更新频率,提升性能)。
四、总结
- 防抖适合「等待操作结束后执行一次」的场景(如输入、 resize)。
- 节流适合「固定频率执行」的场景(如滚动、高频点击)。
- 实际开发中可根据需求选择基础版或增强版实现(如是否立即执行、是否保留最后一次触发)。
20、BFC原理和使用场景,如何触发BFC
BFC(Block Formatting Context,块级格式化上下文)
一、什么是 BFC?
BFC 是 CSS 中一种渲染机制,它是一个独立的渲染区域,规定了内部块级元素的布局规则,且与外部元素互不影响。可以理解为:BFC 是一个「隔离的容器」,容器内的元素布局不会干扰容器外的元素,反之亦然。
二、BFC 的核心原理(布局规则)
- 内部块级元素垂直排列:BFC 内的块级元素会在垂直方向上一个接一个地放置。
- 边距折叠(Margin Collapse) :BFC 内相邻块级元素的垂直外边距会发生折叠(取较大值),但不同 BFC 内的元素外边距不会折叠。
- 区域独立:BFC 的区域不会与浮动元素的区域重叠。
- 计算高度时包含浮动元素:BFC 在计算自身高度时,会包含内部所有浮动元素(解决「浮动元素导致父元素高度塌陷」问题)。
- 内部元素的定位不会影响外部:BFC 内元素的定位、浮动等操作,不会影响到外部元素的布局。
三、如何触发 BFC?
满足以下条件之一的元素会创建 BFC:
- 根元素(
<html>) :整个页面默认在一个 BFC 中。 - 浮动元素:
float值为left或right(非none)。 - 绝对定位 / 固定定位元素:
position为absolute或fixed。 - 块级容器且
overflow不为visible:如overflow: hidden、auto、scroll。 - 弹性盒 / 网格容器:
display: flex、inline-flex、grid、inline-grid。 - 表格相关元素:
display: table-cell(单元格)、table-caption等(表格默认创建 BFC)。 display为flow-root:CSS3 新增属性,专门用于创建无副作用的 BFC(推荐使用,无overflow的滚动条等副作用)。
四、BFC 的使用场景
利用 BFC 的隔离性和布局规则,可以解决以下常见 CSS 问题:
-
解决浮动元素导致的父元素高度塌陷父元素默认不会计算浮动子元素的高度,导致高度为 0。触发父元素的 BFC 后,其高度会包含浮动元素。
css
.parent { /* 触发 BFC,包含浮动子元素 */ overflow: hidden; /* 或使用更推荐的 flow-root */ /* display: flow-root; */ } .child { float: left; width: 100px; height: 100px; } -
阻止垂直外边距(Margin)折叠相邻块级元素的垂直外边距会折叠(如两个
p标签的margin-top和margin-bottom会取最大值)。将其中一个元素放入新的 BFC 中,可避免折叠。css
.box1 { margin-bottom: 20px; } .container { /* 触发 BFC,隔离内部元素 */ overflow: hidden; } .box2 { margin-top: 30px; }此时
box1和box2的间距为 50px(而非 30px)。 -
阻止元素被浮动元素覆盖浮动元素会脱离文档流,可能覆盖相邻的非浮动元素。让非浮动元素触发 BFC,可避免被覆盖(常用于实现「两栏布局」)。
css
.float-box { float: left; width: 200px; height: 300px; } .normal-box { /* 触发 BFC,不与浮动元素重叠 */ overflow: hidden; /* 宽度会自动适应剩余空间,实现两栏布局 */ } -
清除内部浮动与「解决父元素高度塌陷」原理一致,BFC 会包含内部浮动元素,无需额外添加
clear: both。
五、总结
- BFC 是独立的渲染区域,内部元素布局不影响外部,反之亦然。
- 触发方式:通过
overflow(非 visible)、float(非 none)、position(absolute/fixed)、display: flow-root等。 - 核心用途:解决高度塌陷、阻止外边距折叠、实现自适应布局、避免浮动覆盖等。
- 推荐方案:优先使用
display: flow-root触发 BFC,无副作用(如overflow: hidden可能隐藏溢出内容)。
21、webpack命令执行过程
Webpack 命令的执行过程可以分为初始化、编译、输出三大阶段,每个阶段包含多个关键步骤。以下是详细拆解:
一、初始化阶段(准备工作)
-
解析命令行参数Webpack 首先解析终端输入的命令(如
webpack、webpack serve)及参数(如--mode production、--config webpack.config.js),确定构建模式、配置文件路径等核心信息。 -
加载配置文件
- 默认查找项目根目录的
webpack.config.js(或.json、.ts等格式),也可通过--config指定路径。 - 执行配置文件中的代码(支持
require模块、环境变量process.env等),合并默认配置与用户配置,生成最终配置对象(包含entry、output、module、plugins等)。
- 默认查找项目根目录的
-
初始化 Compiler 实例Webpack 核心类
Compiler被实例化,传入最终配置对象。Compiler是全局唯一的编译管理器,负责统筹整个构建流程,暴露生命周期钩子(如entryOption、run、emit等)供插件调用。 -
注册插件遍历配置中的
plugins数组,调用每个插件的apply方法,将插件挂载到Compiler实例上,插件可通过监听钩子介入构建过程。
二、编译阶段(核心处理)
-
**入口处理(Entry Option)**根据配置的
entry(入口文件,如./src/index.js),解析入口模块的路径,触发entryOption钩子(插件可在此修改入口)。 -
构建模块依赖图(Dependency Graph)
- 从入口开始递归解析:通过
loader处理不同类型的文件(如babel-loader转译 JS、css-loader解析 CSS),将非 JS 模块转为 JS 模块。 - 收集依赖:解析每个模块的代码(借助
acorn等工具进行 AST 语法分析),找出import、require等依赖语句,递归处理依赖模块,形成完整的依赖图(所有模块及其依赖关系)。 - 模块缓存:已解析的模块会被缓存,避免重复处理。
- 从入口开始递归解析:通过
-
模块转换与优化
-
Loader 链执行:对模块按
module.rules配置的loader顺序处理(从右到左 / 从下到上),例如style-loader!css-loader!sass-loader会先将 SASS 转 CSS,再处理 CSS 依赖,最后注入到 DOM。 -
代码优化:
- Tree-shaking:移除未引用的代码(需
mode: production或配置usedExports)。 - 代码分割(Code Splitting):根据
splitChunks配置拆分公共模块、异步模块。 - 压缩混淆:通过
terser-webpack-plugin等压缩 JS,css-minimizer-webpack-plugin压缩 CSS。
- Tree-shaking:移除未引用的代码(需
-
-
生成 Chunk
- 依赖图中的模块会被分组为 Chunk(代码块):入口模块对应一个初始 Chunk,异步加载的模块(如
import())会生成新的 Chunk。 - 插件(如
HtmlWebpackPlugin)可在此阶段介入,修改 Chunk 内容。
- 依赖图中的模块会被分组为 Chunk(代码块):入口模块对应一个初始 Chunk,异步加载的模块(如
三、输出阶段(生成文件)
-
确定输出路径与文件名根据
output配置(如path输出目录、filename文件名规则),计算每个 Chunk 的最终输出路径和文件名(支持变量如[name]、[hash])。 -
生成 Assets
- 将每个 Chunk 转换为可输出的 Asset(资源文件,如
.js、.css、.png等)。 - 处理资源路径:通过
file-loader、url-loader等将图片、字体等资源输出到指定目录,并替换代码中的资源路径为最终 URL。
- 将每个 Chunk 转换为可输出的 Asset(资源文件,如
-
输出文件到磁盘
- 触发
emit钩子(插件可在此阶段修改即将输出的文件内容)。 - 通过
fs模块将所有 Asset 写入到output.path配置的目录中。
- 触发
-
完成构建
- 触发
done钩子,输出构建成功信息(如耗时、文件大小)。 - 若使用
webpack serve(开发服务器),则不会退出进程,而是监听文件变化,触发热更新(HMR) 流程(重新编译变化的模块并更新浏览器)。
- 触发
简化流程总结
plaintext
解析命令 → 加载配置 → 初始化 Compiler → 注册插件
→ 处理入口 → 递归解析模块与依赖 → 用 Loader 转换模块
→ 优化代码(Tree-shaking、分割等) → 生成 Chunk
→ 确定输出路径 → 生成资源文件 → 写入磁盘 → 完成构建
理解这一过程有助于排查构建问题(如 Loader 失效、插件冲突)和优化构建性能(如减少不必要的模块解析、合理配置缓存)。
22、webpack的split chunks如何确定分包大小
minSize 和 maxSize 的值需要结合项目实际场景(如模块体积、网络环境、性能目标)来确定,没有固定标准,但有一些通用的设计思路和参考原则。以下是具体分析:
一、核心设计原则
-
平衡「请求数」和「单文件大小」
- 过小的
minSize会导致拆分出大量小 Chunk,增加 HTTP 请求数(或 HTTP/2 连接开销),反而降低性能。 - 过大的
minSize会导致单文件体积过大,首次加载时间变长(尤其在弱网环境)。 - 目标:在「减少大文件」和「避免过多小文件」之间找平衡点。
- 过小的
-
结合网络环境
- 面向 PC 端 / 高速网络:可适当增大
minSize(允许更大的单文件,减少请求数)。 - 面向移动端 / 弱网环境:需减小
minSize(拆分更大的文件,利用并行加载提升速度)。
- 面向 PC 端 / 高速网络:可适当增大
-
参考模块实际体积
- 分析项目中第三方库(如
lodash、react)和业务模块的体积,让minSize略小于常见的「可共享大模块」体积,确保其被拆分。 - 例如:若项目中多数共享工具库体积在 30KB
100KB,30KB(确保这些库被拆分)。minSize可设为 20KB
- 分析项目中第三方库(如
二、minSize 的取值建议
minSize 决定了「最小可拆分的 Chunk 体积」,默认值为 20KB(Webpack 5),实际可按以下场景调整:
| 场景 | 建议值(字节) | 说明 |
|---|---|---|
| 通用项目(默认) | 20000(20KB) | Webpack 默认值,适合多数场景,避免拆分过小的模块(如几 KB 的工具函数)。 |
| 移动端 / 弱网项目 | 10000~15000 | 允许拆分更小的模块(10KB~15KB),利用并行加载加速弱网环境下的加载。 |
| 大型第三方库较多 | 30000~50000 | 若项目依赖多个 50KB 以上的库(如 echarts、pdf.js),可提高 minSize 避免拆分出过多 20KB 左右的小库,减少请求数。 |
| 业务模块碎片化严重 | 15000~25000 | 若业务代码中有很多 10KB~30KB 的独立模块,可略降低 minSize 拆分这些模块,提升缓存利用率(修改一个模块不影响其他)。 |
三、maxSize 的取值建议
maxSize 用于限制「单个 Chunk 的最大体积」,超过则尝试拆分,默认值为 0(不限制)。需根据「最大可接受的单文件加载时间」来设定:
-
计算参考值
- 假设目标加载时间为 1 秒(弱网环境下 3G 网速约 300kbps,即~37KB / 秒),则
maxSize不宜超过 30KB~40KB。 - 若为 4G 环境(约 1Mbps,
125KB / 秒),150KB。maxSize可放宽到 100KB - 若使用 CDN 且主要面向 PC 端,
maxSize可设为 200KB~300KB(避免过度拆分)。
- 假设目标加载时间为 1 秒(弱网环境下 3G 网速约 300kbps,即~37KB / 秒),则
-
常见取值场景
场景 建议值(字节) 说明 弱网 / 移动端优先 30000~50000 限制单文件最大 30KB 50KB,确保单个文件能在 12 秒内加载完成。普通网络环境 100000~200000 允许 100KB~200KB 的单文件,平衡加载速度和请求数。 大型库单独处理 不设置(0) 若某些库(如 react约 40KB,vue约 33KB)本身体积适中,无需拆分,可保持默认值 0。 -
注意:
maxSize是「建议值」,Webpack 可能无法完美拆分(如模块不可分割时),且拆分后子 Chunk 仍需满足minSize。
四、实操步骤:如何确定具体值?
-
分析现有模块体积
-
使用
webpack-bundle-analyzer生成体积分析报告,查看:- 第三方库的体积(如
node_modules中各包的大小)。 - 业务代码中较大的模块(如页面组件、工具库)的体积。
- 第三方库的体积(如
-
例:若报告显示多数共享模块在 25KB~80KB,
minSize可设为 20KB(确保这些模块被拆分),maxSize设为 100KB(避免 80KB 以上的模块过大)。
-
-
结合性能指标测试
- 用 Lighthouse 或 WebPageTest 测试不同配置下的加载性能(首次内容绘制 FCP、交互时间 TTI 等)。
- 若发现「大文件加载缓慢」,则减小
maxSize;若「请求数过多导致阻塞」,则增大minSize。
-
参考社区最佳实践
- 多数开源项目(如 React 生态、Vue 生态)的
minSize设为 20KB30KB,150KB。maxSize设为 100KB - 大型应用(如电商平台)常将
minSize设为 30KB~50KB,避免拆分过多小文件影响首页加载速度。
- 多数开源项目(如 React 生态、Vue 生态)的
五、示例配置
javascript
运行
// 移动端弱网项目配置
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 15000, // 15KB:允许拆分较小的模块
maxSize: 50000, // 50KB:限制单文件最大体积,适合弱网
minChunks: 2, // 至少被 2 个模块引用才拆分
},
},
};
// PC 端大型应用配置
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000, // 30KB:避免拆分过小模块
maxSize: 200000, // 200KB:允许更大的单文件,减少请求数
minChunks: 3, // 需被 3 个以上模块引用才拆分
},
},
};
总结
minSize主要避免「过多小文件」,取值需略小于项目中常见的「可共享大模块」体积(通常 15KB~50KB)。maxSize主要避免「单个文件过大」,取值需结合目标网络环境的加载速度(通常 30KB~200KB)。- 没有「最优值」,需通过体积分析工具和性能测试,根据项目实际情况动态调整
23、ts的泛型和unknown、any的区别
TypeScript 中的泛型(Generics) 、unknown 和 any 都与「类型灵活性」相关,但它们的设计目的和使用场景截然不同。理解三者的区别是写出类型安全代码的关键,以下是详细解析:
一、泛型(Generics):类型参数化,兼顾灵活性与类型安全
泛型是一种「类型参数化」机制,允许在定义函数、类、接口时不指定具体类型,而是在使用时动态传入类型,从而实现类型复用和类型关联,同时保持类型检查。
核心特点:
- 类型参数化:用
<T>等占位符表示「未知类型」,使用时由调用方指定具体类型。 - 类型安全:编译期会根据传入的类型进行严格检查,避免类型错误。
- 类型关联:能在输入和输出之间建立类型关联(如函数参数和返回值类型一致)。
示例:
typescript
// 泛型函数:实现输入输出类型一致
function identity<T>(arg: T): T {
return arg;
}
// 使用时指定类型(或由 TS 自动推断)
const num: number = identity(123); // 正确:T 被推断为 number
const str: string = identity("hello"); // 正确:T 被推断为 string
const err: string = identity(123); // 错误:number 不能赋值给 string
适用场景:
- 实现通用工具函数(如数组操作、缓存逻辑),需保证输入输出类型一致。
- 定义可复用的组件、类或接口(如 React 组件的
Props类型)。 - 关联多个类型(如函数参数、返回值、内部变量的类型)。
二、unknown:类型安全的「未知类型」,需显式断言后使用
unknown 表示「类型未知」,是 TypeScript 3.0 引入的类型,比 any 更安全。它允许接收任意类型的值,但使用前必须通过类型检查或断言明确其类型,否则无法进行任何操作。
核心特点:
- 接收任意类型:可以赋值为任何类型的值(与
any类似)。 - 类型安全:对
unknown类型的值进行操作前,必须先确认其具体类型(否则报错)。 - 不可赋值给其他类型:
unknown类型的值不能直接赋值给除any和unknown外的其他类型(需断言)。
示例:
typescript
let value: unknown;
// 1. 可以赋值为任意类型
value = 123;
value = "hello";
value = { foo: "bar" };
// 2. 直接操作会报错(必须先确认类型)
value.toFixed(); // 错误:unknown 类型上不存在 toFixed 方法
// 3. 类型检查后可安全使用
if (typeof value === "number") {
value.toFixed(); // 正确:确认是 number 类型
}
// 4. 类型断言后使用(需手动保证类型正确)
(value as string).toUpperCase(); // 正确:断言为 string 类型
适用场景:
- 接收未知来源的数据(如 API 响应、用户输入),需在使用前验证类型。
- 替代
any实现类型安全的「动态类型处理」,避免无意识的类型错误。
三、any:完全关闭类型检查,灵活性代价是类型不安全
any 表示「任意类型」,使用 any 后 TypeScript 会完全关闭该变量的类型检查,允许对其执行任何操作,也可以赋值给任何类型。
核心特点:
- 接收任意类型:与
unknown一样,可以赋值为任何类型的值。 - 关闭类型检查:对
any类型的值执行任何操作(调用不存在的方法、访问不存在的属性)都不会报错。 - 可赋值给任意类型:
any类型的值可以直接赋值给其他任何类型(无需断言)。
示例:
typescript
let value: any;
// 1. 可以赋值为任意类型
value = 123;
value = "hello";
value = { foo: "bar" };
// 2. 执行任何操作都不报错(即使逻辑错误)
value.toFixed(); // 不报错(即使 value 实际是 string)
value.foo(); // 不报错(即使 foo 不是函数)
// 3. 可直接赋值给其他类型
const num: number = value; // 不报错(即使 value 是 string)
const str: string = value; // 不报错
适用场景:
- 临时兼容 JavaScript 代码(迁移老项目时),避免过多类型错误。
- 处理动态性极强的场景(如动态生成的对象、与 JS 库的交互),且无法确定具体类型。
- 不推荐在新项目中大量使用,会失去 TypeScript 的类型安全优势。
四、三者核心区别对比
| 特性 | 泛型(Generics) | unknown | any |
|---|---|---|---|
| 类型检查 | 严格检查(基于传入的类型参数) | 必须显式确认类型后才能操作 | 完全关闭类型检查 |
| 类型关联 | 能关联输入 / 输出 / 内部变量的类型 | 无关联(仅表示单个值的未知类型) | 无关联 |
| 赋值规则 | 受类型参数约束 | 可赋值给 unknown/any,其他类型需断言 | 可赋值给任何类型 |
| 使用代价 | 需定义类型参数(略复杂) | 需显式类型检查 / 断言 | 无代价(但丢失类型安全) |
| 设计目的 | 类型复用与关联,保持类型安全 | 安全处理未知类型 | 最大灵活性,兼容非类型化代码 |
五、总结
- 泛型:用于「已知需要类型灵活性,但能在使用时确定具体类型」的场景,核心是类型参数化和关联,完全类型安全。
- unknown:用于「类型未知,但需要类型安全」的场景,核心是必须显式确认类型后才能使用,是
any的安全替代。 - any:用于「类型完全无法确定,且需要最大灵活性」的场景,核心是关闭类型检查,代价是失去 TypeScript 的类型保护。
最佳实践:优先使用泛型和 unknown,避免滥用 any。泛型适合通用逻辑的类型复用,unknown 适合处理动态输入,any 仅作为最后的妥协。
24、http1升级到http2需要做哪些操作
将服务从 HTTP/1 升级到 HTTP/2 能显著提升性能(如多路复用、头部压缩等),但需要服务器、客户端和应用层做相应调整。以下是具体操作步骤和注意事项:
一、核心前提:HTTPS 支持
HTTP/2 虽然标准允许非加密传输,但几乎所有主流浏览器(Chrome、Firefox、Safari 等)仅支持 “基于 TLS 的 HTTP/2” (即 https:// 协议)。因此,升级 HTTP/2 的前提是:必须先部署 HTTPS(配置 SSL/TLS 证书) ,否则浏览器会拒绝使用 HTTP/2 协议。
二、服务器配置(核心步骤)
HTTP/2 的支持主要依赖服务器端配置,不同服务器的开启方式不同,以下是主流服务器的配置示例:
1. Nginx 配置
-
版本要求:Nginx 1.9.5+ 支持 HTTP/2,建议使用最新稳定版(如 1.25+)。
-
配置步骤:
nginx
# 1. 确保已配置 HTTPS(SSL 证书) server { listen 443 ssl http2; # 关键:在 443 端口后添加 http2 标识 server_name example.com; # SSL 证书配置(必填) ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; # 可选:优化 TLS 配置(推荐使用现代加密套件) ssl_protocols TLSv1.2 TLSv1.3; # 禁用老旧协议 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers on; }- 重启 Nginx 后,通过
nginx -t检查配置,systemctl restart nginx生效。
- 重启 Nginx 后,通过
2. Apache 配置
-
版本要求:Apache 2.4.17+ 支持 HTTP/2,需启用
mod_http2模块。 -
配置步骤:
apache
# 1. 启用 http2 模块(先执行 a2enmod http2) LoadModule http2_module modules/mod_http2.so # 2. 在 HTTPS 虚拟主机中开启 HTTP/2 <VirtualHost *:443> ServerName example.com Protocols h2 http/1.1 # 优先使用 h2(HTTP/2),降级支持 HTTP/1.1 # SSL 证书配置 SSLEngine on SSLCertificateFile /path/to/cert.pem SSLCertificateKeyFile /path/to/key.pem </VirtualHost>- 重启 Apache:
systemctl restart apache2。
- 重启 Apache:
3. Node.js 服务(如 Express)
-
需依赖支持 HTTP/2 的库(如
spdy或原生http2模块)。 -
示例(使用原生
http2模块):javascript
运行
const http2 = require('http2'); const fs = require('fs'); // 加载 SSL 证书 const options = { key: fs.readFileSync('/path/to/key.pem'), cert: fs.readFileSync('/path/to/cert.pem') }; // 创建 HTTP/2 服务器 const server = http2.createSecureServer(options, (req, res) => { res.end('Hello HTTP/2!'); }); server.listen(443, () => { console.log('HTTP/2 server running on port 443'); });
4. 云服务 / CDN 配置
若使用云服务商(如阿里云、Cloudflare)或 CDN,通常只需在控制台开启 “HTTP/2 支持”:
- Cloudflare:在 “SSL/TLS” 设置中启用 “HTTP/2”(默认开启)。
- 阿里云 CDN:在 “域名配置 - 高级设置” 中勾选 “HTTP/2 协议支持”。
三、应用层适配(避免兼容性问题)
HTTP/2 虽然设计为 “与 HTTP/1 语义兼容”,但部分旧有实践可能导致性能问题或错误,需调整:
1. 避免 “域名分片” 反模式
- HTTP/1 因 “队头阻塞” 问题,常通过多域名(如
img1.example.com、img2.example.com)并行加载资源。 - HTTP/2 支持多路复用(一个连接并发传输多个资源),域名分片会增加连接开销,建议合并为单域名。
2. 优化资源合并策略
- HTTP/1 中常合并小 JS/CSS 减少请求数,但 HTTP/2 中过度合并会导致 “缓存失效” 和 “首屏加载冗余”。
- 建议:按功能拆分资源(如基础库、页面组件),保持合理颗粒度,利用 HTTP/2 的多路复用并行加载。
3. 检查依赖库兼容性
- 部分老旧客户端库(如过时的 AJAX 工具、WebSocket 封装)可能不兼容 HTTP/2,需升级到最新版本。
- 确认 WebSocket 连接:HTTP/2 支持
h2c-ws(非加密)和h2-ws(加密),但需确保客户端和服务器端库支持(如ws库 v7+ 支持 HTTP/2)。
4. 利用 HTTP/2 新特性(可选)
-
服务器推送(Server Push) :服务器可主动推送页面依赖的资源(如 HTML 引用的 CSS/JS),减少请求延迟。
-
Nginx 配置示例(推送
style.css):nginx
location / { http2_push /style.css; }
-
-
头部压缩(HPACK) :HTTP/2 自动压缩请求头 / 响应头,无需手动配置,但需避免重复发送冗余头部(如不必要的
Cookie)。
四、验证 HTTP/2 是否生效
-
浏览器验证:打开 Chrome 开发者工具(F12)→“Network” 面板→右键表头→勾选 “Protocol”,查看请求的协议列是否为
h2(表示 HTTP/2)。 -
命令行验证:使用
curl测试(需 curl 7.47+):bash
curl -I --http2 https://example.com # 若返回 "HTTP/2 200" 则表示生效 -
在线工具:使用 HTTP/2 Test 输入域名,检测是否支持 HTTP/2。
五、注意事项
- 降级兼容:HTTP/2 服务器会自动对不支持的客户端(如老旧浏览器)降级为 HTTP/1.1,无需额外配置。
- 性能监控:升级后建议监控核心指标(如页面加载时间、连接数),对比 HTTP/1 时代的性能提升。
- TLS 优化:HTTP/2 依赖 TLS,需确保 TLS 配置安全(禁用弱加密套件、使用 TLS 1.2+),可通过 SSL Labs 评分 检测。
总结
升级 HTTP/2 的核心步骤是:
- 部署 HTTPS(配置 SSL 证书);
- 在服务器端开启 HTTP/2 支持(如 Nginx 添加
http2标识); - 调整应用层策略(如取消域名分片、优化资源合并);
- 验证生效并监控性能。
对于现代 Web 应用,HTTP/2 能显著提升加载速度,且升级成本较低,是值得优先实施的优化方案。
编辑分享
如何判断服务器是否支持HTTP/2?
除了Nginx,还有哪些服务器支持HTTP/2?
升级HTTP/2后,如何监控和优化性能?
25、nexttick实现原理
在 Vue 中,nextTick 是一个核心 API,用于在DOM 更新完成后执行回调函数,解决了数据更新后立即操作 DOM 可能获取不到最新 DOM 状态的问题。其实现原理与 JavaScript 的事件循环(Event Loop) 密切相关,核心是利用异步任务队列延迟执行回调,确保回调在 DOM 更新后触发。
一、为什么需要 nextTick?
Vue 采用异步更新 DOM 的策略:当组件数据(data)发生变化时,Vue 不会立即更新 DOM,而是将数据变化缓存起来,等待当前 “事件循环周期” 的同步代码执行完毕后,再批量更新 DOM。这一机制的目的是减少 DOM 操作次数,提升性能。
例如:
javascript
运行
this.msg = 'hello';
this.count = 10;
// 此时 DOM 尚未更新,直接操作 DOM 会获取旧值
console.log(document.getElementById('msg').textContent); // 旧值
此时需要用 nextTick 等待 DOM 更新完成:
javascript
运行
this.msg = 'hello';
this.count = 10;
this.$nextTick(() => {
// 回调在 DOM 更新后执行,可获取最新值
console.log(document.getElementById('msg').textContent); // 'hello'
});
二、nextTick 的实现原理
nextTick 的核心逻辑是:将回调函数放入 “异步任务队列”,并确保它在当前 DOM 更新周期后执行。具体步骤如下:
1. 异步任务队列的优先级
JavaScript 事件循环中,异步任务分为微任务(Microtask) 和宏任务(Macrotask) ,执行优先级为:同步代码 → 微任务队列(全部执行)→ 宏任务队列(取出一个执行)→ 重复循环。
微任务的优先级高于宏任务,常见的微任务有:
Promise.then()MutationObserver(监听 DOM 变化的 API)process.nextTick(Node.js 环境,优先级最高)
宏任务有:
setTimeoutsetIntervalsetImmediate(Node.js)- DOM 事件回调、I/O 操作等
2. Vue 对异步任务的选择
nextTick 会优先选择微任务(性能更好,执行更快),若环境不支持微任务,则降级为宏任务。Vue 源码中对异步任务的检测顺序如下(优先级从高到低):
Promise.then():浏览器环境首选,微任务,兼容性好。MutationObserver:浏览器环境备选,微任务(通过监听一个空 DOM 节点的变化触发回调)。setImmediate:Node.js 环境首选,宏任务(比setTimeout快)。setTimeout(fn, 0):兜底方案,宏任务(所有环境都支持,但延迟可能略高)。
源码核心逻辑简化如下:
javascript
运行
let microTimerFunc; // 微任务函数
let macroTimerFunc; // 宏任务函数
// 检测并定义微任务
if (typeof Promise !== 'undefined') {
microTimerFunc = () => {
Promise.resolve().then(flushCallbacks); // flushCallbacks 执行所有回调
};
} else if (typeof MutationObserver !== 'undefined') {
// 创建一个空节点,通过监听其变化触发回调
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode('1');
observer.observe(textNode, { characterData: true });
microTimerFunc = () => {
textNode.data = '2'; // 改变节点内容,触发 observer 回调
};
}
// 检测并定义宏任务(Node.js 环境)
if (typeof setImmediate !== 'undefined') {
macroTimerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// 兜底:使用 setTimeout
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
// 选择最终的异步任务函数(优先微任务)
const timerFunc = microTimerFunc || macroTimerFunc;
// nextTick 核心函数
function nextTick(cb) {
const callbacks = [];
callbacks.push(cb); // 将回调存入队列
// 标记是否正在等待执行,避免重复触发异步任务
if (!pending) {
pending = true;
timerFunc(); // 触发异步任务,等待执行 flushCallbacks
}
}
// 执行所有回调
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for (let i = 0; i < copies.length; i++) {
copies[i](); // 依次执行回调
}
}
3. 与 DOM 更新的配合
Vue 中,当数据变化时,会触发 “响应式更新”,并将 DOM 更新操作放入一个队列(queueWatcher),同样通过 nextTick 的异步机制延迟执行。因此:
- 数据变化 → 触发更新 → DOM 更新操作被放入异步队列(微任务)。
nextTick的回调也被放入同一个异步队列(或后续的微任务)。
最终,DOM 更新操作会先于 nextTick 回调执行,确保回调能获取最新的 DOM 状态。
三、关键结论
nextTick利用 JavaScript 事件循环的异步任务机制,确保回调在 DOM 更新后执行。- 优先使用微任务(
Promise.then),兼容性不足时降级为宏任务(setTimeout)。 - 核心目的是配合 Vue 的异步 DOM 更新策略,解决 “数据更新后立即操作 DOM 无效” 的问题。
理解 nextTick 的原理,有助于更合理地在 Vue 中处理 DOM 操作时机,避免因异步更新导致的逻辑错误。
26、虚拟滚动的实现
虚拟滚动(Virtual Scrolling)是一种高效处理大量数据列表(如万级以上数据)的技术,核心思想是只渲染可视区域内的 DOM 元素,而非全部数据,从而大幅减少 DOM 节点数量,提升页面性能和流畅度。
一、核心原理
虚拟滚动的核心逻辑基于以下几点:
- 可视区域计算:确定当前用户能看到的列表范围(通过容器高度、滚动位置计算)。
- 数据截取:从全部数据中,只提取可视区域内及前后少量 “缓冲” 数据(避免快速滚动时出现空白)。
- DOM 复用:通过定位(
position: absolute)动态更新可视区域内的 DOM 元素内容和位置,复用有限的 DOM 节点,而非频繁创建 / 删除。 - 滚动同步:通过一个 “占位元素” 模拟整个列表的高度,确保滚动条正常显示;同时根据滚动位置调整可视元素的偏移量(
transform或top),使其与滚动位置对齐。
二、实现步骤(简化版)
以下是一个基础虚拟滚动组件的实现思路,假设列表项高度固定(简化计算,动态高度需额外处理):
1. 基本结构
html
预览
<!-- 容器:限制可视区域,overflow: auto 产生滚动条 -->
<div class="virtual-list-container" ref="container">
<!-- 占位元素:高度 = 总数据量 × 单条高度,用于撑开容器,显示正确滚动条 -->
<div class="placeholder" :style="{ height: totalHeight + 'px' }"></div>
<!-- 可视区域内容:绝对定位,通过 transform 调整位置 -->
<div class="visible-content" ref="content"></div>
</div>
2. 核心参数定义
javascript
运行
data() {
return {
allData: [], // 全部数据(如10万条)
itemHeight: 50, // 每条数据的固定高度(px)
visibleCount: 0, // 可视区域能显示的条数
bufferCount: 5, // 可视区域外的缓冲条数(避免快速滚动留白)
startIndex: 0, // 当前渲染的起始索引
endIndex: 0, // 当前渲染的结束索引
scrollTop: 0, // 容器滚动距离
};
},
computed: {
// 总高度 = 数据总量 × 单条高度(用于占位元素)
totalHeight() {
return this.allData.length * this.itemHeight;
},
// 可视区域内需要渲染的数据(包含缓冲)
visibleData() {
return this.allData.slice(this.startIndex, this.endIndex);
},
},
3. 初始化与滚动监听
javascript
运行
mounted() {
const container = this.$refs.container;
// 计算可视区域能显示的条数(容器高度 / 单条高度,向上取整)
this.visibleCount = Math.ceil(container.clientHeight / this.itemHeight);
// 初始化渲染范围(包含缓冲)
this.updateVisibleRange();
// 监听滚动事件,实时更新渲染范围
container.addEventListener('scroll', this.handleScroll);
},
methods: {
handleScroll(e) {
this.scrollTop = e.target.scrollTop;
this.updateVisibleRange(); // 滚动时更新渲染范围
},
// 更新可视区域的渲染范围
updateVisibleRange() {
// 计算当前滚动位置对应的起始索引(滚动距离 / 单条高度,向下取整)
const start = Math.floor(this.scrollTop / this.itemHeight);
// 起始索引减去缓冲条数(避免顶部留白)
this.startIndex = Math.max(0, start - this.bufferCount);
// 结束索引 = 起始索引 + 可视条数 + 2×缓冲条数(上下都留缓冲)
this.endIndex = this.startIndex + this.visibleCount + 2 * this.bufferCount;
// 防止结束索引超出数据总量
this.endIndex = Math.min(this.endIndex, this.allData.length);
// 调整可视内容的偏移量(让内容对齐滚动位置)
this.updateContentPosition();
},
// 更新可视内容的位置(关键)
updateContentPosition() {
const content = this.$refs.content;
// 计算偏移量:起始索引对应的顶部距离(减去滚动距离,实现视觉对齐)
const offset = this.startIndex * this.itemHeight - this.scrollTop;
content.style.transform = `translateY(${offset}px)`;
},
},
4. 渲染可视数据
html
预览
<!-- 在 visible-content 中渲染可视数据 -->
<div class="visible-content" ref="content">
<div
class="list-item"
v-for="(item, index) in visibleData"
:key="index"
:style="{ height: itemHeight + 'px' }"
>
{{ item }}
</div>
</div>
三、处理动态高度(进阶)
如果列表项高度不固定(如内容长度可变),需额外处理:
- 预估高度:初始化时给每条数据一个预估高度(如平均高度),用于计算总高度和渲染范围。
- 实际高度缓存:当列表项首次渲染后,通过
getBoundingClientRect()获取实际高度并缓存。 - 动态调整:滚动时,若发现缓存的实际高度与预估高度不一致,重新计算总高度和渲染范围,避免滚动偏移。
核心思路是用 “预估 + 缓存” 弥补动态高度的不确定性,典型库(如 vue-virtual-scroller)均采用此方案。
四、优化点
- DOM 复用:避免每次滚动都销毁 / 创建 DOM,可通过固定数量的 “槽位” 动态更新内容(类似 React 的
react-window)。 - 节流处理:滚动事件触发频繁,可通过
requestAnimationFrame或节流函数优化性能。 - 懒加载:结合数据懒加载(如分页加载后端数据),避免一次性加载全部数据导致的内存占用过高。
- 滚动锚定:当数据更新(如删除 / 新增项)时,通过计算偏移量保持滚动位置稳定。
五、常用库
手动实现虚拟滚动需处理大量边界情况(如动态高度、快速滚动、数据更新),实际开发中建议使用成熟库:
- Vue:
vue-virtual-scroller、vue3-virtual-list - React:
react-window、react-virtualized - 通用:
virtua(轻量级,支持多框架)
总结
虚拟滚动的核心是 “按需渲染”,通过计算可视区域、截取数据、动态定位 DOM,解决大量数据列表的性能问题。固定高度场景实现简单,动态高度需结合预估与缓存机制。实际开发中,优先使用成熟库,避免重复造轮子。
27、vue2和3的虚拟dom有啥区别
Vue 2 和 Vue 3 的虚拟 DOM(Virtual DOM)在设计和实现上有显著差异,这些差异直接影响了性能和开发体验。以下是核心区别:
1. 虚拟 DOM 结构优化
- Vue 2:虚拟 DOM 节点(VNode)是一个包含大量属性的普通 JavaScript 对象,例如
tag、text、data、children、elm(真实 DOM 引用)、context(上下文)等,结构相对冗余。 - Vue 3:VNode 结构更精简,移除了不必要的属性(如
context、isStatic等),并通过 扁平化属性 减少内存占用。例如,将data中的class、style等常用属性直接提升到 VNode 顶层,减少属性访问层级。
2. 静态节点处理
- Vue 2:所有节点(包括静态节点,如纯文本、无动态绑定的元素)都会被创建为 VNode 并参与 diff 过程,即使它们不会发生变化,也会消耗性能。
- Vue 3:通过 编译时优化,识别静态节点并标记为
StaticVNode,这类节点在初始化时创建一次,后续更新中 跳过 diff 过程,直接复用。同时,相邻的静态节点会被合并为一个 “静态片段”,进一步减少 diff 开销。
3. Diff 算法改进
-
Vue 2:采用 双端比较算法(从新旧节点的首尾两端开始对比),但对于列表渲染(
v-for),若节点没有唯一key,可能导致大量不必要的节点移动或重建。此外,Diff 过程是 全量递归对比,即使父节点无变化,也会递归对比所有子节点。 -
Vue 3:
- 引入 静态标记(PatchFlag) :编译时为动态节点添加标记(如
TEXT、CLASS、PROPS等),标记该节点哪些部分可能变化。Diff 时只关注带标记的节点,且仅更新标记对应的属性,跳过静态内容。 - 列表 diff 优化:基于
key的对比更高效,减少节点移动次数;对于长列表,通过 块级结构(Block Tree) 将节点分组,只对比同一组内的动态节点,避免全量递归。
- 引入 静态标记(PatchFlag) :编译时为动态节点添加标记(如
4. 编译时与运行时结合
- Vue 2:虚拟 DOM 的 diff 主要依赖 运行时逻辑,编译阶段对虚拟 DOM 的优化较少,动态节点的判断完全在运行时进行。
- Vue 3:将 编译时优化 与虚拟 DOM 深度结合。编译器通过分析模板,提前确定动态节点的位置和变化类型,并生成带有优化信息(如 PatchFlag、静态节点标记)的渲染函数。运行时的虚拟 DOM diff 会利用这些信息,大幅减少计算量,实现 “按需更新”。
5. 性能表现
- Vue 2:由于全量 diff 和冗余的 VNode 结构,在大型应用或频繁更新场景下,性能开销相对较高。
- Vue 3:通过精简 VNode、静态节点跳过 diff、PatchFlag 精准更新等优化,虚拟 DOM 的更新性能提升约 50% (官方测试数据),尤其在动态内容较少的场景下优势更明显。
总结
Vue 3 的虚拟 DOM 并非 “重写”,而是通过 编译时优化驱动运行时效率提升,核心思路是 “减少不必要的计算”:通过静态分析标记动态内容,让 diff 过程只关注变化的部分。这种 “编译时 + 运行时” 的协同优化,是 Vue 3 性能提升的关键原因之一。
核心区别对比表
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| Diff 算法 | 全面递归 Diff,忽略静态节点 | Block Tree + Patch Flags,动静分离,靶向更新 |
| 静态节点提升 | 无 | 有,在编译阶段将静态节点提升到渲染函数之外 |
| Fragment 支持 | 不支持,组件必须单个根节点 | 支持,组件可以有多个根节点 |
| 事件缓存 | 无 | 有,将动态事件处理器缓存起来,避免不必要的更新 |
| VNode 结构 | 包含所有属性,结构较“重” | 结构更“扁平”,根据 Patch Flags 按需包含动态属性 |
详细解析
1. Diff 算法优化:从“全面排查”到“靶向更新”
Vue 2 的 Diff 策略:
Vue 2 在数据变化后,会对新旧虚拟 DOM 树进行全面的递归比较。即使某个节点及其子节点完全是静态的(永远不会改变),在每次重渲染时,Diff 算法仍然会去检查它。这在大型应用中会造成不必要的性能开销。
Vue 3 的 Diff 策略:Block Tree 和 Patch Flags
Vue 3 引入了革命性的 Block Tree 和 Patch Flags 概念,实现了“动静分离”。
-
Patch Flags(补丁标志) :
- 在编译阶段,Vue 3 的编译器会分析模板,对动态节点(绑定了响应式数据的节点)做标记。
- 这个标记就是一个数字(Patch Flag),例如
1代表只有文本内容是动态的,8代表只有props是动态的。 - 在运行时,Diff 算法看到这个标志,就能直接知道这个节点需要更新哪部分内容,而无需再对比整个节点。
28、http1升级到2需要做哪些
将 HTTP/1 升级到 HTTP/2 涉及协议特性、服务器配置、客户端兼容性等多方面调整,核心目标是利用 HTTP/2 的多路 iplexing(多路复用)、二进制帧、头部压缩等特性提升性能。以下是关键步骤和注意事项:
1. 确认基础依赖与兼容性
- 服务器支持:确保服务器软件支持 HTTP/2。主流服务器(如 Nginx 1.9.5+、Apache 2.4.17+、IIS 10+、Caddy 等)已原生支持 HTTP/2,但需启用相关模块。
- 客户端兼容性:HTTP/2 需客户端(浏览器、APP)支持,现代浏览器(Chrome、Firefox、Safari 等)均已支持,但需通过 HTTPS 传输(大部分浏览器仅允许 HTTPS 下使用 HTTP/2,少数支持明文
h2c但不推荐)。 - TLS 版本:HTTP/2 通常依赖 TLS 1.2+,需确保服务器配置了符合要求的 TLS 版本(禁用不安全的 TLS 1.0/1.1)。
2. 启用 HTTPS(强制要求)
- HTTP/2 几乎所有场景下都依赖 HTTPS(因浏览器安全限制),需先为域名配置 SSL 证书(可使用 Let's Encrypt 等免费证书)。
- 配置 TLS 优化:启用 ALPN(应用层协议协商),让客户端与服务器在 TLS 握手时协商使用 HTTP/2(ALPN 是 HTTP/2 标准协商方式,优于 HTTP/1 的 NPN)。
3. 服务器配置调整
以 Nginx 为例:
-
确保编译时包含
--with-http_v2_module模块(可通过nginx -V检查)。 -
在
server块中启用 HTTP/2:nginx
server { listen 443 ssl http2; # 关键:在 443 端口同时启用 ssl 和 http2 server_name example.com; # SSL 证书配置 ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; # 启用 ALPN(自动协商 HTTP/2) ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; # 可选:配置 HTTP/2 相关参数(如最大并发流) http2_max_concurrent_streams 100; # 限制并发流数量,默认 128 }
以 Apache 为例:
-
启用
mod_http2模块:a2enmod http2。 -
在虚拟主机配置中添加:
apache
<VirtualHost *:443> ServerName example.com Protocols h2 http/1.1 # 优先使用 h2(HTTP/2),降级兼容 HTTP/1.1 # SSL 证书配置 SSLEngine on SSLCertificateFile /path/to/cert.pem SSLCertificateKeyFile /path/to/key.pem </VirtualHost>
4. 应用层适配与优化
HTTP/2 的特性可能改变传统 HTTP/1 的优化思路,需调整应用策略:
-
废除 “域名分片” :HTTP/1 因队头阻塞(head-of-line blocking),常通过多域名(如
img1.example.com、img2.example.com)增加并发连接。但 HTTP/2 多路复用允许单连接并发传输多个请求,无需分片,反而多域名会增加 TLS 握手开销,应合并为单域名。 -
减少资源合并:HTTP/1 常合并 CSS/JS 减少请求数,但 HTTP/2 中细粒度资源(如单独的小 JS 文件)可通过多路复用并行传输,且利于缓存(修改单个文件不影响整体缓存)。可适当拆分大资源,保留必要的合并(如基础库)。
-
利用服务器推送(Server Push) :HTTP/2 允许服务器主动推送关联资源(如 HTML 引用的 CSS/JS),无需等待客户端请求。但需谨慎使用(避免推送冗余资源浪费带宽),可通过
Link头配置:http
Link: </style.css>; rel=preload; as=style # 告知服务器推送 style.css -
优化头部传输:HTTP/2 使用 HPACK 算法压缩请求头,减少冗余(如重复的
Host、User-Agent)。应用层无需额外操作,但需避免自定义头过度膨胀(HPACK 对高频重复头压缩效率更高)。
5. 测试与监控
-
验证 HTTP/2 启用:
- 浏览器开发者工具(Network 面板,Protocol 列显示
h2)。 - 命令行工具:
curl -I --http2 https://example.com(返回HTTP/2 200即成功)。 - 在线工具:HTTP/2 Test。
- 浏览器开发者工具(Network 面板,Protocol 列显示
-
性能监控:关注关键指标:页面加载时间、请求并发数、TLS 握手耗时。HTTP/2 通常在多资源并行加载场景下提升明显(如图片多、JS/CSS 多的页面)。
-
兼容性降级:确保对不支持 HTTP/2 的客户端(如老旧浏览器)自动降级为 HTTP/1.1,服务器配置中需保留
http/1.1作为备选(如 Nginx 的listen无需单独配置,Apache 的Protocols h2 http/1.1)。
6. 注意事项
- 避免混合内容:HTTP/2 页面中若引用 HTTP 资源,浏览器可能阻止加载,需确保所有资源(图片、JS、CSS 等)均为 HTTPS。
- 代理层支持:若使用 CDN 或反向代理(如 Cloudflare、NGINX 反向代理),需确保代理层也支持 HTTP/2,否则可能在代理与后端服务器间降级为 HTTP/1。
- 长连接优化:HTTP/2 依赖长连接,需配置合理的连接超时时间(如 Nginx 的
keepalive_timeout),避免频繁重建连接。
总结
升级 HTTP/2 的核心步骤是:配置 HTTPS + 启用服务器 HTTP/2 模块 + 调整应用层优化策略。无需修改业务代码,但需根据 HTTP/2 特性优化资源加载方式,最终实现减少连接开销、提升并行传输效率的目标。