MVVM模型
MVVM是Model-View-ViewModel的简写,其中 Model 代表data中的数据,View 代表看到的页面(html),ViewModel是View和Model之间的桥梁, 也就是vue实例,只要改了数据,会自动通知页面更新。
Vue的生命周期
Vue 的生命周期可以分为8个阶段:创建前后、挂载前后、更新前后、销毁前后。
| Vue 2中的生命周期钩子 | Vue 3选项式API的生命周期选项 | Vue 3 组合API中生命周期钩子 | 描述 |
|---|---|---|---|
beforeCreate | beforeCreate | setup() | 创建前,初始化了一个vue的空实例对象,有一些生命周期函数和默认的事件,data,method中的数据还没有初始化 |
created | created | setup() | 创建后,可以拿到data,method中的数据了,可以进行一些Ajax请求 |
beforeMount | beforeMount | onBeforeMount | 挂载前,还没渲染出页面,此时是修改data值的最后机会 |
mounted | mounted | onMounted | 挂载后,页面已经渲染好了,可以访问dom了,有些库可以在此使用 |
beforeUpdate | beforeUpdate | onBeforeUpdate | 更新前,此时数据瞬间改变,但dom的改变需要等待updated后才可以,可用于获取更新前各种状态 |
updated | updated | onUpdated | 更新后,异步更新dom,此时可以拿到更新后的dom了 |
beforeDestroy | beforeUnmount | onBeforeUnmount | 销毁前,可用于一些定时器或订阅的取消 |
destroyed | unmounted | onUnmounted | 销毁后,可以在此处删掉window.onresize,清除定时器等 |
父子组件的生命周期:
加载渲染阶段:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted更新阶段:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated销毁阶段:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
Vue 中组件间的通信方式?
1. 父子组件通信:
(1).父传子:在父组件引用子组件的时候绑定一个属性,把父组件身上的数据通过属性传值的方式传给子组件,在子组件里面用props接收。
-
父组件:
`<div> <child message='value'></child> </div>` -
子(vue2):dom和js中可以直接用this.props.message
props:{ message:{ type:stringify, default:"默认文字", } } -
子(vue3):dom 中使用message,js中可以直接用props.message
import { defineProps } from "vue" const props = defineProps(( message:{ type:stringify, default:"默认文字", } })
2. 还可以通过ref实现父组件调用子组件的方法从而给子组件传值
父组件:ref.value.function
子组件:vue3需要导出对应的方法
import { defineExpose } from "vue"
defineExpose({
function
})
(2).子传父:子组件想要调用父组件的事件,首先在父组件身上定义一个函数,设置一个参数用来接收数据,在子组件里绑定一个方法,在方法里用emit()触发父组件的函数,$emit()中的参数是父组件中定义的一个方法名和参数。
父组件:
<div>
<child @message='parentMethod'></child>
</div>
parentMethod(a){
console.log("子组件传过来的信息a:",a);
}
子(vue2):
<div @click='childMethod'></div>
childMethod(){
this.$emit("message",this.a)
}
子(vue3):
<div @click='childMethod'></div>
const emit = defineEmits(["message"]);
//子组件调用父组件的方法
const childMethod = () => {
emit("message","参数”)
}
子组件是否可以直接改变父组件的数据?
不可以,违反单向数据流原则,应该emit一个方法,让父组件自己修改自己的数据
2. 兄弟组件通信:
全局事件总线`EventBus`、`Vuex`、中间人模式:父->子->父->子。
Vue2:
let bus=new vue();
组件1:
handleClick(){
bus.$emit("a","信息")
}
组件2:创建完组件时:
bus.$on("a",(b)=>{
})
Vue3:

- mitt:

-
跨层级组件通信:
全局事件总线
EventBus、Vuex、provide/inject。传递: provide("data",ref.value) provide("method",(n)=>{ref.value=n}) 接收: let a = inject("data") let b = inject("method") b(222)
Vue 的响应式原理
-
Vue 2 通过Object.defineProperty()进行set,get方法拦截,就是当把js对象传递给vue实例来作为他的data时,vue会遍历它的属性,用Object.defineProperty()监听他们的get、set方法,这样,他们就可以让vue追踪依赖,在对象的属性被访问和修改时通知变化。
缺点:监听不到有些变化,比如:- 新增或删除对象属性 --> 用vue.set(哪个属性,属性名,属性值),这样才可以变成响应式数据
- 数组定义好后,用一些不影响原数组的方法改变原数组 --> 要用新数组替换原数组才可以更新
- 数组通过索引值来修改某个元素(可以影响到原数组) --> 用splice修改;或者用vue.set(哪个数组,第几个,改成什么)
- 对于
ES6中新产生的Map、Set这些数据结构不支持。
-
Vue 3 中利用Proxy可以直接监听整个对象而非属性,可见听数组的变化,同时支持对象和数组,动态属性增、删都可以拦截,但对ie不友好,ie不支持es6,对于ie会自动降为vue2的方法。
双向绑定?
- 概念:
Vue 中双向绑定是一个指令v-model,可以绑定一个响应式数据到视图,同时视图的变化能改变该值。v-model是语法糖,默认情况下相当于:value和@input,使用v-model可以减少大量繁琐的事件处理代码,提高开发效率。 - 使用:
通常在表单项上使用v-model,还可以在自定义组件上使用,表示某个值的输入和输出控制。 - 原理:
v-model是一个指令,双向绑定实际上是Vue 的编译器完成的,通过输出包含v-model模版的组件渲染函数,实际上还是value属性的绑定及input事件监听,事件回调函数中会做相应变量的更新操作。
computed 和 watch 的区别?
-
computed计算属性,可以在引用一些数据,这些数据经过一系列操作才返回一个值的时候使用。必须有return,每次的求值结果会被缓存起来,如果页面中多次使用该变量,会直接拿缓存的结果,提升性能。// 初始变量 const myname = ref(0) // computed后的变量(只要初始变量改变,就会自动改变) const computedname = computed(()=>{ return myname.value+1 }) let params = reactive({ aliceDRArray: [// 存放虚线框上对应的dom元素--alice 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 ], )} const computed = computed(()=>{ return params.aliceDRArray.filter(item =>item!=0) }) // js使用:computedname.value // dom使用:computedname.value -
watch可以监视data中数据的变化,然后触发这个watch对应的function处理函数// 对于ref变量: watch(stasus,(val, oldVal) => { }); // 对于reactive变量: watch(() => params.a,(val, oldVal) => { },{ immediate:true,//首次进页面执行一次 deep:true,//深度监听 });
vue3响应式api有哪些:ref,reactive
###### ref可以对简单字符串和数据类型进行拦截,可以传入简单字符串或者数字
声明:
const age = ref(100);
ref转为reactive:
let params = reactive({
ref,
})
###### reactive只能对有key和value的对象进行拦截,只能传入对象
声明:
let params = reactive({
a:10
})
reactive转为ref:...toRefs(params)
//vue3定义全局变量的方式:
// app.vue中定义全局变量:
const Utils = {
// function
}
app.config.globalproperties.Utils = Utils;
// child1.vue中接收全局变量:
const { proxy }= getCurrentInstance();
const init = () => {
let test =""
console.log("调用全局为空判断",proxy.Utils.isEmpty(test));
}
Vue 中的修饰符有哪些?
- 表单修饰符
number把字符串转换为数字trim自动删除首尾空格,而中间的空格不会被过滤。
- 事件修饰符
stop阻止冒泡prevent阻止默认行为self只有事件源是自身才触发,是子元素不会触发父元素的事件,间接阻止了冒泡once只能触发一次,第二次就不会触发。capture使用事件捕获模式。
- 鼠标按键修饰符
left左键点击。right右键点击。middle中键点击。
- 键值修饰符
键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)
- 普通键(enter、tab、delete、space、esc、up...)
- 系统修饰键(ctrl、alt、meta、shift...)
Vue的内部指令
-
V-text和 V-html:
V-html可以解析html结构。在data中定义html结构,用v-html解析 `<div v-html="cont"></div> cont: "<a href='http://www.baidu.com'>百度</a>",` -
V-show和 V-if:
V-show切换Display:none;V-if直接删除dom,每一次都会使组件重新跑一遍生命周期,因为显隐决定了组件的生成和销毁; -
V-else和 V-else-if:
V-else与v-if互斥,不用写条件;V-else-if跟v-if用法一样; -
V-for:
循环数组,对象,数字,字符串;要加key,为了知道删除的是哪一个
-
V-on:
绑定事件:@click="method1($event,params1,params2)"
-
V-bind:
动态绑定变量,把普通属性修饰成vue的地盘,后面可以写变量了
-
V-model:
双向数据绑定:代替表单中的value,表单输入框值改变,会同步改变v-model绑定的变量,反过来也适用
-
V-slot插槽:
父组件
<div> <child> <template #a> <p>1234567890</p> </template> </child> </div>子组件
<div> <slot name="a"></slot> </div>//插槽中:子传父数据 父组件
<div> <child> <template #a="obj">//这里的obj也可以直接解构赋值:{item} //这里的obj是个对象,{item:“子组件的数据1”,index:“子组建的数据二”} <p>1234567890</p> </template> </child> </div>子组件
<div> <slot name="a" :item="子组件的数据1" :index="子组件的数据2"></slot> </div> -
V-once:
让元素和组件只渲染一次
Vue中key的作用?
key的作用主要是为了更加高效的更新虚拟 DOM。
首先,真实的dom元素内部含有很多属性,所以为了更高效,采用了虚拟dom,这个虚拟dom是一个js对象,保存了一些主要的属性,当数据发生修改的时候,产生了新的虚拟dom,此时用新的虚拟dom和旧的虚拟dom进行对比,就可以得到被修改的地方,在这个地方做一个标记,再去有选择的改变真实dom,这样就防止了全部更新一遍dom,效率提升了很多;然而在这其中,如何才能更高效的对比新旧虚拟dom呢?其实设置key值就是为了更高效的更新虚拟DOM,比如现在有个对象数组,当往这个数组里增加新的数据的时候,这种情况下用index值是没问题的;但如果要从数据中间删掉几个,就不适用了,这时候可以用数组的item值,但如果数组中有重复元素的话,这个也不适用了,所以最好的方式是给每个对象设置一个独有的id,作为它的唯一的标识,用这个id作为key值,这样就不会出现任何问题了。 Vue 判断两个节点是否相同时,主要是判断两者的key和元素类型tag。因此,如果不设置key ,它的值就是 undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,将造成大量的 DOM 更新操作。
自定义指令:
vue.directive("hello",{//binding是传来的值,el是当前element元素
inserted(el,binding){//标签插入父节点时执行
el.style.background = binding.value
},
update(el,binding){//改变binding时执行
el.style.background = binding.value
},
})
简写:
vue.directive("hello",function(el,binding){//初次插入和更新时都会调用
el.style.background = binding.value
})
使用:<div v-hello="red"></div>
为什么组件中的 data 是一个函数?
如果data是一个对象的话, 对象是一个引用类型; 它会在堆空间中开辟一片区域, 将内存地址存入. 这就使得所有的组件公共一个data, 当一个组件中修改了data中的数据就会出现一变全变的现象. 如果是一个函数的话,只有函数才有作用域的问题, 当组件被复用的时候; 函数与函数之间是有作用域的, 所以两个不同的作用域之间, 也是互不干扰的,每一次调用就会执行data函数然后返回新的数据对象,这样,可以避免多处调用之间的数据互相干扰。就能让每个实例都有自己的data对象。 在 new Vue() 中,可以是函数也可以是对象,因为根实例只有一个,不会产生数据污染。 在组件中,data 必须为函数,目的是为了防止多个组件实例对象之间共用一个 data,产生数据污染。
Vue.$nextTick
因为dom是异步更新的,数据是立即更新的,所以当数据更新后 ,想立刻执行dom更新的话需要加nexttick才可以,否则不生效
this.data = [0,1,2,3];
this.$nextTick(()=>{
dom操作
// 监听当前上文的data数据的更新,等着dom插入并且update也更新完之后,浏览器空闲时后执行这个回调函数,并且只会执行一次
})
使用场景:
- 如果想要在修改数据后立刻得到更新后的
DOM结构,可以使用Vue.nextTick() - 在
created生命周期中进行DOM操作
keep-alive 是什么?
<div>
<keep-alive>
<component is="child1"></component>
</keep-alive>
</div>
- 作用:实现组件来回切换的时候,不清除缓存组件缓存,提升性能。
router 和 route 的区别?
$router是VueRouter的实例对象,是一个全局的路由对象,包含了所有路由的对象和属性。$route是一个跳转的路由对象,可以认为是当前组件的路由管理,指当前激活的路由对象,包含当前url解析得到的数据,可以从对象里获取一些数据,如:name,path,params,query等。
vue-router 的路由传参方式?
- 声明式导航
router-link:
<router-link :to="'/users?userId:1'"></router-link>
<router-link :to="{ name: 'users', params: { userId: 1 } }"></router-link>
<router-link :to="{ path: '/users', query: { userId: 1 } }"></router-link>
2. 编程式导航 router-push:
* 通过`query`传参
<!---->
this.$router.push({
path: '/users',
query: {
userId: 1
}
});
// 路由配置
{
path: '/users',
name: 'users',
component: User
}
// 跳转后获取路由参数
this.$route.query.userId
* 动态路由
<!---->
this.$router.push('/users/${userId}');
// 路由配置
{
path: '/users/:userId',
name: 'users',
component: User
}
// 跳转后获取路由参数
this.$route.params.userId
* 通过`params`传参
<!---->
this.$router.push({
name: 'users',
params: {
userId: 1
}
});
// 路由配置
{
path: '/users',
name: 'users',
component: User
}
// 跳转后获取路由参数
this.$route.params.userId // 为 1
12.路由有哪些模式呢?又有什么不同呢?
1.hash模式 通过#号后面的内容的更改,触发hashchange事件,实现路由切换
一般网址是http:localhost:8080/#/home
location.hash的值就是url中#后面的东西。它的特点在于:hash虽然出现url中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。- 可以为hash的改变添加监听事件
window.addEventListener("hashchange", funcRef, false),每一次改变hash (window.location.hash),都会在浏览器的访问历史中增加一个记录,利用hash的以上特点,就可以实现前端路由更新视图但不重新请求页面的功能了。
特点:兼容性好但是不美观
2.history模式:通过pushState和replaceState切换url,实现路由切换,需要后端配合
-
一般网址是http:localhost:8080/home
-
由默认的hash改为history方法:new Router({mode:"history"})
-
利用 HTML5 History Interface 中新增的`pushState()`和`replaceState()`方法。 -
这两个方法应用于浏览器的历史记录栈,在当前已有的`back`、`forward`、`go` 的基础上(使用`popState()`方法),他们提供了对历史记录进行修改的功能。 -
这两个方法有个共同点:当调用他们修改浏览器历史记录栈后,虽然当前url改变了,但浏览器不会刷新页面,这就为单页面应用前端路由“更新视图但不重新请求页面”提供了基础 -
特点:虽然美观,但是刷新会出现 404 需要后端进行配置。
动态路由?
很多时候,我们需要将给定匹配模式的路由映射到同一个组件,这种情况就需要定义动态路由。例如,我们有一个 User组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用动态路径参数(dynamic segment)来达到这个效果:{path: '/user/:id', compenent: User},其中:id就是动态路径参数。
项目的目录结构:

