
Vue面试题整理(一)
1. 谈谈你对Vue的理解?
官方: Vue 是一套用于构建用户界面的渐进式框架,Vue 的核心库只关注视图层
1.1 声明式框架
Vue 的核心特点,用起来简单。那我们就有必要知道命令式和声明式的区别!
- 早在JQ 的时代编写的代码都是命令式的,命令式框架重要特点就是关注过程
- 声明式框架更加关注结果
命令式的代码封装到了 Vuejs 中,过程靠 vuejs 来实现声明式代码更加简单,不需要关注实现,按照要求填代码就可以(给上原材料就出结果)
- 命令式编程:
let numbers = [1,2,3,4,5]
let total = 0
for(let i = 0; i < numbers.length; i++){
total += numbers[i] //关注了过程
}
console.log(total)
-声明式编程:
let total2 = numbers.reduce(function (memo,current) {
return memo + current
},0)
console.log(total2)
1.2 MVVM模式
说起MVVM
,就要知道另一个东东那就是 MVC
。为什么要有这些模式呢?
目的:职责划分、分层管理.
对于前端而言就是如何将数据同步到页面上,也是借鉴后端思想。
- MVC模式:
Backbone
+underscore
+jquery
对于前端而言,数据变化无法同步到视图中。需要将逻辑聚拢在
controller
层。 - MVVM 模式:映射关系的简化(隐藏
controller
)虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm(ViewModel的缩写) 这个变量名表示 Vue 实例。
1.3 采用虚拟DOM
传统更新页面,拼接一个完整的字符串 innerHTML
全部重新渲染,添加虚拟 DOM 后,可以比较新旧虚拟节点,找到变化在进行更新。虚拟 DOM 就是一个对象,用来描述真实 DOM 的
https://github.com/vuejs/vue/blob/main/src/core/vdom/vnode.ts
1.4 区分编译时(打包)和运行(浏览器)时
- Vue 的渲染核心就是调用渲染(
render
)方法将虚拟 DOM 渲染成真实 DOM(缺点就是虚拟 DOM 编写麻烦) - 专门写个编译时可以将模板编译成虚拟 DOM(在构建的时候进行编译性能更高,不需要再运行的时候进行编译)
1.5 组件化
实现高内聚、低耦合、单向数据流
- 组件化开发能大幅提高应用开发效率、测试性、复用性等
- 降低更新范围,只重新渲染变化的组件
2. 谈谈你对 SPA 的理解?
2.1 理解基本概念
- SPA(single-page application)单页应用,默认情况下我们编写 Vue、React都只有一个htm1 页面,并且提供一个挂载点,最终打包后会再此页面中引入对应的资源。(页面的渲染全部是由IS 动态进行渲染的)。切换页面时通过监听路由变化,渲染对应的页面 Clientside Rendering,客户端渲染
CSR
- MPA(Multi-page application)多页应用,多个html页面。每个页面必须重复加载,js,css 等相关资源。(服务端返回完整的 html,同时数据也可以再后端进行获取一并返回“模板引擎”)。多页应用跳转需要整页资源刷新。Server side Rendering,服务器端渲染
SSR
如何分清在哪渲染:HTML是在前端动态生成的“客户端渲染",在服务端处理好并返回的是“服务端渲染”。
2.2 优缺点
单页面应用(SPA) | 多页面应用(MPA) | |
---|---|---|
组成 | 一个主页面和页面组件 | 多个完整的页面 |
刷新方式 | 局部刷新 | 整页刷新 |
SEO 搜索引擎优化 | 无法实现 | 容易实现 |
页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
维护成本 | 相对容易 | 相对复杂 |
- 用户体验好、快,内容的改变不需要重新加载整个页面,服务端压力小。
- SPA 应用不利于搜索引擎的抓取。
- 首次渲染速度相对较慢(第一次返回空的 html,需要再次请求首屏数据)白屏时间长。
2.3 解决方案
- 静态页面预渲染(Static Site Generation)
SSG
,在构建时生成完整的 html 页面。(就是在打包的时候,先将页面放到浏览器中运行一下,将HTML保存起来),仅适合静态页面网站。变化率不高的网站 SSR
+CSR
的方式,首屏采用服务端染的方式,后续交互采用客户端染方式。例如:Nuxtjs
3. Vue 为什么需要虚拟 DOM?
3.1 基本概念
基本上所有框架都引入了虚拟 DOM 来对真实 DOM 进行抽象,也就是现在大家所熟知的 VNode 和 VDOM
- Virtual DOM 就是用 js 对象来描述真实 DOM,是对真实 DOM 的抽象,由于直接操作 DOM 性能低但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过 diff 算法比对差异进行更新 DOM(减少了对真实 DOM 的操作)。
- 虚拟 DOM 不依赖真实平台环境从而也可以实现跨平台。
3.2 VDOM 是如何生成的?
- 在 vue 中我们常常会为组件编写模板 -
template
- 这个模板会被编译器编译为渲染函数 -
render
- 在接下来的挂载过程中会调用
render
函数,返回的对象就是虚拟 dom。 - 会在后续的
patch
过程中进一步转化为 真实 dom。
3.3 VDOM 如何做 diff 的?
- 挂载过程结束后,会记录第一次生成的
VDOM-oldVnode
- 当响应式数据发生变化时,将会引起组件重新
render
,此时就会生成新的VDOM-newVnode
- 使用
oldVnode
与newnode
做diff
操作,将更改的部分应到真实 DOM 上,从而转换为最小量的 dom 操作,高效更新视图。
4. 谈一谈对 Vue 组件化的理解
webcomponent 组件化的核心组成:板、属性、事件、插、生命周期。
组件化好处: 高内聚、可重用、可组合
- 组件化开发能大幅提高应用开发效率、测试性、复用性等,
- 降低更新范围,只重新渲染变化的组件;
4.1 伴随组件化的其它问题
- Vue 中的每个组件都有一个渲染函数 watcher、effect。
- 数据是响应式的,数据变化后会执行watcher或者effect。
- 组件要合理的划分,如果不拆分组件,那更新的时候整个页面都要重新更新。
- 如果过分的拆分组件会导致watcher、effect产生过多也会造成性能浪费。
5. 既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
Vue 内部设计原因导致,Vue 设计的是每个组件一个 watcher (渲染 watcher),没有采用一个属性对应一个 watcher。这样会导致大量 watcher 的产生而且浪费内存,如果粒度过低也无法精准检测变化。所以采用 diff 算法+组件级 watcher。
6. 请说一下你对响应式数据的理解?
6.1 如何实现响应式数据
数组和对象类型当值变化时如何劫持到。对象内部通过 defineReactive
方法,使用object.defineProperty
将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。多层对象是通过递归来实现劫持。Vue3 则采用 proxy
6.2 Vue2 处理缺陷
- 在 Vue2 的时候使用
defineProperty
来进行数据的劫持,需要对属性进行重写添加getter
及setter
性能差。 - 当新增属性和删除属性时无法监控变化。需要通过
$set
、$delete
实现 - 数组不采用
defineProperty
来进行劫持(浪费性能,对所有索引进行劫持会造成性能浪费)需要对数组单独进行处理。 - 对于 ES6 中新产生的 Map、Set 这些数据结构不支持。
6.3 Vue2 与 Vue3 实现对比
function defineReactive(target,key,value{
observer(value);
Object.defineProperty(target,key,{
get(){
return value;
},
set(newvalue){
if(value !== newvalue){
value = newalue;
observer(newvalue)
}
}
})
}
function observer(data){
if(typeof data !=='object'){
return data
}
for(let key in data){
defineReactive(data,key,data[key]);
}
}
let handler ={
get(target, key){
if(typeof target[key]==="object"){
return new Proxy(target[key], handler);
}
return Reflect.get(target, key);
},
set(target, key,value){
let oldvalue = target[key];
if(oldvalue !== value){
return Reflect.set(target, key,value);
}
return true;
},
};
let proxy =new Proxy(obj, handler);
7. Vue 中如何检测数组变化?
7.1 实现数组劫持
- 数组考虑性能原因没有用
defineProperty
对数组的每一项进行拦截,而是选择重写数组(push
,shift
,pop
,splice
,unshift
,sort
,reverse
)方法。 - 数组中如果是对象数据类型也会进行递归劫持
7.2 数组的缺点
- 数组的索引和长度变化是无法监控到的
8. Vue 中如何进行依赖收集?
8.1 依赖收集的流程
- 每个属性都拥有自己的
dep
属性,存放他所依赖的watcher
,当属性变化后会通知自己对应的watcher
去更新 - 默认在初始化时会调用
render
函数,此时会触发属性依赖收集dep.depend
- 当属性发生修改时会触发
watcher
更新dep.notify()
8.2 Vue3 依赖收集
Vue3
中会通过Map
结构将属性和effect
映射起来。- 默认在初始化时会调用
render
函数,此时会触发属性依赖收集track
- 当属性发生修改时会找到对应的
effect
列表依次执行trigger
9. Vue.set方法是如何实现的
Vue 不允许在已经创建的实例上动态添加新的响应式属性
/**
* 为对象或数组设置一个新值,并触发响应式更新。
* @param {Object|Array} target - 要设置值的目标对象或数组。
* @param {string|number} key - 要设置值的属性名或数组索引。
* @param {*} val - 要设置的新值。
* @returns {*} - 返回设置的新值。
*/
function set(target, key, val) {
// 检查目标是否为 undefined、null 或原始值,如果是则发出警告
if (isUndef(target) || isPrimitive(target)) {
warn$2(
`Cannot set reactive property on undefined, null, or primitive value: ${target}`
);
}
// 检查目标是否为只读对象,如果是则发出警告并返回
if (isReadonly(target)) {
warn$2(`Set operation on key "${key}" failed: target is readonly.`);
return;
}
// 获取目标对象的观察者实例
const ob = target.__ob__;
// 如果目标是数组且索引有效
if (isArray(target) && isValidArrayIndex(key)) {
// 确保数组长度足够
target.length = Math.max(target.length, key);
// 使用 splice 方法替换指定位置的元素,触发数组的响应式更新
target.splice(key, 1, val);
// 服务端渲染(SSR)模拟场景下,数组方法可能未被劫持
if (ob && !ob.shallow && ob.mock) {
// 对新值进行响应式观察
observe(val, false, true);
}
return val;
}
// 如果 key 已经存在于目标对象中,且不是 Object.prototype 的属性
if (key in target && !(key in Object.prototype)) {
// 直接更新属性值
target[key] = val;
return val;
}
// 如果目标是 Vue 实例或根数据对象,发出警告
if (target._isVue || (ob && ob.vmCount)) {
warn$2(
"Avoid adding reactive properties to a Vue instance or its root $data " +
"at runtime - declare it upfront in the data option."
);
return val;
}
// 如果目标对象没有观察者实例,直接设置属性值
if (!ob) {
target[key] = val;
return val;
}
// 为目标对象的属性定义响应式属性
defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock);
{
// 通知依赖该属性的所有观察者,属性已添加
ob.dep.notify({
type: "add" /* TriggerOpTypes.ADD */,
target: target,
key,
newValue: val,
oldValue: undefined,
});
}
return val;
}
当我们选择新增属性时,可以考虑使用对象合并的方式实现
this.info = {...this.info,...{newProperty1:1,newProperty2:2 ...}}
10. Vue 中的 v-show 和 v-if 怎么理解?
10.1 基本概念
v-if
如果条件不成立不会渲染当前指令所在节点的 dom 元素v-show
只是切换当前 dom 的显示或者隐藏
10.2 如何选择
- v-if 可以阻断内部代码是否执行,如果条件不成立不会执行内部逻辑
- 如果页面逻辑在第一次加载的时候已经被确认后续不会频繁更改则采用 v-if
11. computed 和 watch 区别
Vue2 中有三种 watcher(渲染watcher、计算属性 watcher、用户 watcher)
Vue3 中有三种 effect(渲染effect、计算属性 effect、 用户 effect)
11.1 computed
- 计算属性仅当用户取值时才会执行对应的方法。
- computed 属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重新执行。
- 计算属性可以简化模板中复杂表达式。
- 计算属性中不支持异步透辑。
- computed 属性是可以再模板中使用的。
Vue2:
- 计算属性会创建一个计算属性 watche r,这个 watcher(lazy:true) 不会立刻执行
- 通过 object.defineProperty 将计算属性定义到实例上看 dirty 是否为 true ,如果为 true 则求值
- 当用户取值时会触发 getter,拿列计算属住对应的 watcher
- 并且让计算属性 watcher 中依赖的属性收集最外层的谊染 watcher ,可以做到依赖的属性变化了,触发计算属性更新 dirty 井且可以触发页面更新
- 如果依赖的值没有发生变化,则采用缓存
Vue3:
- 和 Vue2 不一样的是,这个计算属性会收集当前组件渲染产生的 effect
- 依赖的值变化后,会通知计算属性 effect 更新 dirty,并且计算属性会触发自己收集的渲染 effect 执行
11.2 watch
watch 则是监控值的变化,当值发生变化时调用对应的回调函数。经常用于监控某个值的变化,进行一些操作。(异步要注意克本问题)
vue3 提供了 onCleanup 的数,让用户更加方便使用也解决了消理问题。
12. 解释 ref和 reactive 区别?
ref
和 reactive
是 Vue3 数据响应式中非常重要的两个概念
reactive
用于处理对象类型的数据响应式。底层采用的是new Proxy0
ref
通常用于处理单值的响应式,ref主要解决原始值的响应式问题,底层采用的是Object.defineProperty()
实现的.
13. watch 和 watchEffect 的区别?
- watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。
- watch 侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。
const effect = new ReactiveEffect(getter, scheduler);effect.runO;
// getter函数
watchEffect(() => {
app.innerHTML= state.name;//数据变化后,会调用scheduler内部会再次触发effect.runO)近新运行getter
});
// 1.getter 函数 2.cb函数
watch(
() => state.name,// 数据变化后,会调用scheduler,内部会调用cb
(newval,oldval)=>{}
);
14. 如何将 template 转换成 render 函数?
Vue 中含有模版编译的功能,它的主要作用是将用户编写的template 编译为js 中可执行的 render 图数,
- 1.将template极板转换成ast语法树·parserHTML
- 2.对静志语法做静志标记-markup diff来做优化的 静态节点跳过diff操作
- 3.重新生成代码-codeGen
Vue3 中的模版转化,做了更多的优化操作。Vue2 仅仅是标记了静态节点而已-
15.new Vue()这个过程中究竟做了些什么?
- 在 new Vue 的时候 内部会进行切始化提作.
- 内部会初始化组件绑定的事件,初始化组件的父子关系 parents childrens $root
- 初始化响应式数据 data、computed、props、watch、method,同时也初始化了provide 和inject 方法。 内部会对数据进行劫持 对象采用 defineProperty数组采用方法重写。
- 在看一下用户是否传入了el属性和template 或者render。render的优先级更高,如果用户写的是template,会做模板编译(三部曲)。最终就拿到了render的数
- 内部挂载的时候会产生一个watcher,会调用render 图数会触发依赖收集,内部还会给所有的响应式数据增加 dep 属性,让属性记录当前的 watcher (用户后续修改的时候可以触发 watcher 重新渲染)
- vue 更新的时候采用虚拟 DOM 的方式进行diff算法更新
16. Vue.observable 你有了解过吗?说说看
- 在非父子组件通信时,可以使用eventsus或者使用状态管理工具,但是功能不复杂的时候我们可以考虑使用
vue.observable
- Vue3 中已经不再有这个API了
17. v-if 和 v-for 哪个优先级更高?
v-for 和 v-if 避免在同一个标签中使用。如果遇到需要同时使用时可以考虑写成计算属性的方式。
<!--应幽通免这种写法-->
<1i w-for="l in arr" v-if="exists"></1i>
- 在 Vue2 中解析时,先解析v-for 在解析v-if。会导致先循环后在对每一项进行判断,浪费性能。
- 在 Vue3 中 v-if 的优先级高于 v-for。
18. Vue 的生命周期方法有哪些?一般在哪一步发送请求及原因
18.1 Vue2 中的生命周期
主要的生命周期有:创建前后,挂载前后,更新前后,销毁前后
- beforeCreate 初始化父子关系及事件,数据规测(data observer)之前被调用。用此方法一般编写插件的时候会用到,
- created 实例已经创建完成之后被调用,在这一步,实例已完成以下的配置:数据观测(dataobserver),属性和方法等,但是这里没有 $el ,一般也不咋用,
- beforeMount 在挂载开始之前被调用:相关的 render 的数首次被调用,
- mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。可以用于获取 DOM 元紧
- beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。此时修改数据不会再次出发
更新方法 - updated 由于数据更改导致的虚拟 DOM 重新消染和打补丁,在这之后会调用该钩子,
- beforeDestroy 实例销毁之前调用,在这一步,实例仍然完全可用,
- destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁,该钧子在股务器消染期间不被调用。
e keep-alive (activated 和 deactivated)
生命周期 v2 | 生命周期 v3 | 描述 |
---|---|---|
beforeCreate | beforeCreate | 组件实例被创建之初 |
created | created | 组件实例已经完全创建 |
beforeMount | beforeMount | 组件挂载之前 |
mounted | mounted | 组件挂载到实例上去之后 |
beforeUpdate | beforeUpdate | 组件数据发生变化,更新之前 |
updated | updated | 数据数据更新之后 |
beforeDestroy | beforeUnmount | 组件实例销毁之前 |
destroyed | unmounted | 组件实例销毁之后 |
activated | actvated | keep-alive 缓存的组件激活时 |
deactivated | deactivated | keep-alive 缓存的组件停用时调用 |
errorCaptured | errorCaptured | 捕获一个来自子孙组件的错误时被调用 |
-- | renderTracked Dev | 调试钧子,咱应式依赖被收集时调用 |
-- | renderTriggered Dev | 调试钩子,响应式依赖被触发时调用 |
-- | serverPrefetch | ssr only,组件实例在服务器上被渲染前调用 |
Vue3 中新增了,组合式API:生命周期钩子,但是不存在
onBeforeCreate
和onCreated
钩子
19. Vue 中 diff 算法原理
19.1 Diff 概念
Vue 基于虚拟 DOM 做更新。dif的核心就是比较两个虚拟节点的差异。Vue 的 diff 算法是平级比较,不考虑跨级比较的情况,内部采用深度递归的方式+双指针的方式进行比较。
19.2 Diff 比较流程
- 先比较是否是相同节点 key tag
- 相同节点比较属性,并复用老节点(将老的虚拟 dom 复用给新的虚拟节点 DOM)
- 比较儿子节点,考虑老节点和新节点儿子的情况,
- 老的没儿子,现在有儿子。直接插入新的儿子
- 老的有儿子,新的没儿子。直接删除页面节点
- 老的儿子是文本,新的儿子是文本,直接更新文本节点即可
- 老的儿子是一个列表,新的儿子也是一个列表
updateChildren
- 优化比较:头头、尾尾、头尾、尾头
- 比对查找进行复用
Vue3 中采用最长递增子序列来实现 diff 优化。
20. 请说明 Vue 中 key 的作用和原理,谈谈你对它的理解
20.1 key 的概念
key
的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。- 当 Vue 正在更新使用
v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染
20.2 key 的作用
- Vue 在 patch 过程中通过 key 可以判断两个虚拟节点是否是相同节点。(可以复用老节点)
- 无 key 会导致更新的时候出问题
- 尽量不要采用索引作为 key
21.Vue.use 是干什么的?
21.1 use 概念
安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,"它会被作为 install方法。install 方法调用时,会将 Vue 作为参数传入,这样插件中就不在需要依赖 Vue 了
21.2 插件的功能
- 添加全局指令、全局过滤器、全局组件。
- 通过全局混入来添加一些组件选项。
- 添加 vue 实例方法,通过把它们添加到
Vue.prototype
上实现:
21.3 实现原理
Vue.use = function(plugin:Function | object){
// 插件缓存
const installedplugins =
this._installedPluginsll(this._installedplugins =[]);
if(installedplugins.indexof(plugin) > -1){
//如果已经有插件 直接返回
return this;
}
// additional parameters
const args=toArray(arguments,1);// 除了第一项其他的参数整合成数组
args.unshift(this);//将 Vue 放入到数组中
if(typeof plugin.install === "function"){
// 调用install方法
plugin.insta11.apply(plugin, args);
}else if(typeof plugin === "function"){
// 直接调用方法
plugin.apply(nu77, args);
}
installedplugins.push(plugin);// 缓存插件
return this;
};
22.Vue.extend 方法的作用?
22.1 Vue.extend 概念
使用基础 Vue 构造器,创建一个“子类"。参数是一个包含组件选项的对象。
data 选项是特例,需要注意-在 vue.extend()中它必须是函数
var Profile = Vue.extend({
template:"<p>{{firstame}} {{lastName}} aka {{alias}}</p>",
data:function(){
return {
firstName:"walter"
lastName:"white"
alias:"Heisenberg",
};
},
});
//创建 Profile 实例,并挂载到一个元素上
new Profile().$mount("#mount-point");
new vue().$mount();
22.2 分析
- 所有的组件创建时都会调用
Vue.extend
方法进行创建 - 有了此方法我们可以用于手动挂载组件。
- 后端存储的字符串模板我们可以通过
Vue.extend
方法将其进行渲染,但是需要引入编译时。
23. Vue 组件 data 为什么必须是个函数?
- 根实例对象data可以是对象也可以是函数“单例”,不会产生数据污染情况
- 组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。所以需要通过工厂函数返回全新的 data 作为组件的数据源
function vue( {}
Vue.extend = function (options) {
function sub() {
// 会将data存起来
this.data = this.constructor.options.data;
}
Sub.options =options;
return sub:
};
let child = Vue.extend({
data: { name: "xxx"},
});
//两个组件就是两个实例,希望数据互不干扰
let child1 = new child();
let child2 = new child();
console.log(child1.data.name); //xxx
child1.data.name = "jw";
console.log(child2.data.name); //jw
24.函数组件的优势
函数式组件的特性:无状态、无生命周期、无this,但是性能高。正常组件是一个类继承了 Vue, 函数式组件就是普通的函数,没有 new 的过程。最终就是将返回的虚拟 DOM 变成真实 DOM 替换对应的组件。
函数式组件不会被记录在组件的父子关系中,在Vue3 中因为所有的组件都不用 new 了,所以在性能上没有了优势。
25.Vue 中的过滤器了解吗?过滤器的应用场景有哪些?
过滤器实质不改变原始数据,只是对数据进行加工处理后返回过滤后的数据再进行调用处理,我们也可以理解成纯函数。
{{ message | filterA("arg1","arg2") | filterB("arg1","arg2") }}
vue.filter('filterA',function(value){
// 返回处理后的值
})
Vue.filter('filterB',function(value){
// 返回处理后的值
})
常见场景:单位转换、千分符、文本格式化、时间格式化等操作。这个写个方法、写个方法不香么?Vue3 果断废弃了过滤器…
<p>{{format(number)}}</p>
const format==>{
return parseFloat(n).toFixed(2);
}
26.v-once的使用场景有哪些
26.1 v-once 概念
v-once
是 Vue 中内置指令,只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
26.2 v-once 使用场景
<!--单个元素-->
<span v-once>This will never change: {{msg}}</span>
<!--有子元素-->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>
<!--组件 -->
<my-component v-once :comment="msg"></my-component>
<!--'v-for'指令-->
<ul>
<li v-for="i in list"v-once>{{i}}</li>
</ul>
Vue3.2 之后,增加了
v-memo
指令,通过依赖列表的方式控制页面渲染。
<div>
<div v-memo="[valueA,valueB]">
<div class="box" v-for="item in arr" :key="item">{{ item }}</div>
</div>
</div>
27. Vue.mixin 的使用场景和原理
27.1 Vue.mixin 概念
mixin 可以用来扩展组件,将公共逻辑进行抽离。在需要该逻辑时进行“混入",采用策略模式针对不同的属性进行合并。如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。
mixin 中有很多缺陷"命名冲突问题”、"数据来源问题",Vue3 采用 CompositionAPI 提取公共逻辑非常方便。
27.2 混入方式
在vue中我们可以 局部混入 跟 全局混入 。一般情况下全局混入用于编写插件。局部混入用于复用逻辑。
27.3 mixin 合井策略
核心就是:对象的合并处理。
- props、methods、inject、computed 同名时会被替换。data 会被合并
- 生命周期和 watch 方法 会被合并成队列
- components、directives、filters 会在原型链上叠加
组件的扩展除了 mixin 之外还有一个属性叫 extends,但是不怎么常用。
28. Vue 中 slot 是如何实现的?什么时候使用它?
28.1 什么是插槽?
插槽设计来源于 Web Components 规范草案,利用 slot
进行占位,在使用组件时,组件标签内部内容会分发到对应的 slot 中。
28.2 什么时候使用它
通过插槽可以让用户更好的对组件进行扩展和定制化。可以通过具名插槽指定染的位置。常用的组件例如:弹框组件、布局组件、表格组件、树组件.
28.3 插槽的分类和原理
- 默认插槽
- 具名插槽
<div>
<slot name="title"></slot>
<slot name="content"></slot>
</div>
// 编译后的结果:with(this){return_c('div',[_t("title"),_v("),t("content")],2)}
<my>
<h1 slot="title">标题</h1>
<div slot="content">内容</div>
</my>
/**编译后的结果
with(this){
return _c('my',[
_c('h1',{attrs:{"slot":"title"},slot:"title"},[_v("标題")]),_v(" "),
_c('div',{attrs:{"slot":"content"},slot:"content"},[_v("内容")])
])
}
**/
- 作用域插槽
<div>
<slot :article="{title:'标题',content:'内容'}"></slot>
</div>
// 编译后的结果 with(this){return _c('div',[_t("default",null,{"article":{title:'标题',content:'内容'}})],2)}
<my>
<template v-slot="{article}">
<h1>{{article.title}}</h1>
<div>{{article.content}}</div>
</template>
</my>
/**
with(this) {
return _c('my',{
scopedslots:_u([{
key: "default",
fn: function({
article
}) {
return [_c('h1',[_v(_s(article.title))]),_c('div',[
_v(_s(article.content))
])]
}
}]}
})
}
*/
普通插槽,渲染在父级,作用域插槽在组件内部染!