本次让我们系统的熟悉vue的面试常用题目,无论是给准备面试还是准备跳槽的小伙伴,一个学习以及温故知新的版块。这其中加入了我的理解,我尽量用白话文和易懂的语言形式进行解释,如果有地方解释的不正确或者不准确的情况,还请大家不吝赐教。
2022年大环境的不景气,让我们用知识充实自己,面对生活的一次次挑战与考验,加油!
入门级:
1:什么是mvvm?
MVVM是Model-View-ViewModel的简写,是M-V-VM三部分组成。它本质上是MVC的改进版本,MVVM就是将其中的View的状态和行为抽象化,其中ViewModel将视图 UI 和业务逻辑分开,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。
MVVM采用双向数据绑定,view中数据变化将自动反映到viewmodel上,反之,model中数据变化也将会自动展示在页面上。把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。
让我们开发人员可以专注于业务逻辑方面(ViewModel)
,对于数据的绑定与展示(自动更新dom)
,不需要过多的dom操作。可以理解为数据(medel)
驱动UI(view)
,而UI(view)
的改变也会影响数据(medel)
。他们之间就是靠ViewModel来实现。
因此开发者只需要关注业务逻辑,不需要手动操作 DOM,也不需要关注数据状态的同步问题,这些都由 MVVM 统一管理。
2:为什么要用vue?
1.虚拟dom:dom操作是非常的消耗性能的,不再使用原生的dom操作节点的方式,模拟一个虚拟dom树,运用deff算法,只更新修改的dom节点,极大的释放了dom操作。
2.视图,数据,结构的分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能晚餐相关操作(mvvm)
3.组件化:把一个单页应用中的各个模块拆分到一个个单独的组件中,各个组件有自己的行为逻辑以及自己的样式,相互不干扰。便于开发,以及后期维护。相同组件的公用也大大节约了开发成本。
3:vue的生命周期(11个钩子函数)
1.beforeCreate(创建前):在此生命周期函数执行的时候,data和methods中的数据还没有初始化。
2.created(创建后):在这个生命周期中,data 和 methods 都已经被初始化好了,可以访问到data中的数据以及methods的方法。
3.beforeMount(载入前):在此生命周期函数执行的时候,模板已经在内存中编译好了,但是尚未挂载到页面中去,此时页面还是旧的。
4.mounted(载入后):此时页面和内存中是最新数据,dom树创建,此时才可以操作dom节点。
5.beforeUpdate(更新前):顾名思义,这个时候页面正准备进行更新,只是还未开始更新动作,所以页面中的数据还是旧的,但是data中的数据已经是新的,只是页面并未和最新数据同步。
6.Updated(更新后):此时页面显示数据和最新的 data 数据同步。
7.beforeDestroy(销毁前):组件销毁前的钩子函数,此时实例上的data,methods,以及过滤器等都处于可用状态。我们一般可以在这个钩子内清除当前组件的定时器(clearInterval() )
。
8.destroyed(销毁后):此时组件以及被完全销毁,实例中的所有的数据、方法、属性、过滤器…等都已经不可用了
// 以上8个生命周期钩子函数是我们常用的钩子函数,接下来的3个钩子函数可以在特定的时候使用,方便我们理解
9.activated(组件激活时):在使用被keep-alive缓存的组件时,进入函数。如果一个子页面或者一个组件被keep-alive包含,那么当这个页面或组件第一次时会走一次完整的生命周期,当此组件无法被销毁。等重复使用的时候,会从缓存里面直接调出,此时组件不在执行前4个生命周期钩子(因为它并没有被销毁,不需要重新创建)
。取而代之的是进入activated钩子函数。表示该页面或组件被激活。
10.deactivated(组件未激活时):实例没有被激活时(同9)。
11.errorCaptured(错误调用):当捕获一个来自后代组件的错误时被调用
注意:有很多面试会问父子组件创建销毁的生命周期顺序:
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted
-> 父mounted
即父组件走到beforeMount 后,开始走完子组件的完整生命周期,然后再走父组件mounted
这样理解:
1.有父才有子,所以一开始是父组件先创建实例。
2.mounted本身就是挂在到页面上的钩子,所以必须要等所有的子组件完成后再一起挂载到页面中。(既然是父,当然要包含所有的子)
4:watch、computed 和 methods 的区别
- methods 方法:组件内部的业务逻辑方法的集合
- computed 计算属性:依赖已有的变量来计算一个目标变量,例如根据data的变量来计算新的变量,用来格式化。或者引用store,...mapState的语法糖引用state。
(computed是有缓存的,注意不能进行异步操作)
- watch 监听:监听某一个变量的变化,并且执行相应的回调函数
(watch没有缓存,目标变化一次执行一次,可以进行异步操作)
许多面试官喜欢问computed 和watch的区别,这里请多加理解
5.Vue.js的特点
- 简洁:页面由HTML模板+Json数据+Vue实例组成,结构简单易懂
- 数据驱动:自动计算属性和追踪依赖的模板表达式
- 组件化:用组件来构造页面,可复用,维护方便
- 轻量:代码量小,不依赖其他库
- 快速:精确有效批量 DOM 更新
- 模板友好:可通过 npm,bower 等多种方式安装,很容易融入
6:插槽的理解
插槽用于决定将所携带的内容,插入到子组件指定的某个位置,但内容必须在父组件中子组件的标签内定义,在子组件中用标签接收。slot 是组件内部的占位符。即组件编写时留给组件使用者的一个插入口。使用者可以在此编写自己的html格式以及样式,插入的位置由编写者制定
7.Vue组件之间传参
1.父传子:
- 父组件通过props方式传递数据
- 父组件也可以直接选择子节点的形式选中子组件的实例
this.$refs.nodeName // refs是根据组件名字来取的响应组件的属性
this.$children[2] // 返回一个数组(使用几率小,一般不这样调用)
2.子传父:
- 子组件用$emit方法,触发父组件的监听
- this.$parnet获取当前组件的父组件实例
3.兄弟组件之间(这里写的是兄弟组件之间,实际上是所有组件之间都可以,包括父子)
:
- 用一个事件总线,evenBus。新建一个空的vue实例作事件总线
(为了方便调用一般绑定到vue原型)
,然后用$on
监听事件,用$emit
触发事件。
this.$eventBus.$on('sayName',(data)=>{
console.log(data)
}) // 监听事件总线中sayName方法
this.$eventBus.$emit('sayName',data)
详细讲解请移步到:https://www.jianshu.com/p/267e17c59d32
- Vuex:Vuex 是一个专门为 Vue.js 应用程序开发的状态管理模式。在下面的复习中我们会详细讲解到
4.浏览器本地缓存,例如localStorage,sessionStorage
8.vue 组件中的 data 为什么是一个函数
data 是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。Object 是引用数据类型
,如果不用 function 返回,每个组件的 data 都是内存的同一个地址(储存的是地址,指针,此处可以复习浅拷贝和深拷贝)
,一个数据改变了其他也改变了。
理解就是运用了function的函数作用域让他变成局部作用域,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间
9.路由懒加载(首页加载优化)
在单页应用中,如果没有应用懒加载,运用 webpack 打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,延时过长,不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
原理:vue 异步组件技术:异步加载,vue-router 配置路由 , 使用 vue 的异步组件技术 , 实现按需加载
import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
// {
// path: '/',
// name: 'HelloWorld',
// component: HelloWorld
// }
{
path: '/',
name: 'HelloWorld',
component: () => import('@/components/HelloWorld.vue')
}
]
})
10:请说出 vue.cli 项目中 src 目录每个文件夹和文件的用法`
assets 文件夹是放静态资源;
components 是放组件;
router 是定义路由相关的配置;
view 视图;
app.vue 是一个应用主组件;
main.js 是入口文件
11.Vue 中 key 值的作用
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key 的作用主要是为了高效的更新虚拟 DOM。
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
12:vue 的指令
v-bind:给元素绑定属性,简写为
:
v-on:给元素绑定事件,简写为
@
v-html:给元素绑定数据,且该指令可以解析 html 标签
v-text:给元素绑定数据,不解析标签
v-model:数据双向绑定
v-for:遍历数组
v-show:条件渲染指令,将不符合条件的数据隐藏
(display:none)
v-if:条件渲染指令,动态在 DOM 内添加或删除 DOM 元素
(无节点)
v-else:条件渲染指令,必须跟 v-if 成对使用
v-else-if:判断多层条件,必须跟 v-if 成对使用
v-cloak:解决插值闪烁问题
v-once:只渲染元素或组件一次
v-pre:跳过这个元素以及子元素的编译过程,以此来加快整个项目的编译速度
13.v-for 与 v-if
因为v-for的优先级高于v-if,势必会先遍历数组,然后再判断是否显示,这样就会影响速度,尤其是只需要渲染很小一部分的时候,渲染了许多无用的节点,增加许多无用的dom操作,建议用computed先把需要的数组格式化出来
{{item}}
computed() {
list() {
return [1, 2, 3, 4, 5, 6, 7].filter(item => item !== 3)
}
}
14.Vue怎么兼容IE
使用 babel-polyfill 插件省省吧,2022年6月16日起ie都退役了
15.Vue 怎么重置 data
在很多情况下我们会遇到初始化data的时候,比如在调用element的dialog组件时(组件是v-show控制,会保留之前的状态)
Object.assign(this.$data, this.$options.data()) // 初始化data
16.route 和 router
- route 是
“路由信息对象”
,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
route查询修改当前路由的对象
- router 是
“路由实例对象”
,包括了路由的跳转方法(push、go),钩子函数等。
router获取当前路由实例,调用方法
17.Vue的修饰符有哪些?
常用修饰符:
- .stop 阻止事件继续传播
- .prevent 阻止标签默认行为
- .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
- .self 只当在 event.target 是当前元素自身时触发处理函数
- .once 事件将只会触发一次
- .passive 告诉浏览器你不想阻止事件的默认行为
- .right 使用鼠标右键触发事件
@click.right
中级:
1.虚拟 DOM 原理
虚拟 DOM,其实就是用对象的方式取代真实的DOM操作,把真实的DOM操作放在内存当中,在内存中的对象里做模拟操作。当页面打开时浏览器会解析 HTML 元素,构建一颗 DOM树,将状态全部保存起来,在内存当中模拟我们真实的 DOM操作,操作完后又会生成一颗 dom 树,两颗DOM树进行比较,根据 diff 算法比较两颗 DOM树不同的地方,只渲染一次不同的地方。
用diff算法把虚拟DOM和真实DOM比较,找出不同的地方,页面只渲染不同的地方
2:nextTick 的理解
Vue 是异步修改 DOM 的,并且不鼓励开发者直接接触 DOM,但是有时候需要必须对数据更改后的 DOM 元素做相应的处理,但是获取到的 DOM 数据并不是更改后的数据,这时候就需要 this.$nextTick();
很常见一个例子,在数据变化后马上要使用ref进行节点操作,由于vue是异步修改dom的,所以在数据变化后可能没有找到需要操作的dom,故而报错
methods() {
this.isShow = true;
this.$nextTick(()=>{
this.$refs.mydom.play() // 等待最新的dom更新了之后的回调,此时已经有了mydom节点,可以对其进行dom操作
})
},
3.不需要响应式的数据应该怎么处理?
在我们的Vue开发中,会有一些数据,从始至终都未曾改变过,这种死数据,既然不改变,那也就不需要对他做响应式处理了,不然只会做一些无用功消耗性能,比如一些写死的下拉框,写死的表格数据,这些数据量大的死数据,如果都进行响应式处理,那会消耗大量性能。
// 方法一:将数据定义在return 之外(初始化时期只会对data内的数据绑定getter和setter)
data () {
this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
return {}
}
// 方法二:Object.freeze()
data () {
return {
list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
}
}
4.vue 中的双向绑定原理
概念:双向绑定是vue的一个核心功能,所谓双向绑定就是当试图发生改变的时候传递给VM(ViewModel ),让数据得到更新,当数据发生改变的时候传给VM(ViewModel ),使得视图发生变化。
那么vue怎么做到的呢?
observer(观察者),劫持监听所有属性,什么意思呢?
vue.js是采用数据劫持
结合发布者-订阅者模式
的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。
先简单的实现一个js的双向数据绑定来熟悉一下Object.defineProperty()方法
输入的值为: