一、 Vue的基本原理
基本原理(响应式原理):
vue通过Object.defineProperty进行数据劫持,遍历data里的属性,将其转化为getter和setter,并通过依赖收集(watcher→dep)和派发更新(dep→watcher)实现数据驱动视图。
vue3是Proxy
进一步理解说明:
- 技术手段:Object.defineProperty(Vue 2)。
- 核心操作:定义getter和setter。
- 关键机制:在getter中收集依赖(Dep ← Watcher),在setter中派发更新(Dep → Watcher)。 (每个属性都有一个dep容器,dep收集watcher,watcher是每一个和属性关联的依赖方-组件、computed、watch)
- 最终目标:实现数据驱动视图的自动更新。
深度理解:
通常被称为 “发布-订阅”模式 或 “依赖追踪”模型。核心思想可以概括为三句话:
最核心、最通俗的解释:
- 「劫持」数据: Vue 会把你写在 data 里的所有属性,用 Object.defineProperty(Vue 2)或 Proxy(Vue 3)「加工」一遍,给每个属性装上 getter 和 setter。从此,你读或写这些属性,Vue 都能知道。
- 「收集」依赖: 当你读取属性时(比如在模板里用了 {{ message }}),getter 会触发,Vue 就会把这个「使用数据的地方」(比如当前组件的渲染函数)记下来,当做这个属性的一个「依赖」(订阅者)。简单说:谁用了我这个数据,我就把谁记在小本本上。
- 「通知」更新: 当你修改属性时(比如 this.message = 'new'),setter 会触发,Vue 就会去翻出第二步记的「小本本」,通知所有「依赖」这个数据的地方:“我变了!你们该更新了!”。然后,页面就会自动重新渲染。简单说:数据一变,就通知所有用到它的地方更新。
二、Vue的双向绑定
实现原理
数据→视图:由响应式原理实现;
视图->数据:通过标准的事件监听机制实现的。
(双向绑定是一个语法糖,它描述了数据和视图之间的双向关系; v-model 并不是什么魔法,它本质上是一个语法糖,是v-bind(数据绑定)和v-on:input(事件监听)的结合体。
三、 vue2与vue3的区别
补充下:
- 以上静态提升去除,它属于编译时优化。
- patchFlags为动态节点添加标记,diff算法在此数据上做对比
- 最长递增子序列:是在乱序中找到已经可复用的稳定节点做喵点,减少其他节点的移动次数
四、 Vue3.0性能优化
在Vue 3中,性能优化方面也有一些新的特性和改进。
1. 运行时优化
- 1)响应式系统重构(Proxy 机制):
-
优化点: Vue 2 使用
Object.defineProperty递归遍历对象属性,初始化负担重且无法监听属性新增/删除。 -
Vue 3: 使用 ES6 的
Proxy代理整个对象。- 懒响应式: 只有当你真正读取一个对象的属性时,才会对该属性进行响应式转换,消除了初始化时递归遍历的性能开销。
- 内存占用: 大幅减少了初始化时的内存占用。
-
- 2) Fragment(碎片节点):
- 优化点: Vue 2 要求组件必须有且只有一个根节点,导致许多无意义的包裹容器(例如多余的 div)。
- Vue 3: 组件支持多个根节点。
- 优势: 减少了 DOM 树的层级嵌套,降低了浏览器渲染引擎在绘制和布局时的计算成本(减少回流重绘的复杂度)。
2. 编译优化:
Vue 3对编译器进行了改进,引入了一些优化措施来提升编译的效率和性能。
-
1)静态提升:在Vue 2中,模板中的静态内容在每次重新渲染时都会被重新创建,这可能导致一些性能损失。而在Vue 3中,通过静态提升技术,编译器可以检测并优化静态内容,将其提升为静态的常量,减少了重新创建的开销。 (静态节点提升:编译器会检测模板中的静态节点,并将其提升为常量,避免在每次重新渲染时都创建新的VNode,减少了渲染开销。)
静态提升可以带来以下优势:
- 减少了虚拟DOM的创建和比对次数,提升了渲染性能。
- 通过减少不必要的DOM操作,降低了页面的回流和重绘成本。
-
2)Patch flag:编译器会根据模板的静态内容和动态内容的不同,为每个VNode节点添加一个Patch flag,用于在更新过程中进行更精细的操作,减少不必要的比对和渲染操作。
-
3)缓存事件处理函数:Vue 3中,编译器会将事件处理函数进行缓存,避免在每次重新渲染时都创建新的函数实例,提升了事件处理的性能。
-
4)静态节点预字符串化:
-
优化点: 当编译器遇到大量连续的静态节点时(例如一大段纯静态的HTML文本),Vue 3 会直接将这些节点序列化为一个字符串,并设置为
innerHTML。 -
优势: 这跳过了创建大量独立 VNode 的过程,减少了内存占用,并且直接将字符串扔给 innerHTML,比挨个创建 DOM 节点快得多。
-
3. 体积优化:
- Tree shaking:Vue 3的编译器支持会剔除所有未使用的 JavaScript 代码,包括 API、组件、指令、工具函数、第三方库模块等任何可以被静态分析的代码,从而减小打包体积,提升应用的加载性能
- 优化点: Vue 3 的核心库通过 ES Module 方式组织,并将很多内置功能(如
v-model指令、transition组件)设计成按需引入。 - 优势: 如果你没有使用
<transition>组件,最终打包的代码中就不会包含该组件的代码。这直接减少了打包后的 JS 体积,提升了首屏加载速度。
- 优化点: Vue 3 的核心库通过 ES Module 方式组织,并将很多内置功能(如
4. 指令优化(自定义渲染器与性能 API):
-
优化点: Vue 3 将核心逻辑与渲染器分离,提供了更底层的优化能力。
-
v-once 与 v-memo:
v-once:用于渲染一次后不再更新的静态内容。v-memo(Vue 3.2+):这是一个更强大的优化工具,允许开发者通过数组依赖来控制子树的更新,在大型列表渲染或复杂条件渲染中,可以跳过大量的虚拟 DOM 比对。
总结:
五、diff算法
Vue2 使用双端比较算法:
- 同层比较:只比较同一层级,不跨级比较
- 双指针:在新旧两组子节点的首尾各有两个指针
- 四种比较:不断进行首-首、尾-尾、首-尾、尾-首的比较
- 命中则移动:命中一种比较就移动对应的指针,并更新真实DOM
- key 的作用:通过 key 可以更精确地判断是否是相同节点
// 伪代码思路
while(旧首 <= 旧尾 && 新首 <= 新尾) {
if(旧首 === 新首) {
// 处理...
} else if(旧尾 === 新尾) {
// 处理...
} else if(旧首 === 新尾) {
// 处理...
} else if(旧尾 === 新首) {
// 处理...
} else {
// 根据 key 查找
}
}
Vue3 使用最长递增子序列:
-
同层比较:同样只比较同一层级
-
前置处理:从前往后对比,遇到不同就停止
-
后置处理:从后往前对比,遇到不同就停止
-
剩余处理:
- 如果新节点多:创建添加
- 如果旧节点多:删除移除
-
乱序处理:使用最长递增子序列算法,通过数组记录新节点位置,找出不需要移动的节点,最大程度复用
// 伪代码思路
// 1. 从左向右比对
while(旧节点[i] === 新节点[i]) { i++ }
// 2. 从右向左比对
while(旧节点[e1] === 新节点[e2]) { e1--; e2-- }
// 3. 处理新增/删除
// 4. 处理乱序(核心优化)
生成新节点位置数组
找出最长递增子序列
移动其他节点
举例:最长递增子序列
核心差异对比
备注:vue2的静态节点-每次渲染都会被重新创建,但是不会重新比对
最长递增子序列:
实际是可以这么理解,通过递增子序列找到已经可复用的稳定节点,以此为参照物,减少其他节点的移动次数
-
- “已可复用的稳定节点” 这个表述很好——这些节点不仅在旧列表中存在(可复用),而且在旧列表中的相对顺序已经符合新列表的要求,所以它们不需要移动,可以作为锚点。
-
- “以此为参照物” 正是Vue的做法:最长递增子序列中的节点保持不动,只移动其他节点,并且移动时参考这些稳定节点的位置。
-
- “减少其他节点的移动次数” 是最终目的——通过最大化不动节点的数量,让每个需要移动的节点只移动一次就能到位,达到DOM移动操作的最小化。
知识点补充:
-
序列生成案例
旧节点:[C, D] · C 在整个新列表 [A, B, D, C, E] 中的位置是 3 · D 在整个新列表中的位置是 2 · 所以生成的序列是 [3, 2]
-
稳定节点:
- 序列生成:是以旧列表为顺序,查找在新列表中的索引,以此序列生成最长递增子序列。
- 优先选择长度最长的;若多个等长,选择字典序最小(即尽可能让前面的值小)的那一个作为最终选项。这个最终选项中的每个索引,对应的旧节点就是稳定节点(锚点) 。 )
-
diff算法顺序:同序比较完,剩余节点以稳定节点(最长递增子序列)做为参照物进行移动,减少移动次数
-
流程图演示:
同序比较(头/尾) ↓ 剩余乱序节点 ↓ 生成索引序列(旧顺序 → 新位置) ↓ 计算最长递增子序列(LIS) ↓ LIS 中的节点 = 稳定节点(不动) ↓ 其他节点以稳定节点为参照物移动 ↓ DOM 移动次数最小化
-
六、vue-loader /core.js / babel
| 工具 | 主要工作 | 工作阶段 | 配置位置 | 执行顺序 | 依赖关系 |
|---|---|---|---|---|---|
| vue-loader | 解析 .vue 文件,分离 template/script/style,编译模板为 render 函数 | Webpack 打包时 | vue.config.js 中的 chainWebpack | 第 1 步 (最先执行) | 被 Webpack 调用,处理后把 script 交给 Babel |
| Babel | 转换现代 JS 语法(箭头函数、解构、async/await 等)为旧浏览器兼容语法 | Webpack 打包时 | babel.config.js | 第 2 步 (处理 vue-loader 传来的 JS) | 接收 vue-loader 的 script,按需引入 core-js |
| core-js | 提供新 API 的兼容实现(Promise、Map、Array.includes 等)--让浏览器可兼容 | 浏览器运行时 | 通过 Babel 的 useBuiltIns 和 corejs 选项配置 | 第 3 步 (最后运行) | 被 Babel 按需引入,打包进最终代码,在浏览器执行 |
一句话总结
- vue-loader:拆解 Vue 文件
- Babel:转换 JS 语法(语法)
- core-js:补充 JS 功能(提供api)
vue-loader 拆解 .vue 文件,Babel 转换 JS 语法,core-js 补充 JS 功能——三者分工明确,共同让 Vue 代码能在所有浏览器运行。
七、vue-loader / postcss-loader / css-loader / style-loader
| 工具 | 处理对象 | 执行顺序 |
|---|---|---|
| vue-loader | 解析 .vue 文件,提取 style 部分 | 第 1 步 |
| postcss-loader | 处理 CSS 代码(添加前缀-webkit-、转换单位px-em、压缩css、变量、嵌套等) ——转换常规css | 第 2 步 |
| css-loader | 转换为js模块,并处理 @import 、url() 依赖关系-为webpack能识别 | 第 3 步 |
| style-loader | 将 CSS 注入到 DOM | 第 4 步 |
八、Tree Shaking
一句话总结
| Vue 2 | Vue 3 |
|---|---|
| Tree Shaking 只能救第三方库,救不了 Vue 本身 | Tree Shaking 从架构层面拯救了 Vue 本身 |
九、Webpack、Rollup差异化
在 Vue 项目中如何选择
✅ 选 Webpack 的场景
✅ 选 Rollup 的场景
十、webpack一般会做哪些配置
const path = require('path')
module.exports = {
// 1. 公共路径(部署到非根目录时使用)
publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/',
outputDir: 'dist', // 2. 输出目录(打包后的文件夹名称)
assetsDir: 'static', // 3. 静态资源目录(存放图片、字体等)
productionSourceMap: false, // 4. 是否生成 source map(生产环境建议关闭)
devServer: { // 5. 开发服务器配置
port: 8080, // 端口号
open: true, // 启动时自动打开浏览器
proxy: { // 代理跨域请求
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
},
// 6. Webpack 配置(核心)
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'), // 设置 @ 指向 src
'@components': path.resolve(__dirname, 'src/components')
}
},
// 生产环境优化
optimization: {
splitChunks: {
chunks: 'all', // 代码分割
cacheGroups: {
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: 10
}
}
}
}
},
// 7. 链式 Webpack 配置(更细粒度的控制)
chainWebpack: config => {
// 设置 svg-sprite-loader
// 规则:排除 src/assets/icons 下的 svg 文件走默认的 url-loader
config.module
.rule('svg')
.exclude.add(path.resolve(__dirname, 'src/assets/icons'))
.end()
// 添加 svg-sprite-loader 处理指定目录的 svg
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(path.resolve(__dirname, 'src/assets/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]' // 设置 symbol ID
})
.end()
// 压缩图片(生产环境)
if (process.env.NODE_ENV === 'production') {
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true, quality: 65 },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.9], speed: 4 },
gifsicle: { interlaced: false }
})
.end()
}
},
// 8. CSS 相关配置
css: {
// 是否将 CSS 提取为独立文件(生产环境默认开启)
extract: process.env.NODE_ENV === 'production',
// 启用 CSS 源映射
sourceMap: false,
// CSS 模块化配置
modules: false,
// 预处理器配置
loaderOptions: {
sass: {
// 全局引入 SCSS 变量
additionalData: `@import "@/styles/variables.scss";`
}
}
}
}
注意点:全局变量引入、使用
// src/styles/variables.scss
$primary-color: #1890ff;
$font-size-base: 14px;
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin respond-to($breakpoint) {
@if $breakpoint == sm {
@media (min-width: $screen-sm) {
@content;
}
}
@else $breakpoint == md {
@media (min-width: $screen-md) {
@content;
}
}
}
// vue.config.js
const path = require('path');
module.exports = {
css: {
loaderOptions: {
scss: {
// 自动在每个 SCSS 文件开头注入全局变量
additionalData: `@import "@/styles/variables.scss";`
}
}
}
}
// 在组件内使用
<template>
<div class="demo-container">
<h1 class="title">全局变量使用示例</h1>
<div class="card">
<p>这是一段测试文本</p>
</div>
</div>
</template>
<script>
export default {
name: 'DemoComponent'
}
</script>
<style lang="scss" scoped>
// 注意:不需要再写 @import,variables.scss 已自动注入
.title {
color: $primary-color; // 使用颜色变量
font-size: $font-size-large; // 使用字体变量
margin-bottom: $spacing-md;
// 使用混合宏
@include ellipsis;
}
.card {
// 响应式布局
@include respond-to(md) {
padding: $spacing-lg;
}
@include respond-to(lg) {
padding: $spacing-xl;
}
}
</style>
十一、打包优化
1、代码分割
- 首屏优化:Element UI 和 ECharts 单独打包,如果首屏没用 ECharts,用户就不需要下载 800KB
- 缓存优化:Element UI 和 ECharts 版本稳定,可以长期缓存
- 加载策略:可以配合路由实现真正的按需加载
2、生产环境移除console
3、图片压缩\小图片转base64\雪碧图\图片懒加载
4、第三方库优化:CDN/按需引入
5、打包体积分析
6、Gzip 压缩(传输压缩)
十二、vue-cli、vite差异化
| 对比维度 | Vite | Vue CLI (基于 Webpack) |
|---|---|---|
| 核心原理 | 利用浏览器原生 ES Modules (ESM) 特性,在开发时不打包,直接按需编译请求的模块。 | 基于 Webpack 打包器,在开发时需要将整个项目打包成一个或多个 Bundle,再启动 dev server。 |
| 开发服务器启动速度 | 极快。无需打包,直接启动,尤其适合大型项目。 | 较慢。项目越大,启动等待时间越长。 |
| 热更新 (HMR) 速度 | 极快且不会随项目增大而明显变慢。因为只需替换被编辑的模块,浏览器立即更新。 | 随项目增大而显著变慢。修改代码后需要重新编译部分打包内容。 |
| 生产环境构建 | 使用 Rollup 进行打包,生产优化和 Tree-shaking 效果更好。 | 使用 Webpack 进行打包,配置更复杂,优化效果依赖配置。 |
| 配置复杂度 | 极简。开箱即用,预设了合理的默认配置(如支持 TS、CSS 预处理器)。 | 较复杂。虽然也有预设,但 Webpack 自身的配置概念(loader、plugin)更繁琐。 |
| 浏览器兼容性 | 默认需要支持 ESM 的现代浏览器(如 Chrome ≥61)。可通过插件 @vitejs/plugin-legacy 降级支持旧浏览器。 | 打包后的代码兼容性更好,默认支持 IE 11 及更早的浏览器(通过 Polyfill)。 |
| 生态与插件 | 插件体系较新,但发展迅速,Vue 官方已全面转向支持 Vite(如 create-vue)。 | 生态成熟,有大量 Webpack 的 loader 和 plugin 可用,但官方已不再推荐用于新项目。 |
| 使用场景建议 | 新项目首选,特别是 Vue 3 + TypeScript 项目,或需要极速开发体验的项目。 | 需要兼容 IE 11 或依赖大量旧 Webpack 插件的遗留项目。 |