该面试题只是为了记录我自己的面试笔记,大多数摘自行内有关大佬总结,本人只是搬运工,有关链接已放置相关笔记的下面
compute 实现原理
computed 本质是一个惰性求值的观察者。
computed 内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。
其内部通过 this.dirty 属性标记计算属性是否需要重新求值。
当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,
computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 重新渲染,本质上是一种优化。)
没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后 其他地方需要读取属性 的时候,它才会真正计算,即具备 lazy(懒计算)特性。比如这个属性并没有被其他地方用到,就不会重新去计算它)
watch 的deep 机制
watch 本身懒监听,加上deep 属性会深度监听,一种是userDep,一种是getter/setter
setup 有关
- 在执行setup函数时,还没执行created生命周期方法,因此在setup函数中,无法使用data和methods的变量和方法。
- 不能在setup函数中使用data和methods,在setup中使用this是无法操作的,因为setup 在生命周期beforeCreate和created之间执行,调用发生在 data property、computed property 或 methods 被解析之前,此时组件实例未实例化成功,所以data,methods等东西无法在 setup 中使用this被获取。
V-if 和 v-show 区别
- V-if 是对dom增删操作,对性能消耗较高
- V-show 是对css 操作,控制display 属性
vue路由模式hash和history,hash解析
Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。
- hash模式
简介: hash模式是开发中默认的模式,它的URL带着一个#,例如:www.abc.com/#/vue,它的hash值就是#/vue。
特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
原理: hash模式的主要原理就是onhashchange()事件:
ini
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
}
使用onhashchange()事件的好处就是,在页面的hash值发生变化时,无需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的hash值和对应的URL关联起来了。
- history模式
简介: history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。
特点: 当使用history模式时,URL就像这样:abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404。
API: history api可以分为两大部分,切换历史状态和修改历史状态:
● 修改历史状态:包括了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。
● 切换历史状态: 包括forward()、back()、go()三个方法,对应浏览器的前进,后退,跳转操作。
虽然history模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。
如果想要切换到history模式,就要进行以下配置(后端也要进行配置):
arduino
const router = new VueRouter({
mode: 'history',
routes: [...]
})
- 两种模式对比
调用 history.pushState() 相比于直接修改 hash,存在以下优势:
● pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
● pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
● pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
● pushState() 可额外设置 title 属性供后续使用。
● hash模式下,仅hash符号之前的url会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回404错误;history模式下,前端的url必须和实际向后端发起请求的url一致,如果没有对用的路由处理,将返回404错误。
hash模式和history模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。
vue3 静态提升
PatchFlags
ini
export const enum PatchFlags {
TEXT = 1,// 动态的文本节点
CLASS = 1 << 1, // 2 动态的 class
STYLE = 1 << 2, // 4 动态的 style
PROPS = 1 << 3, // 8 动态属性,不包括类名和样式
FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 动态 solt
HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff
BAIL = -2 // 一个特殊的标志,指代差异算法
}
Vue3
中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用
这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
css
<span>你好</span>
<div>{{ message }}</div>
没有做静态提升之前
php
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("span", null, "你好"),
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
做了静态提升之后
php
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
], 64 /* STABLE_FRAGMENT */))
}
// Check the console for the AST
静态内容_hoisted_1
被放置在render
函数外,每次渲染的时候只要取 _hoisted_1
即可
同时 _hoisted_1
被打上了 PatchFlag
,静态标记值为 -1 ,特殊标志是负整数表示永远不会用于 Diff
Vue自定义指令
都是分全局指令和局部指令
全局定义:Vue.directive("focus",{})
局部定义:directives:{focus:{}}
vue2
钩子函数:指令定义对象提供钩子函数
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inSerted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前调用。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
- ComponentUpdate:指令所在组件的 VNode及其子VNode全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数
-
el
:指令所绑定的元素,可以用来直接操作DOM
-
binding
:一个对象,包含以下property
:name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
-
vnode
:Vue
编译生成的虚拟节点 -
oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用
除了 el 之外,其它参数都应该是只读的 ,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
ini
Vue.directive('throttle', {
bind: (el, binding) => {
let throttleTime = binding.value; // 节流时间
if (!throttleTime) { // 用户若不设置节流时间,则默认2s
throttleTime = 2000;
}
let cbFun;
el.addEventListener('click', event => {
if (!cbFun) { // 第一次执行
cbFun = setTimeout(() => {
cbFun = null;
}, throttleTime);
} else {
event && event.stopImmediatePropagation();
}
}, true);
},
});
vue3
七个钩子函数(在 Vue3 中,自定义指令的钩子函数主要有如下七种(这块跟 Vue2 差异较大))
- created:在绑定元素的 attribute 或事件监听器被应用之前调用。在指令需要附加在普通的 v-on 事件监听器调用前的事件监听器中时,这很有用。
- beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。
- mounted:在绑定元素的父组件被挂载后调用,大部分自定义指令都写在这里。
- beforeUpdate:在更新包含组件的 VNode 之前调用。
- updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。
- beforeUnmount:在卸载绑定元素的父组件之前调用
- unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。
虽然钩子函数比较多,看着有点唬人,不过我们日常开发中用的最多的其实是 mounted 函数。
四个参数
这里七个钩子函数,钩子函数中有回调参数,回调参数有四个,含义基本上和 Vue2 一致:
-
el:指令所绑定的元素,可以用来直接操作 DOM,我们松哥说想实现一个可以自动判断组件显示还是隐藏的指令,那么就可以通过 el 对象来操作 DOM 节点,进而实现组件的隐藏。
-
binding:我们通过自定义指令传递的各种参数,主要存在于这个对象中,该对象属性较多,如下属性是我们日常开发使用较多的几个:
- name:指令名,不包括 v- 前缀。
- value:指令的绑定值,例如:v-hasPermission="['user:delete']" 中,绑定值为 'user:delete',不过需要小伙伴们注意的是,这个绑定值可以是数组也可以是普通对象,关键是看你具体绑定的是什么,在 2.1 小节的案例中,我们的 value 就是一个数字。
-
expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
-
arg:传给指令的参数,可选。例如 v-hasPermission:[name]="'zhangsan'" 中,参数为 "name"。
-
vnode:Vue 编译生成的虚拟节点。
-
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的 ,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
ini
const app = createApp(App);
app.directive('onceClick',{
mounted(el, binding, vnode) {
el.addEventListener('click', () => {
if (!el.disabled) {
el.disabled = true;
let time = binding.value;
if (binding.arg == "s") {
time = time * 1000;
}
setTimeout(() => {
el.disabled = false;
}, time);
}
});
}
})
使用场景
普通DOM元素进行底层操作的时候,可以使用自定义指令
自定义指令是用来操作DOM的。尽管Vue推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的DOM操作,并且是可复用的。
使用案例
初级应用:
鼠标聚焦
下拉菜单
相对时间转换
滚动动画
高级应用:
自定义指令实现图片懒加载
自定义指令集成第三方插件
参考链接:手把手教你在 Vue3 中自定义指令_vue.js_肥肥技术宅-华为云开发者联盟
Pinia 相较于 vuex 的优点
Pinia 和 Vuex
Vuex : State
、Gettes
、Mutations
(同步)、Actions
(异步)
Pinia : State
、Gettes
、Actions
(同步异步都支持)
Vuex 当前最新版是 4.x
- Vuex4 用于 Vue3
- Vuex3 用于 Vue2
Pinia 当前最新版是 2.x
- 即支持 Vue2 也支持 Vue3
就目前而言 Pinia 比 Vuex 好太多了,解决了 Vuex 的很多问题,所以笔者也非常建议直接使用 Pinia,尤其是 TypeScript 的项目
Pinia 核心特性
-
Pinia 没有
Mutations
-
Actions
支持同步和异步 -
没有模块的嵌套结构
- Pinia 通过设计提供扁平结构,就是说每个 store 都是互相独立的,谁也不属于谁,也就是扁平化了,更好的代码分割且没有命名空间。当然你也可以通过在一个模块中导入另一个模块来隐式嵌套 store,甚至可以拥有 store 的循环依赖关系
-
更好的
TypeScript
支持- 不需要再创建自定义的复杂包装器来支持 TypeScript 所有内容都类型化,并且 API 的设计方式也尽可能的使用 TS 类型推断
-
不需要注入、导入函数、调用它们,享受自动补全,让我们开发更加方便
-
无需手动添加 store,它的模块默认情况下创建就自动注册的
-
Vue2 和 Vue3 都支持
- 除了初始化安装和SSR配置之外,两者使用上的API都是相同的
-
支持
Vue DevTools
- 跟踪 actions, mutations 的时间线
- 在使用了模块的组件中就可以观察到模块本身
- 支持 time-travel 更容易调试
- 在 Vue2 中 Pinia 会使用 Vuex 的所有接口,所以它俩不能一起使用
- 但是针对 Vue3 的调试工具支持还不够完美,比如还没有 time-travel 功能
-
模块热更新
- 无需重新加载页面就可以修改模块
- 热更新的时候会保持任何现有状态
-
支持使用插件扩展 Pinia 功能
-
支持服务端渲染
链接:上手 Vue 新的状态管理 Pinia,一篇文章就够了 - 掘金
vue3优化
-
监测机制的改变
- 3.0 将带来基于代理 Proxy的 observer 实现,提供全语言覆盖的反应性跟踪。
- 消除了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限制:
-
只能监测属性,不能监测对象
- 检测属性的添加和删除;
- 检测数组索引和长度的变更;
- 支持 Map、Set、WeakMap 和 WeakSet。
-
模板
- 作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而 3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。
- 同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
-
对象式的组件声明方式
- vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。
- 3.0 修改了组件的声明方式,改成了类式的写法,这样使得和 TypeScript 的结合变得很容易
-
其它方面的更改
- 支持自定义渲染器,从而使得 weex 可以通过自定义渲染器的方式来扩展,而不是直接 fork 源码来改的方式。
- 支持 Fragment(多个根节点)和 Protal(在 dom 其他部分渲染组建内容)组件,针对一些特殊的场景做了处理。
- 基于 tree shaking 优化,提供了更多的内置功能。
vue2可以使用vite吗
根目录创建vite.config.js,使用到require-context需处理兼容。
@vitejs/plugin-vue2
perl
pnpm i rollup-plugin-require-context generate-source-map
javascript
import { defineConfig, loadEnv } from "vite";
import { createVuePlugin } from "vite-plugin-vue2"; // 使用Vue2版本
import requireContext from "rollup-plugin-require-context"; // 处理兼容webpack工具require-context;
const path = require("path");
export default defineConfig(({ command, mode }) => {
let config = {};
const env = loadEnv(mode, process.cwd()); // 根据服务环境获取环境变量
// 情景模式配置
if (command === "serve") {
// dev 独有配置
config = {
base: "/",
};
} else {
// build 独有配置
config = {
base: env.VITE_APP_BASE, // 生产环境基础路径必须前后都带斜杠否则打包会出现警告提示
build: {
terserOptions: {
// 打包编译清除控制台输出及debugger
compress: {
drop_console: true,
drop_debugger: true,
},
},
outDir: env.VITE_APP_DIR,
assetsDir: env.VITE_APP_ASSETS,
rollupOptions: {
output: {
entryFileNames: `${env.VITE_APP_ASSETS}/js/entry[hash].js`, // [name]-[hash] 入口文件
chunkFileNames: `${env.VITE_APP_ASSETS}/js/chunk[hash].js`, // [name]-[hash] 共享文件
assetFileNames: `${env.VITE_APP_ASSETS}/assets/[hash][extname]`, // [name]-[hash] 静态资源
},
},
brotliSize: false, // 计算打包时间关闭,不进行该环节;提升bundle效率
},
};
}
return {
...config, // 合并开发生产环境配置
css: {
preprocessorOptions: {
scss: {
// 处理全局公共样式兼容问题
additionalData: `@import "./src/assets/css/_config.scss";
@import "./src/assets/css/bem.scss";
@import "./src/assets/css/mixins.scss";
@import "./src/assets/css/vars.scss";`,
},
},
},
resolve: {
extensions: [".js", ".vue", ".json", ".scss"], // 处理文件扩展名
alias: {
"@": path.resolve(__dirname, "src"), // 处理全局别名
},
},
plugins: [createVuePlugin(), requireContext()],
// 代理配置
server: {
port: 8888,
strictPort: true,
proxy: {
"/api": {
target: "http://127.0.0.1:8888",
changeOrigin: true,
secure: false,
ws: true,
},
},
},
};
});
参考链接:基于pnpm搭建Vite+Vue2+element-ui项目 - 掘金
保存当页面的状态
既然是要保持页面的状态(其实也就是组件的状态),那么会出现以下两种情况:
- 前组件会被卸载
- 前组件不会被卸载
那么可以按照这两种情况分别得到以下方法:
组件会被卸载:
-
将状态存储在LocalStorage / SessionStorage
-
只需要在组件即将被销毁的生命周期中在 LocalStorage / SessionStorage 中把当前组件的 state 通过 JSON.stringify() 储存下来就可以了。在这里面需要注意的是组件更新状态的时机。比如从 B 组件跳转到 A 组件的时候,A 组件需要更新自身的状态。但是如果从别的组件跳转到 B 组件的时候,实际上是希望 B 组件重新渲染的,也就是不要从 Storage 中读取信息。所以需要在 Storage 中的状态加入一个 flag 属性,用来控制 A 组件是否读取 Storage 中的状态。
-
优点
-
兼容性好,不需要额外库或工具。
-
简单快捷,基本可以满足大部分需求。
-
缺点
-
状态通过 JSON 方法储存(相当于深拷贝),如果状态中有特殊情况(比如 Date 对象、Regexp 对象等)的时候会得到字符串而不是原来的值。(具体参考用 JSON 深拷贝的缺点)
-
如果 B 组件后退或者下一页跳转并不是前组件,那么 flag 判断会失效,导致从其他页面进入 A 组件页面时 A 组件会重新读取 Storage,会造成很奇怪的现象
-
-
路由传值
-
通过 react-router 的 Link 组件的 prop ------ to 可以实现路由间传递参数的效果。
-
在这里需要用到 state 参数,在 B 组件中通过 history.location.state 就可以拿到 state 值,保存它。返回 A 组件时再次携带 state 达到路由状态保持的效果。
-
优点
-
简单快捷,不会污染 LocalStorage / SessionStorage。
-
可以传递 Date、RegExp 等特殊对象(不用担心 JSON.stringify / parse 的不足)
-
缺点
-
如果 A 组件可以跳转至多个组件,那么在每一个跳转组件内都要写相同的逻辑。
-
组件不会被卸载
- 单页面渲染
要切换的组件作为子组件全屏渲染,父组件中正常储存页面状态。
优点
- 代码量少
- 不需要考虑状态传递过程中的错误
缺点
- 增加 A 组件维护成本
- 需要传入额外的 prop 到 B 组件
- 无法利用路由定位页面
keep-alive
除此之外,在Vue中,还可以是用keep-alive来缓存页面,当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行
被包裹在keep-alive中的组件的状态将会被保留:
javascript
// main.js
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</kepp-alive>
// router.js
{
path: '/',
name: 'xxx',
component: ()=>import('../src/views/xxx.vue'),
meta:{
keepAlive: true // 需要被缓存
}
}
keep-alive LRU 算法
作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染
场景:tabs标签页 后台导航,vue性能优化
原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染
原理
- 获取 keep-alive 包裹着的第一个子组件对象及其组件名
- 根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例
- 根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键)
- 在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key)
- 最后组件实例的 keepAlive 属性设置为 true,这个在渲染和执行被包裹组件的钩子函数会用到,这里不细说
LRU 缓存淘汰算法
LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也更高"。
keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]
kotlin
export default {
name: "keep-alive",
abstract: true, // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中
props: {
include: patternTypes, // 被缓存组件
exclude: patternTypes, // 不被缓存组件
max: [String, Number] // 指定缓存大小
},
created() {
this.cache = Object.create(null); // 缓存
this.keys = []; // 缓存的VNode的键
},
destroyed() {
for (const key in this.cache) {
// 删除所有缓存
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted() {
// 监听缓存/不缓存组件
this.$watch("include", val => {
pruneCache(this, name => matches(val, name));
});
this.$watch("exclude", val => {
pruneCache(this, name => !matches(val, name));
});
},
render() {
// 获取第一个子元素的 vnode
const slot = this.$slots.default;
const vnode: VNode = getFirstComponentChild(slot);
const componentOptions: ?VNodeComponentOptions =
vnode && vnode.componentOptions;
if (componentOptions) {
// name不在inlcude中或者在exlude中 直接返回vnode
// check pattern
const name: ?string = getComponentName(componentOptions);
const { include, exclude } = this;
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode;
}
const { cache, keys } = this;
// 获取键,优先获取组件的name字段,否则是组件的tag
const key: ?string =
vnode.key == null
? // same constructor may get registered as different local components
// so cid alone is not enough (#3269)
componentOptions.Ctor.cid +
(componentOptions.tag ? ::${componentOptions.tag} : "")
: vnode.key;
// 命中缓存,直接从缓存拿vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
remove(keys, key);
keys.push(key);
}
// 不命中缓存,把 vnode 设置进缓存
else {
cache[key] = vnode;
keys.push(key);
// prune oldest entry
// 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode);
}
}
// keepAlive标记位
vnode.data.keepAlive = true;
}
return vnode || (slot && slot[0]);
}
};
传送门 ☞ # 全面讲解LRU算法