【vue篇】Vue 性能优化神器:keep-alive 深度解析与实战指南

在 Vue 开发中,你是否遇到过:

"切换页面时,列表滚动位置丢失?"
"组件反复创建销毁,性能卡顿?"
"表单输入内容在路由切换后清空?"

keep-alive 就是解决这些问题的"银弹"。

它能缓存组件状态,避免重复渲染,大幅提升用户体验。

但你是否真正理解:

  • keep-alive 缓存的是什么?
  • 它是如何实现的?
  • LRU 策略如何工作?

本文将从 源码级 深度解析 keep-alive 的核心机制。


一、keep-alive 是什么?

✅ 核心定义

keep-alive 是 Vue 的内置抽象组件,用于:

缓存动态组件或路由视图的实例,避免重复销毁与重建

📌 基础用法

vue 复制代码
<!-- 缓存所有匹配的组件 -->
<keep-alive>
  <component :is="currentView"></component>
</keep-alive>

<!-- 或缓存路由视图 -->
<keep-alive>
  <router-view></router-view>
</keep-alive>

二、三大核心属性

属性 类型 说明
include `string RegExp
exclude `string RegExp
max `number string`

💡 实战示例

vue 复制代码
<keep-alive 
  include="UserInfo,OrderList"
  exclude="SearchResult"
  :max="10"
>
  <router-view />
</keep-alive>
  • ✅ 缓存 UserInfoOrderList
  • ❌ 不缓存 SearchResult
  • ⚠️ 最多缓存 10 个实例,超出时按 LRU 策略清除。

三、keep-alive 如何工作?源码级解析

🧩 核心数据结构

js 复制代码
// 初始化缓存
created() {
  this.cache = Object.create(null); // 存储 { key: vnode }
  this.keys = []; // 存储 key,实现 LRU
}
  • cache: 缓存对象,keyvnode 映射;
  • keys: key 数组,记录访问顺序,实现 LRU(最近最少使用) 策略。

🔁 render 函数核心流程

步骤 1:获取第一个子组件

js 复制代码
const slot = this.$slots.default;
const vnode = getFirstComponentChild(slot);

💡 keep-alive 只对第一个子组件生效。


步骤 2:判断是否需要缓存

js 复制代码
const name = getComponentName(vnode.componentOptions);

// 不在 include 或在 exclude 中,直接返回
if (
  (include && !matches(include, name)) ||
  (exclude && matches(exclude, name))
) {
  return vnode;
}

步骤 3:生成唯一 key

js 复制代码
const key = vnode.key == null
  ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
  : vnode.key;

❗ 为什么不用 cid 唯一标识?

因为同一个构造函数可以注册为多个本地组件cid 相同但组件不同。


步骤 4:缓存命中与 LRU 更新

js 复制代码
if (cache[key]) {
  // 命中缓存
  vnode.componentInstance = cache[key].componentInstance;
  
  // LRU:将 key 移到末尾(最新使用)
  remove(keys, key);
  keys.push(key);
} else {
  // 未命中,加入缓存
  cache[key] = vnode;
  keys.push(key);
  
  // 超出 max,清除最久未使用的
  if (max && keys.length > parseInt(max)) {
    pruneCacheEntry(cache, keys[0], keys);
  }
}

步骤 5:标记 keepAlive

js 复制代码
vnode.data.keepAlive = true;

这个标记告诉 Vue:跳过组件的 createdmounted 等生命周期


四、缓存的是什么?真相揭秘

✅ 正确答案

keep-alive 缓存的是:

组件的 vnode 实例 和 componentInstance(组件实例)

包括:

  • 组件的 DOM 结构;
  • 组件的 data 状态;
  • 事件监听器;
  • 子组件树。

❌ 常见误解

  • ❌ 不是只缓存 HTML 字符串;
  • ❌ 不是只缓存 data 对象;
  • ❌ 不是"冻结"组件。

五、生命周期钩子:activateddeactivated

当组件被 keep-alive 包裹时,会新增两个生命周期钩子:

钩子 触发时机
activated 组件被激活(从缓存中显示)时调用
deactivated 组件被缓存(切换离开)时调用

💡 实战应用

js 复制代码
export default {
  data() {
    return {
      timer: null
    };
  },
  activated() {
    // 页面可见时恢复轮询
    this.timer = setInterval(() => {
      console.log('轮询中...');
    }, 1000);
  },
  deactivated() {
    // 页面隐藏时清除轮询,避免内存泄漏
    clearInterval(this.timer);
  }
}

六、首次渲染 vs 缓存渲染

📌 首次渲染

js 复制代码
init(vnode) {
  // 初次渲染,componentInstance 为 undefined
  const child = createComponentInstanceForVnode(vnode);
  child.$mount();
}
  • 正常执行 beforeCreatecreatedmounted

📌 缓存渲染

js 复制代码
init(vnode) {
  if (vnode.componentInstance && vnode.data.keepAlive) {
    // 直接复用实例,执行 prepatch
    componentVNodeHooks.prepatch(vnode, vnode);
  }
}
  • 跳过 createdmounted
  • 直接将缓存的 DOM 插入文档;
  • 触发 activated 钩子。

七、LRU 缓存策略详解

📌 LRU(Least Recently Used)

"最近使用的,将来最可能再次使用。"

🔁 工作机制

操作 行为
新组件 插入 keys 数组末尾
命中缓存 将 key 从原位置移除,插入末尾
超出 max 移除 keys[0](最久未使用)

📊 示例:max=3

text 复制代码
操作: A → B → C → A → D
keys: [A] → [A,B] → [A,B,C] → [B,C,A] → [C,A,D]
  • D 加入时,B 被清除(最久未使用)。

八、最佳实践与避坑指南

✅ 最佳实践

  1. 合理设置 max:避免内存泄漏;
  2. deactivated 中清理资源:定时器、事件监听;
  3. 使用 include 精准控制:避免缓存重型组件。

❌ 常见错误

  • 忘记给组件命名(name 选项);
  • created 中启动轮询,未在 deactivated 中清除;
  • 缓存大量列表组件,导致内存飙升。

💡 结语

"keep-alive 是性能与体验的平衡艺术。"

要点 说明
缓存内容 vnode + 组件实例
核心机制 cache + keys + LRU
生命周期 activated / deactivated
适用场景 频繁切换的路由、表单、列表

掌握 keep-alive,你就能:

✅ 显著提升应用流畅度;

✅ 保留用户交互状态;

✅ 优化关键路径性能。

相关推荐
LuckySusu3 小时前
【vue篇】Vue 核心机制揭秘:为什么组件的 data 必须是函数?
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 响应式陷阱:动态添加对象属性为何不更新?如何破解?
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 异步更新之魂:$nextTick 原理与实战全解
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 条件渲染终极对决:v-if vs v-show 深度解析
前端·vue.js
LuckySusu3 小时前
【vue篇】单页 vs 多页:Vue 应用架构的终极对决
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 核心指令原理解析:v-if、v-show、v-html 的底层奥秘
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 进阶指南:如何在自定义组件中完美使用 v-model
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue v-model 深度解析:从表单到组件的双向绑定之谜
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 2 响应式系统:Object.defineProperty 的五大缺陷
前端·vue.js