vue基底石

126 阅读14分钟

一、 Vue的基本原理

基本原理(响应式原理)

vue通过Object.defineProperty进行数据劫持,遍历data里的属性,将其转化为getter和setter,并通过依赖收集(watcher→dep)和派发更新(dep→watcher)实现数据驱动视图。

vue3是Proxy

进一步理解说明

  1. 技术手段:Object.defineProperty(Vue 2)。
  2. 核心操作:定义getter和setter。
  3. 关键机制:在getter中收集依赖(Dep ← Watcher),在setter中派发更新(Dep → Watcher)。 (每个属性都有一个dep容器,dep收集watcher,watcher是每一个和属性关联的依赖方-组件、computed、watch)
  4. 最终目标:实现数据驱动视图的自动更新。

深度理解:

通常被称为 “发布-订阅”模式 或 “依赖追踪”模型。核心思想可以概括为三句话:

最核心、最通俗的解释:

  1. 「劫持」数据: Vue 会把你写在 data 里的所有属性,用 Object.defineProperty(Vue 2)或 Proxy(Vue 3)「加工」一遍,给每个属性装上 getter 和 setter。从此,你读或写这些属性,Vue 都能知道。
  2. 「收集」依赖: 当你读取属性时(比如在模板里用了 {{ message }}),getter 会触发,Vue 就会把这个「使用数据的地方」(比如当前组件的渲染函数)记下来,当做这个属性的一个「依赖」(订阅者)。简单说:谁用了我这个数据,我就把谁记在小本本上。
  3. 「通知」更新: 当你修改属性时(比如 this.message = 'new'),setter 会触发,Vue 就会去翻出第二步记的「小本本」,通知所有「依赖」这个数据的地方:“我变了!你们该更新了!”。然后,页面就会自动重新渲染。简单说:数据一变,就通知所有用到它的地方更新。

二、Vue的双向绑定

实现原理

数据→视图:由响应式原理实现;

视图->数据:通过标准的事件监听机制实现的。

(双向绑定是一个语法糖,它描述了数据和视图之间的双向关系; v-model 并不是什么魔法,它本质上是一个语法糖,是v-bind(数据绑定)和v-on:input(事件监听)的结合体。

image.png

三、 vue2与vue3的区别

image.png 补充下:

  • 以上静态提升去除,它属于编译时优化。
  • 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 体积,提升了首屏加载速度。

4. 指令优化(自定义渲染器与性能 API):

  • 优化点:  Vue 3 将核心逻辑与渲染器分离,提供了更底层的优化能力。

  • v-once 与 v-memo:

    • v-once:用于渲染一次后不再更新的静态内容。
    • v-memo(Vue 3.2+):这是一个更强大的优化工具,允许开发者通过数组依赖来控制子树的更新,在大型列表渲染或复杂条件渲染中,可以跳过大量的虚拟 DOM 比对。

总结:

1772082260113.png

五、diff算法

Vue2 使用双端比较算法:

  1. 同层比较:只比较同一层级,不跨级比较
  2. 双指针:在新旧两组子节点的首尾各有两个指针
  3. 四种比较:不断进行首-首、尾-尾、首-尾、尾-首的比较
  4. 命中则移动:命中一种比较就移动对应的指针,并更新真实DOM
  5. key 的作用:通过 key 可以更精确地判断是否是相同节点
// 伪代码思路
while(旧首 <= 旧尾 && 新首 <= 新尾) {
  if(旧首 === 新首) {
    // 处理...
  } else if(旧尾 === 新尾) {
    // 处理...
  } else if(旧首 === 新尾) {
    // 处理...
  } else if(旧尾 === 新首) {
    // 处理...
  } else {
    // 根据 key 查找
  }
}

Vue3 使用最长递增子序列

  1. 同层比较:同样只比较同一层级

  2. 前置处理:从前往后对比,遇到不同就停止

  3. 后置处理:从后往前对比,遇到不同就停止

  4. 剩余处理

    • 如果新节点多:创建添加
    • 如果旧节点多:删除移除
  5. 乱序处理:使用最长递增子序列算法,通过数组记录新节点位置,找出不需要移动的节点,最大程度复用

// 伪代码思路
// 1. 从左向右比对
while(旧节点[i] === 新节点[i]) { i++ }
// 2. 从右向左比对  
while(旧节点[e1] === 新节点[e2]) { e1--; e2-- }
// 3. 处理新增/删除
// 4. 处理乱序(核心优化)
生成新节点位置数组
找出最长递增子序列
移动其他节点

举例:最长递增子序列

image.png

核心差异对比

image.png 备注:vue2的静态节点-每次渲染都会被重新创建,但是不会重新比对

最长递增子序列:

实际是可以这么理解,通过递增子序列找到已经可复用的稳定节点,以此为参照物,减少其他节点的移动次数

    1. “已可复用的稳定节点” 这个表述很好——这些节点不仅在旧列表中存在(可复用),而且在旧列表中的相对顺序已经符合新列表的要求,所以它们不需要移动,可以作为锚点。
    1. “以此为参照物” 正是Vue的做法:最长递增子序列中的节点保持不动,只移动其他节点,并且移动时参考这些稳定节点的位置。
    1. “减少其他节点的移动次数” 是最终目的——通过最大化不动节点的数量,让每个需要移动的节点只移动一次就能到位,达到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

image.png

一句话总结

Vue 2Vue 3
Tree Shaking 只能救第三方库,救不了 Vue 本身Tree Shaking 从架构层面拯救了 Vue 本身

九、Webpack、Rollup差异化

image.png

在 Vue 项目中如何选择

✅ 选 Webpack 的场景

  • 开发完整的 Vue 单页应用
  • 需要处理大量静态资源(图片、字体、CSS)
  • 需要代码分割、按需加载优化性能
  • 需要使用 HMR 提升开发体验
  • 项目复杂度高,需要丰富的插件生态

✅ 选 Rollup 的场景

  • 开发 Vue 组件库并发布到 NPM
  • 封装工具函数库
  • 追求极致的打包体积和运行性能
  • 需要输出多种模块格式(ESM、CJS、UMD)
  • 项目相对简单,不需要复杂的资源处理

十、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差异化

对比维度ViteVue 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 插件的遗留项目。