不去面试看了千遍间隔太久也宛如一遍,面试前看一遍宛如千遍,临阵磨枪不快也光,赶紧冲,收藏+关注每天睡前看一遍催眠又深刻,梦里梦外面试不迷路
1.v-show和v-if的区别
- v-show:通过CSS display控制显示和隐藏
- v-if:组价真正的渲染和销毁,而不是显示和隐藏
- 频繁切换显示状态时候我们就用v-show,否则就用v-if
2.为何在v-for中用key
- 必须用key,且不能是index和random
- diff算法中通过tag和key来判断,是否是sameNode
- 减少渲染次数,提升渲染性能
3.描述Vue组件的生命周期(父子组件)
- 挂载
- beforeCreate
- created
- beforeMount
- mounted
- 更新
- beforeUpdate
- updated
- 销毁
- beforeDestroy
- Destroyed
钩子函数 | 过程 |
---|---|
beforeCreate | 在实例初始化之后, 数据观测和事件配置之前被调用此时data和methods以及页面的DOM结构都没有初始化什么都做不了 |
created | 在实例创建完成后被立即调用此时data 和 methods已经可以使用但是页面还没有渲染出来 |
beforeMount | 在挂载开始之前被调用此时页面上还看不到真实数据只是一个模板页面而已 |
mouted | el被新创建的vm.$el替换, 并挂载到实例上去之后调用该钩子。数据已经真实渲染到页面上在这个钩子函数里面我们可以使用一些第三方的插件 |
beforeUpdate | 数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的 |
update | 由于数据更改导致的虚拟DOM重新渲染和打补丁, 在这之后会调用该钩子。页面上数据已经替换成最新的 |
beforeDestroy | 实例销毁之前调用 |
destroyed | 实例销毁后调用 |
4.vue组件如何通讯(常见)
- 父子组价props和this.$emit
- 自定义事件event. <math xmlns="http://www.w3.org/1998/Math/MathML"> n o 、 e v e n t . no、event. </math>no、event.off、event.$emit
- vuex
- 更多请看:vue2.x的10种组件间通信方式
5.描述组件渲染和更新的过程
- 初次渲染的过程
- 解析模板为render函数
- 触发响应式,监听data属性getter setter
- 执行render函数,生成vnode,patch(elem,vnode)
- 执行render会触发getter
- 更新过程
- 修改data,触发setter
- 重新执行render函数,生成newVnode
- patch(vnode,newVnode),diff算法会算差异
6.双向数据绑定v-model的实现原理
- input元素的value=this.name
- 绑定input事件 this.name = $event.target.value
- data 更新触发 re-render
3个步骤,实现数据的双向绑定:
vue的双向绑定实现原理:juejin.cn/post/684490...
vue 双向数据绑定实现原理:juejin.cn/post/684490...
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
vue是采用数据劫持结合发布者-订阅者模式的方式,通过**Object.defineProperty()**来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。
v-model原理其实就是给input事件绑定oninput事件 就会立刻调用底层对象对应的setter方法 改变data里的属性的值 从而实现双向数据绑定
vue单项数据绑定原理
单项绑定过程:变量变了,由set发通知给watcher,watcher告知虚拟DOM树,叫它该比较了,我这有值变了,于是生成新的dom树进行一个比较,然后逐级分类比较,比较出哪个元素发生变化就把这个元素更新到页面,这就是单项数据绑定原理。
vue双向数据绑定原理
7.对MVVM的理解
8.computed有什么特点
-
computed有缓存,data不变则不会重新计算
-
提高性能
-
计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题
-
计算属性是基于它们的响应式依赖进行缓存的
-
在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
- 比如我们有
firstName
和lastName
两个变量,我们需要显示完整的名称。 - 但是如果多个地方都需要显示完整的名称,我们就需要写多个
{{firstName}} {{lastName}}
- 比如我们有
methods和computed区别
methods
里面的数据不管发没发生变化, 只要调用了都会执行函数(有的时候数据没发生变化我们不希望调用函数)computed
计算属性会进行缓存, 如果数据没发生变化,函数只会被调用一次(数据发生变化才会调用函数)
总结:
methods
不管数据发没发生变化都会调用函数computed
只有在依赖数据发生变化时才回调函数
2.侦听器 watch
侦听器的应用场景:数据变化时执行
异步
或开销较大的操作
(eg:我们可以使用watch来进行路由的监听)注意: watch 中的属性,一定是data 中 已经存在的数据
watch例子🌰:codesandbox.io/s/vuebaseus...
9.为何组件data必须是一个函数
我们需要先复习下原型链的知识,其实这个问题取决于 js ,而并非是 vue 。
因为根本上.vue文件被编译出来是个class是一个类,每个地方使用组价的时候相当于对class实例化,实例化时候执行这个data()
基于此,我们来看看这个问题:
ini
function Component(){
}
Component.prototype.data = {
name:'jack',
age:22,
}
var componentA = new Component();
var componentB = new Component();
componentA.data.age=55;
console.log(componentA,componentB)
复制代码
此时,componentA 和 componentB data之间指向了同一个内存地址,age 都变成了 55, 导致了问题!
接下来很好解释为什么 vue 组件需要 function 了:
javascript
function Component(){
this.data = this.data()
}
Component.prototype.data = function (){
return {
name:'jack',
age:22,
}
}
var componentA = new Component();
var componentB = new Component();
componentA.data.age=55;
console.log(componentA,componentB)
复制代码
此时,componentA 和 componentB data之间相互独立, age 分别是 55 和 22 ,没有问题!
这样每一个实例的data属性都是独立的,不会相互影响了。所以,你现在知道为什么vue组件的data必须是函数了吧。这都是因为js本身的特性带来的,跟vue本身设计无关。其实vue不应该把这个方法名取为data(),应该叫setData或其他更容易立即的方法名。
10.ajax请求应该放在哪个生命周期
- mounted
- js是单线程,ajax异步获取数据
- 放在mounted之前没有用,只会让逻辑更加混乱
11.如何将组件所有props传递给子组件
- $props
12.如何自己实现v-model
vue
<template>
<!-- 例如:vue 颜色选择 -->
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
>
<!--
1. 上面的 input 使用了 :value 而不是 v-model
2. 上面的 change1 和 model.event1 要对应起来
3. text1 属性对应起来
-->
</template>
<script>
export default {
model: {
prop: 'text1', // 对应 props text1
event: 'change1'
},
props: {
text1: String,
default() {
return ''
}
}
}
</script>
13.多个组件有相同的逻辑,如何抽离?mixin
mixin
mixin缺点:变量来源不明确(隐式传入),不利于阅读,使代码变得难以维护;多个mixins的生命周期会融合到一起运行,但是同名属性、同名方法无法融合,可能会导致冲突;mixins和组件可能出现多对多的关系,复杂度较高(即一个组件可以引用多个mixins,一个mixins也可以被多个组件引用)。
Vue2/Vue3中的代码逻辑复用对比(mixins、自定义hook):juejin.cn/post/694978...
eg:
vue
<div v-if="editAuthJudge(pageDetails)" class="canvas-config-item" @click="showDebugModal">
<span class="item-label"><i class="mtdicon-monitor"></i>本地调试</span>
</div>
<div v-if="actionsMap['revoke'] && editAuthJudge(pageDetails)" @click="handleRevokeConfig">
<mtd-tooltip content="撤销修改" placement="top">
<span class="item-label"><i class="mtdicon-undo-o"></i>撤销</span>
</mtd-tooltip>
</div>
<div v-if="actionsMap['revoke'] && editAuthJudge(pageDetails)" :class="['canvas-config-item', sdk.activeVersionIndex === 0 ? 'disabled' : '']" @click="handleRecoveryConfig">
<mtd-tooltip content="恢复修改" placement="top">
<span class="item-label"><i class="mtdicon-redo"></i>恢复</span>
</mtd-tooltip>
</div>
import editAuthJudge from "../components/mixins/editAuthJudge";
export default {
name: 'CanvasConfigPanel',
mixins: [editAuthJudge],
inject: {
zero: {
default: () => ({})
}
},
props: {
pageDetails: {
type: Object,
default: () => {}
},
actions: {
type: Array,
default: () => {
return ['device', 'scale', 'code', 'revoke', 'preview', 'clear', 'import', 'export', 'showLine', 'hideLine']
}
}
},
14.何时要是用异步组件?
- 道理和webpack的按需加载是一样的
- 加载大组件
- 路由异步加载
15.何时需要是用keep-alive?
- 缓存组件,不需要重复渲染
- 如多个静态tab页的切换
- 优化性能
16.何时需要是用beforeDestory?
- 解除自定义事件event.$off,否则容易造成内存泄露
- 清除定时器
- 解绑自定义的DOM事件(addEventLisenner),如window scroll等
17.什么是作用域插槽
ScopedSlotDemo
xml
<template>
<a :href="url">
<slot :slotData="website">
{{website.subTitle}} <!-- 默认值显示 subTitle ,即父组件不传内容时 -->
</slot>
</a>
</template>
<script>
export default {
props: ['url'],
data() {
return {
website: {
url: 'http://wangEditor.com/',
title: 'wangEditor',
subTitle: '轻量级富文本编辑器'
}
}
}
}
</script>
javascript
<ScopedSlotDemo :url="website.url">
<template v-slot="slotProps">
{{slotProps.slotData.title}}
</template>
</ScopedSlotDemo>
import ScopedSlotDemo from './ScopedSlotDemo'
18.Vuex中action和mutation有何区别
- action中处理异步,mutation不可以处理异步
- mutation做原子操作(每次做一个)
- action可以整合多个mutation
19.Vue-router常用的路由模式
- hash
- H5 history(需要服务端支持)
- 原理
- 两者比较
20.如何配置Vue-router异步加载
javascript
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [{ path: '/foo', component: Foo }]
})
21.请用vnode描述一个DOM结构
22.监听data变化的核心API是什么?
核心API------Object.defineProperty(obj, prop, descriptor)
- 监听对象、监听数组
- 复杂对象、深度监听
Object.defineProperty缺点:
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性/删除属性(Vue.set Vue.delete)
- 无法原生监听数组,需要特殊处理
23.Vue如何监听数组变化?
- Object.defineProperty不能监听数组变化
- 重新定义原型,重写push pop等方法,实现监听
- Proxy可以原生支持数组变化
24.请描述响应式原理
监听data变化
组件渲染和更新的过程
25.diff算法的时间复杂度
- O(n)
- 在O(n^3)基础上做了一些调整
26.简述diff算法的过程
使用开始vdom
- patch(elem,vnode) 和 patch(vnode,newVnode)
- patchVode 和 addVnodes 和 removeVnodes
- updateChildren(key的重要性)
27.vue为何是异步渲染,$nextTick何用?
- 异步渲染(以及合并data()修改),以提高渲染性能
- $nextTick 在DOM更新哇之后,触发回调
- 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
vue
<template>
<div id="app">
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="addItem">添加一项</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 1. 异步渲染,$nextTick 待 DOM 渲染完再回调
// 3. 页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
this.$nextTick(() => {
// 获取 DOM 元素
const ulElem = this.$refs.ul1
// eslint-disable-next-line
console.log( ulElem.childNodes.length )
})
}
}
}
</script>
28.Vue常见性能优化
- 合理使用v-show 和 v-if
- 合理使用 computed 有缓存 提高性能
- v-for 中使用 key,以及避免和v-if同时使用
- 自定义事件、DOM事件及时销毁,避免内存泄露,页面越用越卡
- 合理使用异步组件,大的第三方组价
- 合理使用keep-alive,不要重复渲染时候缓存下来
- data 层级不要太深,因为响应式监听,深度监听计算深度多,页面会卡
- 使用vue-loader在开发环境做模板编译(预编译)
- webpack层面的优化
- 前端通用的性能优化,如图片懒加载
- 使用SSR
29.vue父子组件生命周期执行顺序,请完整表述
单一组件中:
beforeCreate→created→beforeMounted→mounted→beforeUpdate→update→beforeDestroy→destroyed
父子组件中:
常用钩子简易版:父create→子created→子mounted→父mounted
①加载渲染过程:
父beforeCreate→父created→父beforeMounted→子beforeCreate→子created→子beforeMounted→子mounted→父mounted
②更新过程:
父beforeUpdate→子beforeUpdate→子update→父update
③销毁过程:
父beforeDestroy→子beforeDestroy→子destroyed→父destroyed
注意问题:
在父组件调用接口数据,通过props传递给子组件,接口响应是异步的,子组件无论在哪个钩子都取不到数据。因为子组件的mounted都执行完之后,父组件的请求才返回数据。会导致,从父组件传递给子组件的数据是undefined。
解决:
①在渲染子组件时候加上一个条件,dataList是父组件调用接口返回的数据。当有数据的时候在去渲染子组件。这样就会形成天然的阻塞。在父组件的created中的请求返回数据后,才会执行子组件的created,mounted。最后执行父组件的mounted。
ini
<div class="test">
<children v-if="dataList" :data="dataList" ></children>
</div>
②在子组件中watch监听,父组件获取到的值变化,子组件就可以监听到
kotlin
watch:{
data:{
deep:true,
handler:function(newVal,oldVal) {
this.$nextTick(() => {
this.data = newVal
})
}
},
}
每个生命周期具体适用哪些场景
钩子函数 | 过程 |
---|---|
beforeCreate | 创建前,在实例初始化之后, 数据观测和事件配置之前被调用此时data和methods以及页面的DOM结构都没有初始化什么都做不了。data,computed,watch,methods 上的方法和数据均不能访问。可以在这里加个loading事件 |
created | 创建后,在实例创建完成后被立即调用,此时data、props、computed 和 methods已经可以使用但是页面还没有渲染出来。①可访问data computed watch methods上的方法数据②初始化完成时的事件写在这里,异步请求也适宜在这里调用(请求不宜过多,避免白屏时间太长)③可以在这里结束loading事件,还做一些初始化,实现函数自执行。④未挂载DOM,若在此阶段进行DOM操作一定要放在Vue.nextTick()的回调函数中 |
beforeMount | **挂载前,**在挂载开始之前被调用此时页面上还看不到真实数据只是一个模板页面而已。但vue挂载的根节点已经创建,下面vue对DOM的操作将围绕这个根元素继续进行。beforeMount这个阶段是过渡性的,一般一个项目只能用到一两次。 |
mouted | **挂载,**完成创建vm. <math xmlns="http://www.w3.org/1998/Math/MathML"> e l ,和双向绑定。 e l 被新创建的 v m . el,和双向绑定。el被新创建的vm. </math>el,和双向绑定。el被新创建的vm.el替换, 并挂载到实例上去之后调用该钩子。①数据已经真实渲染到页面上在这个钩子函数里面我们可以使用一些第三方的插件。②完成挂载DOM和渲染,可在mounted钩子函数中对挂载的DOM进行操作。③可在这发起后端请求,拿回数据,配合路由钩子做一些事情。 |
beforeUpdate | **数据更新前,数据驱动DOM。**数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的。①可在更新前访问现有的DOM,如手动移出添加的事件监听器。 |
update | 数据更新后,完成虚拟DOM的重新渲染和打补丁。由于数据更改导致的虚拟DOM重新渲染和打补丁, 在这之后会调用该钩子。页面上数据已经替换成最新的。①组件DOM已完成更新,可执行依赖的DOM操作。②注意:不要在此函数中操作数据(修改属性),会陷入死循环。 |
activated | 在使用vue-router时有时需要使用**<keep-alive></keep-alive> **来缓存组件状态,这个时候created钩子就不会被重复调用了。 |
deactivated | <keep-alive></keep-alive> 组件被移除时使用 |
beforeDestroy | 实例销毁之前调用①可做一些删除提示,如:您确定删除xx吗? |
destroyed | 实例销毁后调用。前组件已被删除,销毁监听事件,组件、事件、子实例也被销毁。 |
30.vue slot和slot-scope的理解
让用户可以扩展组件,去更好的复用组件和对其做定制化处理。eg:布局组件、表格列、卡片
-
默认插槽|单个插槽|匿名插槽:子: 父:内容直接在子组件的标签内写入内容即可
-
具名插槽 :子:<slot name="up"> 父:
内容
是在默认插槽的基础上加上slot
属性,值为子组件插槽name
属性值 -
作用域插槽|带数据的插槽 :子:<slot name="up" :data ="data"> 父:;通过
slot-scope
获取子组件的信息,在内容中使用。这里可以用解构语法去直接获取想要的属性。- **原理:**slot本质上是返回VNode的函数,一般情况下,Vue中的组件要渲染到页面上需要经过template→render function→VNode→DOM的过程。组件挂载的本质就是执行渲染函数得到VNode,至于data/props/computed这些属性都是给VNode提供数据来源
- 作用域插槽适合的场景是至少包含三级以上的组件层级,是一种优秀的组件化方案!
31.vue3和vue2优缺点(开发、打包、上线等环节)
①生命周期的变化
Vue2.X | Vue3 |
---|---|
beforeCreate | 使用setup() |
created | 使用setup() |
beforeMounted | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
②使用proxy代替defineProperty
Vue2.X-defineProperty | Vue3-proxy |
---|---|
vue为什么对数组对象的深层监听无法实现,因为组件每次渲染都是将data里的数据通过defineProperty 进行响应式或者双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染 |
defineProperty 只能响应首次渲染时候的属性,Proxy 需要的是整体,不需要关心里面有什么属性,而且Proxy的配置项有13种 ,可以做更细致的事情,这是之前的defineProperty 无法达到的 |
vue2.x 之所以只能兼容到IE8就是因为defineProperty 无法兼容IE8,其他浏览器也会存在轻微兼容问题 |
proxy 的话除了IE ,其他浏览器都兼容,这次vue3 还是使用了它,说明vue3 直接放弃了IE 的兼容考虑,个人感觉已经没人用IE 了 |
Object.defineProperty( Obj, 'name', { enumerable: true, //可枚举 configurable: true, //可配置 // writable:true, //跟可配置不能同时存在 // value:'name', //可写死直 get: function () { return def }, set: function ( val ) { def = val } } ) | //两个参数,对象,13个配置项 const handler = { get: function(obj, prop) { return prop in obj ? obj[prop] : 37; }, set:function(){ }, ...13个配置项 }; const p = new Proxy({}, handler); p.a = 1; p.b = undefined; console.log(p.a, p.b); // 1, undefined console.log('c' in p, p.c); // false, 37 |
③diff算法的提升
Vue2.X-diff | Vue3-diff |
---|---|
vue2.x 提供类似于HTML 的模板语法,但是,它是将模板编译成渲染函数来返回虚拟DOM树 。Vue 框架通过递归遍历两个虚拟DOM树 ,并比较每个节点上的每个属性,来确定实际DOM的哪些部分需要更新。由于现代JavaScript 引擎执行的高级优化,这种有点暴力的算法通常非常快速,但是**DOM 的更新仍然涉及许多不必要的CPU 工作** |
①DOM树级别 :在没有动态改变节点结构的模板指令(例如v-if和v-for) 的情况下,节点结构保持完全静态。如果我们将一个模板分成由这些结构指令分隔的嵌套"块",则每个块中的节点结构将再次完全静态。当我们更新块中的节点时,我们不再需要递归遍历DOM树 - 该块内的动态绑定可以在一个平面数组中跟踪。这种优化通过将需要执行的树遍历量减少一个数量级来规避虚拟DOM的大部分开销。②编译器:积极检测模版中的静态节点、子树、数据对象 ,并在生成的代码中将他们提升到渲染函数之外。这样可以避免每次渲染时重新创建这些对象,从而打打提高内存使用率并减少垃圾回收的频率。③元素级别 :编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。Vue 3有时占用的CPU时间不到 Vue 2的十分之一 |
④typeScript的支持
Vue2.X-js | Vue3-ts |
---|---|
⑤打包的体积变化
Vue2.X-23kb | Vue3-10kb |
---|---|
vue2官方 说的运行时打包师23k,但这只是没安装依赖的时候,随着依赖包和框架特性的增多,有时候不必要的,未使用的代码文件都被打包了进去,所以后期项目大了,打包文件会特别多还很大。 |
在Vue 3 中,我们通过将大多数全局API 和内部帮助程序移动到Javascript 的module.exports 属性上实现这一点。这允许现代模式下的module bundler 能够静态地分析模块依赖关系,并删除与未使用的module.exports 属性相关的代码。模板编译器还生成了对树抖动友好的代码,只有在模板中实际使用某个特性时,该代码才导入该特性的帮助程序。 |
vue3组件基本结构分析
vue
//dom 里的东西基本上都是没有变的
<template>
<h1>{{ msg }}</h1>
<button @click="increment">
count: {{ state.count }}, double: {{ state.double }},three:{{ three }},refnum:{{refnum}}
</button>
</template>
<script>
//这里就是Vue3的组合Api了,这里跟react的 import { useState ,useEffect } from 'react' 有些类似,需要用啥引啥
import {ref, reactive, computed ,watchEffect,watch} from "vue";
export default {
name: "HelloWorld",
props: {
msg: String,
},
//上面对比的时候说过,setup相当于beforeCreate 和created,简单理解就是初始化
setup() {
//这里通过reactive使state成为相应状态(后面会详细介绍)
const state = reactive({
count: 0,
//计算属性computed的使用更灵活了
double: computed(() => state.count * 2),
});
//computed也可以单独拿出来使用
const three = computed(() => state.count * 3)
//ref跟reactive作用一样都是用来数据相应的,ref的颗粒度更小(后面详细对比)
const refnum = ref()
//这里的watchEffect只要里面的变量发生了改变就会执行,并且第一次渲染会立即执行,没有变化前后返回参数,无法监听整个reactive
watchEffect(() => {
refnum.value = state.count;
console.log(state, "watchEffect");
});
//watch里第一个参数是监听需要的变量,第二个是执行的回调函数,
watch(refnum,(a,b)=>{
console.log(a,b,'watch,a,b')
})
//所有的方法里再也不需要用this了,这是很爽的
function increment() {
state.count++;
}
//组中模板中需要的变量,都要通过return给暴露出去,就像当初data({return { } }) 是一样的
return {
state,
increment,
three,
refnum
};
},
};
</script>
vue3生命周期的使用
vue
<script>
import {
reactive,
computed,
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
onUnmounted,
onBeforeUnmount,
} from "vue";
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2),
});
function increment() {
state.count++;
}
onUpdated(() => {
console.log("onUpdated");
});
onUnmounted(() => {
console.log("onUnmounted");
});
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
});
onBeforeUpdate(() => {
console.log("onBeforeUpdate1");
});
onMounted(() => {
console.log("onMounted");
});
onBeforeMount(() => {
console.log("onBeforeMount");
});
console.log("setup");
return {
state,
increment,
};
},
};
</script>
执行顺序
1.setup
2.onBeforeMount
3.onMounted
4.onBeforeUpdate
5.onUpdated
Vue3组件API的使用
①setup
父 | 子 |
---|---|
<template><br/> <img alt="Vue logo" src="./assets/logo.png" /><br/> <HelloWorld msg="Baby张 Vue3 RC" /><br/> //这里传参给子组件<br/></template><br/><script><br/>import HelloWorld from "./components/HelloWorld.vue";<br/>import { provide } from "vue";<br/>export default {<br/> name: "App",<br/> components: {<br/> HelloWorld,<br/> }<br/>};<br/></script> |
//props 接收的父组件传的参数,这就有点像react的props了 //ctx 这个参数表示的当前对象实例,也就个是变相的this setup(props,ctx){ console.log(props.msg, ctx, "app-setup"); } 如果你还想要更多当前组件相关的属性,还可以从组合Api 里引用 getCurrentInstance import {getCurrentInstance } from "vue"; const all = getCurrentInstance() console.log(all, "app-setup"); |
②ref、toRef、toRefs
ref:生成值类型的响应式数据;用于模版和reactive;通过.value修改值
javascript
<template>
<p>ref demo {{ageRef}}{{state.name}}</p>
</template>
import { ref , toRef , toRefs } from 'vue'
export default {
setup(){
const ageRef = ref(20) // 值类型 响应式
const nameRef = ref('小圆脸儿')
const state= reactive({
name:nameRef
})
setTimeout(()=>{
console.log('ageRef',ageRef.value)
ageRef.value = 25 //.value 修改值
nameRef.value = '晓雪'
},1000)
return {
ageRef,
state
}
}
}
<template>
<p ref="elemRef">我是一行文字</p>
</template>
import { ref , toRef , toRefs,onMounted } from 'vue'
export default {
name : 'RefTemplate',
setup(){
const elemRef = ref(null)
onMounted(()=>{
console.log('ref template',elemRef.value.innerHTML)
})
return {
elemRef
}
}
toRef:针对一个响应式(reactive封装)对象的prop;创建一个ref,具有响应式;两者保持引用关系
vue
<template>
<p>toRef demo {{ageRef}}-{{state.name}}{{state.age}}</p>
</template>
import { ref , toRef , toRefs,onMounted } from 'vue'
export default {
name : 'ToRef',
setup(){
const state = reactive({ //针对一个响应式(reactive封装)对象的prop
age:20,
name:'小圆脸儿'
})
const ageRef = toRef(state,'age') //创建一个ref,具有响应式
setTimeout(()=>{
state.age = 25 //.value 修改值
},1000)
setTimeout(()=>{
state.age = 30 //.value 修改值
},2000)
return {
state,
ageRef
}
}
}
toRefs:将响应式对象(reactive封装)转换为普通对象;对象的每个prop都对应的ref;两者保持引用关系
抽象合成函数时候
javascript
<template>
<p>toRef demo {{name}}{{age}}</p>
</template>
import { ref , toRef , toRefs,onMounted } from 'vue'
export default {
name : 'ToRef',
setup(){
const state = reactive({ //针对一个响应式(reactive封装)对象
age:20,
name:'小圆脸儿'
})
const stateAsRefs = toRefs(state) //将响应式对象(reactive封装)转换为普通对象
// const {age:ageRef,name:nameRef} = stateAsRefs // 每个属性,都是ref对象
//return {
// ...stateAsRefs
//}
//return {
// ageRef,
// nameRef
// }
return stateAsRefs
}
}
ref
就当作简单的响应式变量 toRef
就是把不是响应式的对象转化成响应式 toRefs
就是把响应式的reactive对象,分解成无数的响应式 ref
1.ref
响应式的数据,在函数里读取的时候需要 .value
获取
dom
里不需要我们+value
框架替我们自动解构了- 组件
return
的时候将reactive
的对象toRefs
,可以使代码更简洁,又不会丢失数据的响应式
③reactive
上面的 demo 中多多少少都用到了,用法也很简单,就说下注意点:
reactive
内部是可以使用计算属性等各种方法,它只是把数据实现响应式而已reactive
后return
的数据最好是用toRefs
转化一下,好处谁用谁知道- 跟
ref
混合使用时候可以用isRef
判断类型
④watch、watchEffect
watch
需要具体监听参数,watchEffect
不需要传入监听参数
watch
的回调函数跟以前一样有前后对比的参数,watchEffect
啥都没有
watch
只有监听属性变化才执行,watchEffect
第一次会立即执行
watch
和watchEffect
都无法监听未被绑定的属性
watch
可以直接监听 ref
和 reactive
绑定的对象,watchEffect
不可以(ref的值要.value,reactive的值要具体到内部属性),只会执行第一次
⑤函数组件
javascript
import { ref, computed, watch, onMounted } from 'vue'
const App = {
template: `
<div>
<span>count is {{ count }}</span>
<span>plusOne is {{ plusOne }}</span>
<button @click="increment">count++</button>
</div>
`,
setup() {
// reactive state
const count = ref(0)
// computed state
const plusOne = computed(() => count.value + 1)
// method
const increment = () => { count.value++ }
// watch
watch(() => count.value * 2, val => {
console.log(`count * 2 is ${val}`)
})
// lifecycle
onMounted(() => {
console.log(`mounted`)
})
// expose bindings on render context
return {
count,
plusOne,
increment
}
}
}
⑥Other
32.vue3为什么选择vite?
①vite比webpack更快
在webpack开发时构建时,默认会抓取并构建你的整个应用,然后才能提供服务,这就导致了你的项目中存在任何一个错误(即使当前错误不是首页引用的模块),他依然会影响到你的整个项目构建。所以你的项目越大,构建时间越长,项目启动速度也就越慢。
vite不会在一开始就构建你的整个项目,而是会将引用中的模块区分为依赖和源码(项目代码)两部分,对于源码部分,他会根据路由来拆分代码模块,只会去构建一开始就必须要构建的内容。
同时vite以原生 ESM的方式为浏览器提供源码,让浏览器接管了打包的部分工作。
②简单
Vite 的用法很简单, 执行初始化命令:
sql
yarn create @vitejs/app my-vue-app --template vue
就得到了一个预设好的开发环境,可以开始愉快地写 demo 了,Vite 开箱就给你一堆功能,包括 css 预处理器、html 预处理器、hash 命名、异步加载、分包、压缩、HMR 等:
这些功能,作者都按行业最佳实践预设好了,通常不需要用户介入做变更。
Vite 定位就是傻瓜式但强大的构建工具,你专心写好业务代码,早点下班,不用再为了工具费神费力了。
③生态
除了极致的运行性能与简易的使用方法外,Vite 对已有生态的兼容性也不容忽略,主要体现在两个点:
- 与 Vue 解耦,兼容支持 React、Svelte、Preact、Vanilla 等,这意味着 Vite 可以被应用在大多数现代技术栈中
- 与 Rollup 极其接近的插件接口,这意味着可以复用 Rollup 生态中大部分已经被反复锤炼的工具
③vite快但是也有缺点
当源码中有commonjs模块加载,那么将会出现模块加载失败的问题。通过依赖预构建的方式解决该问题。
当你首次启动 vite
时,Vite 在本地加载你的站点之前预构建了项目依赖。默认情况下,它是自动且透明地完成的。
这就是 Vite 执行时所做的"依赖预构建"。这个过程有两个目的:
-
CommonJS 和 UMD 兼容性: 在开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将以 CommonJS 或 UMD 形式提供的依赖项转换为 ES 模块。
在转换 CommonJS 依赖项时,Vite 会进行智能导入分析,这样即使模块的导出是动态分配的(例如 React),具名导入(named imports)也能正常工作:
js
javascript// 符合预期 import React, { useState } from 'react'
-
性能: 为了提高后续页面的加载性能,Vite将那些具有许多内部模块的 ESM 依赖项转换为单个模块。
有些包将它们的 ES 模块构建为许多单独的文件,彼此导入。例如,
lodash-es
有超过 600 个内置模块!当我们执行import { debounce } from 'lodash-es'
时,浏览器同时发出 600 多个 HTTP 请求!即使服务器能够轻松处理它们,但大量请求会导致浏览器端的网络拥塞,使页面加载变得明显缓慢。通过将
lodash-es
预构建成单个模块,现在我们只需要一个HTTP请求!
注意
依赖预构建仅适用于开发模式,并使用
esbuild
将依赖项转换为 ES 模块。在生产构建中,将使用@rollup/plugin-commonjs
。
require加载同步,nodejs服务端一般使用require加载模块,一般是一个文件,只需要从本地硬盘中读取,速度比较快。但是在浏览器端就不一样了,文件一般存放在服务器或CDN上,如果使用同步的方式加载一个模块还需要由网络的快慢来决定,可能时间会很长,这样浏览器很容易进去假死状态。所以才有了后面说的AMD和CMD模块化方案,他们都是异步加载的,比较适合浏览器端使用。
CommonJS和ES6模块的区别
-
因为CommonJS 的
require
语法是同步的,所以就导致了CommonJS模块规范只适合用在服务端,而ES6模块无论是在浏览器端还是服务端都是可以使用的,但是在服务端中,还需要遵循一些特殊的规则才能使用 ; -
CommonJS 模块输出的是一个值的拷贝,而ES6 模块输出的是值的引用;
-
CommonJS 模块是运行时加载,而ES6 模块是编译时输出接口,使得对JS的模块进行静态分析成为了可能;
-
因为两个模块加载机制的不同,所以在对待循环加载的时候,它们会有不同的表现。CommonJS 遇到循环依赖的时候,只会输出已经执行的部分,后续的输出或者变化,是不会影响已经输出的变量。而ES6模块相反,使用
import
加载一个变量,变量不会被缓存,真正取值的时候就能取到最终的值; -
关于模块顶层的
this
指向问题,在CommonJS 顶层,this
指向当前模块;而在ES6模块中,this
指向undefined
; -
关于两个模块互相引用的问题,在ES6模块当中,是支持加载CommonJS 模块的。但是反过来,CommonJS 并不能
require
ES6模块,在NodeJS中,两种模块方案是分开处理的。
33.Vue组件传值方式以及使用场景
父传子:props
子传父:子 <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t 、父 emit、父 </math>emit、父on、v-on
兄弟之间:event bus、 <math xmlns="http://www.w3.org/1998/Math/MathML"> r e f s 、 refs、 </math>refs、root、
父取子: <math xmlns="http://www.w3.org/1998/Math/MathML"> r e f :父 t h i s . ref :父 this. </math>ref:父this.refs.child1.msg 、this.$refs.child1.childConsole("hello")
子取父: <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r e n t :子 1 发送: t h i s . parent:子1发送:this. </math>parent:子1发送:this.parent. <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t ( " s e n d " , " 我是子组件 1 " ) ; 子 2 接收: t h i s . emit("send", "我是子组件1"); 子2接收:this. </math>emit("send","我是子组件1");子2接收:this.parent.$on('send',(value)=>{ console.log('child2 received value is :>> ', value); })
父取/调子: <math xmlns="http://www.w3.org/1998/Math/MathML"> c h i l d r e n 父: t h i s . children 父:this. </math>children父:this.children[0]
跨层级:provide/inject
父传子孙后代: <math xmlns="http://www.w3.org/1998/Math/MathML"> a t t r s 子: attrs 子: </math>attrs子:attrs.value v-bind="$attrs" 孙子:props:value
孙传父爷: <math xmlns="http://www.w3.org/1998/Math/MathML"> l i s t e n e r s 子: t h i s . listeners 子:this. </math>listeners子:this.listeners.event ('hello vue') 父:@event="message" message (value) { console.log ('value :>> ', value); };孙:this. <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t ( " e v e n t " , " h e l l o v u e " ) ; 子: v − o n = " emit("event", "hello vue"); 子:v-on=" </math>emit("event","hellovue");子:v−on="listeners" 父:@event="message"
任意组件通信:vuex
slot-scope v-slot
34.vue的 <math xmlns="http://www.w3.org/1998/Math/MathML"> n e x t T i c k 和 nextTick 和 </math>nextTick和set原理及使用场景
①set
①-①使用场景
由于JavaScript的限制,Vue无法检测到data中数组和对象的变化,因此也不会触发视图更新
当我们对data中的数组或对象进行修改时 ,有些操作方式是非响应式的 ,Vue检测不到数据更新,因此也不会触发视图更新。此时需要使用Vue.set()进行响应式的数据更新。
eg:
kotlin
data () {
return {
student: {
name: '',
sex: ''
}
}
}
mounted () { // ------钩子函数,实例挂载之后
this.student.age = 18
}
mounted () {
this.$set(this.student,"age", 24)
// Vue.set
Vue.set(this.student, "age", 24)
}
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时对property将属性转为 getter/setter,所以属性property必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。
正确写法:this.$set(this.data,"key",value')
Vue.set(target,key,value)
Vue 不允许动态添加根级响应式属性。
只可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性,例如
csharp
var vm=new Vue({
el:'#test',
data:{
//data中已经存在info根属性
info:{
name:'小圆脸儿';
}
}
});
//给info添加一个年纪属性
// this.user.age = 28 这样是不起作用, 不会被Observeryua
Vue.set(vm.info,'age','28');
①-②Vue.set()和this.$set()实现原理
vue中$set方法对数组和对象的处理本质上的一样的,对新增的值添加响应然后手动触发派发更新。
vm.$set()在new Vue()时候就被注入到Vue的原型上。
源码位置: vue/src/core/instance/index.js
javascript
复制代码import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
initMixin(Vue)
// 给原型绑定代理属性$props, $data
// 给Vue原型绑定三个实例方法: vm.$watch,vm.$set,vm.$delete
stateMixin(Vue)
// 给Vue原型绑定事件相关的实例方法: vm.$on, vm.$once ,vm.$off , vm.$emit
eventsMixin(Vue)
// 给Vue原型绑定生命周期相关的实例方法: vm.$forceUpdate, vm.destroy, 以及私有方法_update
lifecycleMixin(Vue)
// 给Vue原型绑定生命周期相关的实例方法: vm.$nextTick, 以及私有方法_render, 以及一堆工具方法
renderMixin(Vue)
export default Vue
stateMixin()
ini
复制代码...
Vue.prototype.$set = set
Vue.prototype.$delete = del
...
set()
源码位置: vue/src/core/observer/index.js
$set在Vue中的定义:
javascript
function set(target: Array<any> | Object, key: any, val: any): any {
// ①先判断set的目标是否是undefined 或 null或 基本类型 (因为用户不应该往undefined和基本类型中set东西)
if (
process.env.NODE_ENV !== "production" &&
(isUndef(target) || isPrimitive(target))
) {
// ①result 如果是undefined或基本类型就报错
warn(
`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
);
}
// ②判断了目标是否是数组与key是不是合法的index,合法的index是指值为大于等于0的整数
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
target.length = Math.max(target.length, key);
// ②result如果两个条件都成立就对目标数组调用splice方法插入或者修改数组
target.splice(key, 1, val); //这个splice不是普通的splice,是被vue代理重写过的splice
return val;
}
//对象实现响应
// ③判断了属性如果在目标对象中直接return结束逻辑 (因为vue只有添加目标对象中原本没有的属性时才会失去响应)
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val;
}
// 以上都不成立, 即开始给target创建一个全新的属性
// 获取Observer实例
// vue在初始化的时候会将data里的所有属性都变成响应式,如果的值是对象或者数组则会new一个Observer实例储存在__ob__
const ob = (target: any).__ob__;
// Vue 实例对象拥有 _isVue 属性, 即不允许给Vue 实例对象添加属性
// 也不允许Vue.set/$set 函数为根数据对象(vm.$data)添加属性
// 拿到这个对象的_ob_进行判断,如果不存在就说明是未经过vue初始化的普通对象而不是响应式对象
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== "production" &&
warn(
"Avoid adding reactive properties to a Vue instance or its root $data " +
"at runtime - declare it upfront in the data option."
);
return val;
}
// target本身就不是响应式数据, 直接赋值
if (!ob) {
target[key] = val;
return val;
}
// 进行响应式处理
// 手动通过defineReactive为属性添加get方法与set方法实现响应
defineReactive(ob.value, key, val);
// 然后手动调用dep里的notify()发布更新
ob.dep.notify();
return val;
}
数组实现响应
$set实现数组修改响应的方式是代理重写的数组的一部分方法,接下来我们看一下具体实现
vue中代理重写的不只是splice
,有push、pop、shift、unshift、splice、sort、reverse
这七个方法
javascript
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
// vue中代理重写 有push、pop、shift、unshift、splice、sort、reverse
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
function def(obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
// 首先执行了const result = original.apply(this, args)执行原本数组的方法并获取它的值
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
// 判断如果是往数组中添加值
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 就将新添加的值也实现响应式
if (inserted) ob.observeArray(inserted)
// 最后一步拿到这个数组的_ob_对象对_ob_里的dep进行派发更新
ob.dep.notify()
return result
})
})
工具函数:
javascript
// 判断给定变量是否是未定义,当变量值为 null时,也会认为其是未定义
export function isUndef (v: any): boolean %checks {
return v === undefined || v === null
}
// 判断给定变量是否是原始类型值
export function isPrimitive (value: any): boolean %checks {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
// 判断给定变量的值是否是有效的数组索引
export function isValidArrayIndex (val: any): boolean {
const n = parseFloat(String(val))
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
关于(ob && ob.vmCount)
javascript
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 省略...
if (asRootData && ob) {
// vue已经被Observer了,并且是根数据对象, vmCount才会++
ob.vmCount++
}
return ob
}
在初始化Vue的过程中有
javascript
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
//opts.data为对象属性
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initData(vm)
javascript
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
// 省略...
// observe data
observe(data, true /* asRootData */)
}
②nextTick
②-①使用场景:
nextTick
方法是在 Vue.js 中常见的一种异步更新 DOM 的机制。它的原理是利用 JavaScript 的事件循环机制以及浏览器的渲染流程来实现延迟执行 DOM 更新操作。
它的出现主要是为了解决 Vue 的异步更新导致的 DOM 更新后的操作问题。
在 Vue 中,数据的变化会触发重新渲染 DOM,但实际上,Vue 的数据更新是异步的。也就是说,当我们修改了 Vue 实例的数据后,并不会立即进行 DOM 更新,而是在下一个事件循环中才会进行。
这个异步更新机制的设计是为了优化性能。Vue 会对进行多次数据变化进行合并,然后在下一个事件循环中进行一次性的 DOM 更新,从而减少不必要的 DOM 操作,提高性能。
然而,由于异步更新的机制,有时候可能在修改数据后需要立即执行一些 DOM 操作,例如获取到更新后的 DOM 元素、更新后的样式计算、触发一些特定事件等。这时候就需要使用 nextTick
方法了。
nextTick
方法是 Vue 提供的一个实用工具,它能够将回调函数延迟到下一个 DOM 更新循环之后执行。也就是说,通过 nextTick
方法,我们可以确保在 DOM 更新完成后执行某些操作。
使用 nextTick
方法经常用来解决以下问题:
- 获取更新后的 DOM 元素
- 更新后的样式计算
- 触发一些特定事件
综上所述,nextTick
的出现解决了 Vue 的异步更新机制导致的 DOM 更新后的操作问题,使我们能够在正确的时机执行对应的操作,提高开发效率和灵活性。
②-②实现原理:
- 将传递的回调函数放入 callbacks 全局数组中
- 调用 timerFunc 函数,在浏览器的异步队列中放入刷新callbacks(flashcallbacks ) 函数 ,延迟执行 (根据运行环境判断将flashcallbacks() 放入宏任务或者是微任务队列, 使得 flashcallbacks 被延迟调用)
- 事件循环到了微任务或者宏任务,依次遍历执行 callbacks 数组中的所有函数
延迟调用优先级如下:
Promise.then > MutationObserver > setImmediate > setTimeout
MutationObserver 是H5 新加的一个功能,其功能是监听 DOM 节点的变动,在所有 DOM 变动完成后,执行回调函数。
Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和Dom操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
nextTick实现原理详情:juejin.cn/post/725617...
35.vue更新策略及diff算法
更新过程
- 修改data,触发setter
- 重新执行render函数,生成newVnode
- patch(vnode,newVnode),diff算法会算差异
diff算法
使用开始vdom 虚拟DOM 。虚拟DOM算法 = 虚拟DOM + Diff算法
diff算法:新旧虚拟DOM对比,找出更改的虚拟节点,并更新虚拟节点所对应的真实节点,实现精准的更新真实DOM,进而提高效率。
使用虚拟DOM算法耗损计算:总耗损=虚拟DOM增删改+真实DOM差异增删改(diff算法效率)+重绘与重排(较少)
直接操作DOM的耗损计算:总耗损=真实DOM完全增删改+重绘与重排(较多)
- 同层对比:深度优先算法 patch(elem,vnode) 和 patch(vnode,newVnode)
- 对比流程:patchVode 和 addVnodes 和 removeVnodes
- 对比子节点,并更新:updateChildren(key的重要性)
36.mixins和vuex的优缺点
Mixins 是我们可以重用的代码块
vuex源码就是调用了Vue.mixin,Vuex的双向绑定通过调用 new Vue实现,然后通过 Vue.mixin 注入到Vue组件的生命周期中,再通过劫持state.get将数据放入组件中
37.Vue动态加载异步组件原理及应用场景
核心原理就是 nextTick 实现异步队列 前提是需要理解 js 事件循环机制
38.你都做过哪些 Vue 的性能优化
这里只列举针对 Vue 的性能优化 整个项目的性能优化是一个大工程 可以另写一篇性能优化的文章 哈哈
- 对象层级不要过深,否则性能就会差
- 不需要响应式的数据不要放到 data 中(可以用 Object.freeze() 冻结数据)
- v-if 和 v-show 区分使用场景
- computed 和 watch 区分使用场景
- v-for 遍历必须加 key,key 最好是 id 值,且避免同时使用 v-if
- 大数据列表和表格性能优化-虚拟列表/虚拟表格
- 防止内部泄漏,组件销毁后把全局变量和事件销毁
- 图片懒加载
- 路由懒加载
- 第三方插件的按需引入
- 适当采用 keep-alive 缓存组件
- 防抖、节流运用
- 服务端渲染 SSR or 预渲染
其他Vue真题:juejin.cn/post/696122...
其他Vue详解合集如下:
- Vue基本使用 1
- vue2.x的10种组件间通信方式 2
- Vue2.X的使用 3
- Vue面试真题演练 4 当前文档
- Vue3 5 更新中
感谢阅读
「❤️关注+点赞+收藏+评论+转发❤️」,创作不易,鼓励笔者创作更好的文章