-
/:绝对路径,指向public文件夹中的文件。 -
./:相对路径,指向当前文件所在目录。 -
../:相对路径,指向当前文件的父级目录。 -
在 Vue 开发中,推荐:
public文件夹中的文件使用/引用。src文件夹中的文件使用./、../或@引用。
了解哪些 Vue 的性能优化方法?
- 路由懒加载。有效拆分应用大小,访问时才异步加载。
keep-alive缓存页面。避免重复创建组件实例,且能保留缓存组件状态。v-for遍历避免同时使用v-if。实际上在 Vue 3 中已经是一个错误用法了。- 长列表性能优化,可采用虚拟列表。
v-once。不再变化的数据使用v-once。- 事件销毁。组件销毁后把全局变量和定时器销毁。
- 图片懒加载。
- 第三方插件按需引入。
- 子组件分割。较重的状态组件适合拆分。
- 服务端渲染。
对 SPA 的理解?
-
概念:
SPA(Single-page application),即单页面应用,它是一种网络应用程序或网站的模型,通过动态重写当前页面来与用户交互,这种方法避免了页面之间切换时打断用户体验。在SPA中,所有必要的代码(HTML、JavaScript 和 CSS)都通过单个页面的加载而检索,或者根据需要(通常是响应用户操作)动态装载适当的资源并添加到页面。页面在任何时间点都不会重新加载,也不会将控制转移到其他页面。举个例子,就像一个杯子,上午装的是牛奶,中午装的是咖啡,下午装的是茶,变得始终是内容,杯子始终不变。 -
SPA与MPA的区别:
MPA(Muti-page application),即多页面应用。在MPA中,每个页面都是一个主页面,都是独立的,每当访问一个页面时,都需要重新加载 Html、CSS、JS 文件,公共文件则根据需求按需加载。SPA MPA 组成 一个主页面和多个页面片段 多个主页面 url模式 hash模式 history模式 SEO搜索引擎优化 难实现,可使用SSR方式改善 容易实现 数据传递 容易 通过url、cookie、localStorage等传递 页面切换 速度快,用户体验良好 切换加载资源,速度慢,用户体验差 维护成本 相对容易 相对复杂 -
SPA的优缺点:
优点:-
具有桌面应用的即时性、网站的可移植性和可访问性
-
用户体验好、快,内容的改变不需要重新加载整个页面
-
良好的前后端分离,分工更明确
缺点:
-
不利于搜索引擎的抓取
-
首次渲染速度相对较慢
-
对Vuex的理解?
-
概念:
Vuex 是 Vue 专用的状态管理库,它以全局方式集中管理应用的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 -
解决的问题:
Vuex 主要解决的问题是多组件之间状态共享。利用各种通信方式,虽然也能够实现状态共享,但是往往需要在多个组件之间保持状态的一致性,这种模式很容易出问题,也会使程序逻辑变得复杂。Vuex 通过把组件的共享状态抽取出来,以全局单例模式管理,这样任何组件都能用一致的方式获取和修改状态,响应式的数据也能够保证简洁的单向流动,使代码变得更具结构化且易于维护。 -
什么时候用:
Vuex 并非是必须的,它能够管理状态,但同时也带来更多的概念和框架。如果我们不打算开发大型单页应用或应用里没有大量全局的状态需要维护,完全没有使用Vuex的必要,一个简单的 store 模式就够了。反之,Vuex将是自然而然的选择。 -
用法:
Vuex 将全局状态放入state对象中,它本身是一颗状态树,组件中使用store实例的state访问这些状态;然后用配套的mutation方法修改这些状态,并且只能用mutation修改状态,在组件中调用commit方法提交mutation;如果应用中有异步操作或复杂逻辑组合,需要编写action,执行结束如果有状态修改仍需提交mutation,组件中通过dispatch派发action。最后是模块化,通过modules选项组织拆分出去的各个子模块,在访问状态(state)时需注意添加子模块的名称,如果子模块有设置namespace,那么提交mutation和派发action时还需要额外的命名空间前缀。
页面刷新后Vuex 状态丢失怎么解决?
Vuex 只是在内存中保存状态,刷新后就会丢失,如果要持久化就需要保存起来。
localStorage就很合适,提交mutation的时候同时存入localStorage,在store中把值取出来作为state的初始值即可。
也可以使用第三方插件,推荐使用vuex-persist插件,它是为 Vuex 持久化储存而生的一个插件,不需要你手动存取storage,而是直接将状态保存至 cookie 或者 localStorage中。
diff 算法
-
概念:
diff算法是一种对比算法,通过对比旧的虚拟DOM和新的虚拟DOM,得出是哪个虚拟节点发生了改变,找出这个虚拟节点并只更新这个虚拟节点所对应的真实节点,而不用更新其他未发生改变的节点,实现精准地更新真实DOM,进而提高效率。 -
对比方式:
diff算法的整体策略是:深度优先,同层比较。比较只会在同层级进行, 不会跨层级比较;比较的过程中,循环从两边向中间收拢。
-
首先判断两个节点的
tag是否相同,不同则删除该节点重新创建节点进行替换。 -
tag相同时,先替换属性,然后对比子元素,分为以下几种情况:- 新旧节点都有子元素时,采用双指针方式进行对比。新旧头尾指针进行比较,循环向中间靠拢,根据情况调用
patchVnode进行patch重复流程、调用createElem创建一个新节点,从哈希表寻找key一致的VNode节点再分情况操作。 - 新节点有子元素,旧节点没有子元素,则将子元素虚拟节点转化成真实节点插入即可。
- 新节点没有子元素,旧节点有子元素,则清空子元素,并设置为新节点的文本内容。
- 新旧节点都没有子元素时,即都为文本节点,则直接对比文本内容,不同则更新。
- 新旧节点都有子元素时,采用双指针方式进行对比。新旧头尾指针进行比较,循环向中间靠拢,根据情况调用
虚拟DOM
-
概念:
虚拟DOM,顾名思义就是虚拟的DOM对象,它本身就是一个JS对象,只不过是通过不同的属性去描述一个视图结构。 -
虚拟DOM的好处:
(1) 性能提升
直接操作DOM是有限制的,一个真实元素上有很多属性,如果直接对其进行操作,同时会对很多额外的属性内容进行了操作,这是没有必要的。如果将这些操作转移到JS对象上,就会简单很多。另外,操作DOM的代价是比较昂贵的,频繁的操作DOM容易引起页面的重绘和回流。如果通过抽象VNode进行中间处理,可以有效减少直接操作DOM次数,从而减少页面的重绘和回流。
(2) 方便跨平台实现
同一VNode节点可以渲染成不同平台上对应的内容,比如:渲染在浏览器是DOM元素节点,渲染在Native(iOS、Android)变为对应的控件。Vue 3 中允许开发者基于VNode实现自定义渲染器(renderer),以便于针对不同平台进行渲染。 -
结构:
没有统一的标准,一般包括tag、props、children三项。
tag:必选。就是标签,也可以是组件,或者函数。
props:非必选。就是这个标签上的属性和方法。
children:非必选。就是这个标签的内容或者子节点。如果是文本节点就是字符串;如果有子节点就是数组。换句话说,如果判断children是字符串的话,就表示一定是文本节点,这个节点肯定没有子元素。
v-if 和 v-for 为什么不建议放在一起使用?
Vue 2 中,v-for的优先级比v-if高,这意味着v-if将分别重复运行于每一个v-for循环中。如果要遍历的数组很大,而真正要展示的数据很少时,将造成很大的性能浪费。
Vue 3 中,则完全相反,v-if的优先级高于v-for,所以v-if执行时,它调用的变量还不存在,会导致异常。
通常有两种情况导致要这样做:
- 为了过滤列表中的项目,比如:
v-for = "user in users" v-if = "user.active"。这种情况,可以定义一个计算属性,让其返回过滤后的列表即可。 - 为了避免渲染本该被隐藏的列表,比如
v-for = "user in users" v-if = "showUsersFlag"。这种情况,可以将v-if移至容器元素上或在外面包一层template即可。
关于 Vue SSR 的理解?
SSR即服务端渲染(Server Side Render),就是将 Vue 在客户端把标签渲染成 html 的工作放在服务端完成,然后再把 html 直接返回给客户端。
- 优点:
有着更好的 SEO,并且首屏加载速度更快。 - 缺点:
开发条件会受限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。服务器会有更大的负载需求。