未经允许,禁止转载 持续更新中......
本文整理总结了vue常见面试题,以便前端开发爱好者学习,更好的理解vue框架,拿到满意的offer。如有不对之处,欢迎指正
1. 说一下MVVM和MVC的区别
-
MVVM是一种软件架构的模式。
-
M 代表 model数据模型(发送请求获取到的数据)
-
V 代表 View视图(页面)
-
VM 代表 ViewModel 视图模型 MVVM通过数据绑定和事件监听的方式,实现了数据的双向绑定。一是将模型转换为视图,也就是将获取到的数据转换为页面,实现的方式是数据绑定。二是将视图转换为模型,也就是将页面转换为数据,实现的方式是数据监听
-
-
MVC是Model-View-Controller的简写。即模型-视图-控制器。C指的是Controller页面业务逻辑,负责从视图读取数据,控制用户输入,并向模型发送数据。MVC是单向通信,也就是M和V必须通过Controller来承上启下
MVVM和MVC区别在于MVVM实现了数据和视图的自动同步,不需要我们手动的操作DOM,当model改变时,view会自动改变,反之亦然。
2. Vue响应式的原理是什么
Vue 的响应式原理是核心是通过 ES5 的 Object.defindeProperty 进行数据劫持,然后利用 get 和 set 方法进行获取和设置,data 中声明的属性都被添加到了get和set中,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
let data = {
name:"lis",
age: 20,
sex: "男"
}
// vue2.0实现 使用Object.defineProperty进行数据劫持
// Object.defineProperty接收三个参数 参数1:对象 参数2:对象的某个属性 参数3: 描述信息 对象
for(let key in data){
/** 当数据被劫持后 我们再访问对象的这个属性会访问不到 出现undefined
所以我们要把当前对象的属性赋值给一个临时变量,通过访问这个变量达到目的**/
let temp = data[key]
Object.defineProperty(data, key, {
get(){
return temp
},
//set函数数据变化时会自动调用,接收一个参数value,拿到的就是变化的值
set(value){
temp = value
}
})
}
// vue3.0实现 使用Proxy 进行数据的代理
let newData = new Proxy(data, {
get(target, key){
return target[key]
},
set(target, key, value){
target[key] = value
}
})
3. v-if和v-show的区别
- v-if实质是动态的创建和销毁DOM元素
- v-show 实质是控制元素的css属性display显示和隐藏 使用场景: 需要频繁切换的时候使用v-show,不需要频繁切换的时候使用v-for。这样可以节省性能开销,使用v-if频繁切换会增加性能消耗。
4. v-for和v-if为什么不能一起使用
如果同时出现v-for和v-if,无论判断条件是否成立,都会执行一遍v-for循环,这样浪费性能,所以要尽可能的避免两者一起使用。
5. vue中的修饰符都有哪些
-
事件修饰符:
-
.prevent阻止事件默认行为 -
.stop阻止事件冒泡 -
.capture设置事件的捕获机制 -
.self只有点击元素自身才会触发 -
.once事件只触发一次 -
.nativeelement-ui的修饰符,使用组件注册事件注册不上的时候使用
-
-
按键修饰符
.tab、.enter、.esc、.space、.delete、.up、.down、.left、.right -
v-model修饰符:
-
.trim去除首尾空格 -
.lazy只在输入框失去焦点或按回车键时更新内容,不是实时更新 -
.number将数据转换成number类型(原本是字符串类型) -
.sync父子组件传值,子组件想更新这个值,可以使用此修饰符简化
-
6. v-for为什么要加key
key的主要作用是高效的更新DOM,提高渲染性能。同时key属性可以避免数据混乱的情况出现。
原理: vue能够实现数据和视图实时更新,使我们不操作DOM可以直接操作数据,渲染页面,其原理是虚拟DOM和高效的diff算法。当页面数据发生变化的时候,diff算法会比较同一层级的节点。如果节点类型不同,直接删除前面的节点,再创建插入新的节点,而不会再去比较这个节点后面的子节点;如果节点类型相同,则会重新的设置该节点的属性,从而实现节点更新。使用key给每个节点做一个唯一的标识,diff算法就可以正确的识别此节点,就地更新找到正确的位置插入新的节点。
不加key的情况:如果不加key 在对比新旧虚拟dom的时候,diff算法对比同一层节点相同就复用,不同就重新设置,此时复用的时候就会出现数据混乱的情况
7. 说一下虚拟DOM是什么
用JavaScript的Object对象模拟真实DOM节点,对真实DOM进行抽象,通过特定的render方法将其渲染成真实的DOM节点。由于真实的DOM元素比较复杂,属性相对较多,虚拟DOM只提取了一些和渲染相关的属性。频繁的操作真实DOM会产生性能问题。
8. 为什么组件中的data是一个函数,new vue实例中data可以是一个对象
组件是用来复用的,组件中的data写成一个函数,数据以函数返回值形式定义,函数有独立的作用域,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,由于对象是引用类型,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。因为new vue里面的代码是不存在复用的情况,所以可以写成对象形式
9. computed和watch的区别是什么
-
计算属性computed:
-
支持缓存,只有依赖数据发生改变,才会重新进行计算
-
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
-
如果computed需要对数据修改,需要写get和set两个方法,当数据变化时,调用set方法。
-
computed擅长处理的场景:一个数据受多个数据影响,例如购物车计算总价
-
小知识点: 计算属性函数放在methods和computed的区别: 两者都能实现效果,但是放在methods中需要调用这个函数,用一次就会调用一次造成性能浪费,而放在computed中会缓存计算的结果,只有数据变化才会重新计算,不然都是直接调用的缓存结果
-
监听属性Watch:
-
不支持缓存,数据变,直接会触发相应的操作
-
watch支持异步;监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值
-
immediate:组件加载立即触发回调函数执行
-
deep:true的意思就是深入监听,任何修改obj里面任何一个属性都会触发这个监听器里的 handler方法来处理逻辑
-
watch擅长处理的场景:一个数据影响多个数据,例如搜索框
-
10. 说一下vue的生命周期
-
初始化阶段 : 创建vue实例,准备数据,准备模版,生成虚拟DOM,渲染成真实DOM
-
更新阶段 : 数据变化的时候,会进行新旧虚拟DOM的对比,找到差异化的部分,一次性更新真实DOM
-
销毁阶段 : 当调用vm.destroy()的时候,vue实例就会被销毁,释放掉相关资源,此时适合清除定时器
11. vue勾子函数有哪些
-
beforeCreate在data数据注入到vm实例之前,此时vm实例上并没有数据。适合做loading的一些渲染 -
Created在data数据注入到vm实例之后,此时vm身上已经有了数据。适合发送ajax请求 -
beforeMount生成的结构替换视图之前,此时DOM还没更新 -
mounted生成的结构替换视图之后,此时DOM树已经完成渲染到页面。适合进行一些DOM的操作 -
beforeUpdate数据变化了,DOM更新之前 -
updated数据变化了,DOM更新之后。适合监听数据的一些变化 -
beforeDestroy实例销毁之前,此时还可以访问到实例的数据。 适合销毁非vue资源,防止内存泄露,比如清除定时器。 -
destroyed实例销毁之后,此时组件已经销毁。 -
activated被keep-alive缓存的组件激活时调用。当我们运用了组件缓存时,如果想每次切换都发送一次请求的话,需要把请求函数写在activated中,而写在created或mounted中其只会在首次加载该组件的时候起作用。 -
deactivated被keep-alive缓存的组件失活时调用 -
errorCaptured组件报错的时候执行
12. 说一说vue中的keep-alive
-
keep-alive的话,就是我们在开发的过程中,有一些组件我们不希望频繁的重新加载,keep-alive可以保存组件的状态,也就是缓存组件,下次展示的时候,不用重新渲染,节约性能。
-
keep-alive提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高
场景: 使用vue在开发单页面应用的时候,我们通常会使用Vue-Router进行页面导航,在路由切换的时候,页面是会重新加载的,比如当用户浏览第几页对应的数据详情页面,查看返回之后,如果不加任何处理,列表页面此时会重新渲染,默认显示第一页的数据而不是用户之前浏览到的位置,这样给用户不好的体验,这个时候我们就可以使用keep-alive缓存组件的状态,这样组件就不会销毁和重建。我们就可以在activated和deactivated两个勾子函数中进行一些操作。
13. 有使用过vue中的$nextTick吗,说下它的原理
Vue修改数据后,视图不会立即更新,而是等同一个事件循环中所有的数据变化完成后,统一的更新视图,此时 DOM还没有更新,我们进行DOM的一些操作的时候就会找不到DOM元素报错。但我们又需要操作DOM,Vue中提供了一个$nextTick的方法,此方法会等待组件的DOM刷新之后,执行callback函数,从而保证了callback函数可以立即操作到更新后的DOM。只要DOM一渲染完成,立马执行回调函数。
$nextTick同时也支持Promise,如果没有给此方法传入回调函数,就会返回一个Promise,此时我们可以使用async和await等待dom更新完成
场景: 1、点击按钮显示输入框并获取输入框焦点
场景: 2、点击获取元素高度
14. v-model的原理
- v-model其实是一个语法糖,它主要提供了两个功能,view层输入值影响data的属性值,属性值变化会更新view层的值。我们可以给元素绑定v-bind指令并触发input事件来实现v-model,组件中我们可以给父组件一个v-model绑定值,子组件接收一个名字为value的属性值,并触发一个名字为input的事件来实现v-model
// 父组件
<Children v-model="isShow"> -- 此处相当于注册了一个input事件和v-bind绑定了名为value的值
//子组件接收
props:{
value: Boolean
},
// 子组件触发input事件
methods: {
show() {
this.$emit('input',false)
}
}
15. vue组件与组件之间是如何传数据的
- 父传子
// 父组件
<Children :list="list">
// 子组件 props接收
<script>
export default {
props: {
list: {
type: Array,
required: true
},
}
}
</script>
- 子传父
// 在父组件中给子组件绑定一个自定义的事件,子组件通过$emit()触发该事件并传值
// 父组件
<Children @showDialog="showFn">
// 子组件
<script>
export default {
methods: {
show() {
this.$emit('showDialog',false)
}
}
}
</script>
- 非父子组件
// event bus又叫事件总线,通用的组件通讯解决方案,可以实现任意两个组件的通讯
// 1、在main.js中创建bus对象,并挂载到原型上
const bus = new Vue()
Vue.prototype.bus = bus
// 2、提供数据的组件发布事件
this.bus.$emit('getData',value)
// 3、接收数据的组件订阅事件
this.bus.$on('getData',(value)=>{value就是接收的数据})
-
组件可以通过
$parent和$children获取父组件和子组件实例,进而获取数据 -
使用
$refs获取组件实例,进而获取数据 -
$attrs和$listeners,对一些组件进行二次封装时,方便传值
// $attrs 获取父组件传递的所有属性,但不包含class和style
// $listeners 获取父组件传递的所有事件,但不包含.native修饰符的
/**
总结: `$attrs`和`$listeners` 可以通过v-bind="$attrs"和v-on="$listeners"
向子组件传递父组件的属性和事件
*/
<Children @changeTab="changeTab" :color="color" :size="small"></Children>
-
使用
provide和inject,依赖注入,提供数据的组件provide,使用数据的组件inject -
再有的话就不属于vue了,local也可以传递参数
16. 路由传参方式的区别
- params传参,必须用name跳转,因为path跳转会忽略掉parmars,需要提供
路由的name或手写完整的带有参数的path
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
17. Vue 的父组件和子组件生命周期钩子函数执行顺序
Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
-
加载渲染过程:
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
-
子组件更新过程:
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
-
父组件更新过程
父 beforeUpdate -> 父 updated
-
销毁过程
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
18. Vue-Router路由有哪些方式
-
hash模式: 后面的 hash 值的变化,浏览器既不会向服务器发出请求,浏览器也不会刷新,每次 hash 值的变化会触发 hashchange 事件
-
history 模式:利用了 HTML5 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
19. Vue中style标签加scoped属性的作用和原理
作用: 实现组件的私有化,防止全部同名css污染
原理: scoped会在DOM结构和css样式上添加唯一的标识 data-v-xxxx 以达到类似限制作用域的效果
20. 说一说vue的优缺点
- 优点:
- 数据驱动
- 组件化
- 轻量级
- SPA单页面
- 3.0的界面化管理工具比较好用
- 容易入门
- 中文社区比较强大
- 缺点
- 不支持IE8以下的浏览器
- 比较耗内存:每个组件都会实例化一个Vue实例,实例的属性和方法很多
- 定义在data里面的对象,实例化时,都会递归的遍历转成响应式数据,然而有的响应式数据我们并不会用到,造成性能上的浪费
21. vue2.0和3.0响应式的区别
-
Object.defineProperty
-
用于监听对象的数据变化
-
无法监听到数组变化(下标、长度)
-
只能劫持对象的自身属性,动态添加的劫持不到
-
//vue2中对数组和动态属性劫持不到的解决办法
//1.动态属性
this.$set() //接收三个参数:需要新增属性的对象、新增的属性名、新增的属性值
this.$delete() //删除对象的某个属性
//2.数组 vue重写了数组的方法 push pop shift unshift splice sort reverse
//数组也可以使用this.$set方法,第二个参数改为下标就可以
-
Proxy MDN文档
-
Proxy 可以直接监听对象而非属性
-
Proxy 可以直接监听数组的变化
-
Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是Object.defineProperty 不具备的
-
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而Obejct.defineProperty 只能遍历对象属性直接修改
-
Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利
-
Object.defineProperty 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写
-
22. Vue组件中的定时器要怎么销毁
- 单个定时器
const timer = setInterval(() => {}, 500);
this.$once('hook:beforeDestroy',() => {
clearInterval(timer)
}
-
多个定时器
页面中多个定时器,我们可以在data选项中创建一个timer对象,给每个定时器取个名字一一映射在对象timer中,在beforeDestroy构造函数中 遍历timer对象,清除每个定时器
data() {
timer: {
...n个定时器
}
}
beforeDestroy() {
for(let k in this.timer) {
clearInterval(k)
}
}
23. 有使用过插槽吗? 简单说一下
当我们在封装组件的时候,有一些内容我们不希望写死,可以自定义组件的内容,我们就可以用插槽。插槽会将所携带的内容插入到指定的某个位置,具有模块化和更大的重用性。而插槽显不显示是由父组件控制的,插槽在哪里显示由子组件来控制
-
默认插槽 子组件写入slot,slot所在的位置就是父组件要显示的内容
-
具名插槽 slot添加name属性 父组件通过给template 添加对应的插槽名字
-
作用域插槽 子组件slot标签上绑定需要的值
<slot :data="user"></slot>;父组件使用v-slot="user"接收传递过来的值
24. vue中有哪些指令,能说出它们分别有什么作用吗
-
v-bind用于设置动态属性-
对class增强 动态控制class属性 值可以是一个数组或者对象
-
对style增强 动态控制style属性 值可以是一个数组或者对象
-
-
v-on用于注册事件 -
v-if和v-show控制元素的显示和隐藏 -
v-else和v-else-if配合v-if使用 -
v-model数据双向绑定 -
v-text等同于innerText 标签不生效 -
v-html等同于innerHtml 识别标签 -
v-for遍历
25. 你了解过vue3吗?相比vue2 vue3有哪些改变
-
数据响应式数据重新实现(Es6的proxy替代es5的Object.defineProperty)
-
源码使用typescript重写
-
虚拟Dom新算法
-
提供了compositon api,为了更好的逻辑复用和代码组织
-
自定义了渲染器(可以根据自己需求自定义各种各样的渲染器)
-
Fragment,模版可以有多个根元素 等等
26. vue模版编译过程
-
简单说,Vue的编译过程就是将template转化为render函数的过程。
-
首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
-
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
-
编译的最后一步是将优化后的AST树转换为可执行的代码。
27. vue的优化做过哪些
-
v-if和v-for不能一起使用
-
页面采用keep-alive缓存组件
-
区分v-if和v-show使用场景
-
v-for必须加key且保证key的唯一性
-
使用路由懒加载、异步组件、组件封装
-
防抖 节流
-
第三方模块的按需引入
-
图片路由懒加载
-
精灵图
-
代码压缩
28. 计算属性中的函数名可以和data数据中的名字相同吗
不可以,因为在初始化vue实例的时候,不管是计算属性还是data都会被挂载到实例上,而计算属性中的函数名和data重复,会覆盖掉data
29. methods方法中的函数名可以和data数据中的名字相同吗
不可以,因为vue源码中的 initData() 方法会取出 methods 中的方法进行判断,如果有重复的就会报错
30. 如何在子组件访问父组件的实例
-
this.$parent.event
-
$emit触发父组件的事件,父组件监听这个事件
-
父组件把方法传入子组件,子组件里直接调用这个方法
31. 说一说vue的优缺点
优点: 数据双向绑定、单页面、组件化、容易入门学习
缺点: 不支持ie8以下浏览器、耗内存,因为每个组件都会实例化一个实例,实例上面有很多属性和方法、定义在data里面的对象,实例化时,都会递归的遍历转成响应式数据,然而有的响应式数据我们并不会用到,造成性能上的浪费
32. 导航守卫的勾子函数执行顺序
-
全局类型的勾子函数
-
router.beforeEach全局前置守卫 -
router.beforeResolve全局解析守卫 -
router.afterEach全局后置守卫 没有next参数
-
-
路由勾子函数
router.beforeEnter路由进入之前
-
组件内的勾子函数
-
beforeRouteEnter组件进入之前 -
beforeRouteUpdate组件更新之前 -
beforeRouteLeave组件离开之前
-
如果是更新组件执行顺序是:beforeEach > beforeRouteUpdate > beforeResolve > afterEach
33. vue中不需要响应式的数据如何处理?
开发过程中,会遇到一些非响应式的数据,比如某些selected下拉框的选项,如果我们都定义到data里面是会增加性能消耗的
- 定义在data外面
data(){
this.options = [{}...]
return {
}
}
- 使用Object.freeze()冻结对象或数组,被冻结的对象属性不能被修改
data(){
return {
options: Object.freeze([])
}
}
34. vue中自定义指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
自定义指令包含了以下几个钩子函数:
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind:只调用一次,指令与元素解绑时调用。
这些钩子函数接受以下几个参数:
el:指令所绑定的元素,可以用来直接操作 DOM。binding:一个对象,包含以下 property:name:指令名,不包括v-前缀。value:指令的绑定值oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用。无论值是否改变都可用- (还有几个不常用就不列举额)
vnode:Vue 编译生成的虚拟节点。oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中可用。
函数简写,bind和update时候执行:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
35. vue中的mixin混入
vue2选项式api,抽离逻辑变得复杂,vue提供了一种混入的方式来提取复用逻辑,并在组件中合并执行
// 定义混入对象mixin
var myMixin = {
data: function(){
return {
name: 'zz'
}
}
created: function () { this.hello() },
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 组件内使用
var Component = Vue.extend(
{
mixins: [myMixin]
}
)
- 合并数据对象时,发生重名冲突以组件数据优先
- 合并同名钩子函数时,比如
created等等,组件和mixin都会执行,并且mixin先执行 - 合并其他对象选项时,比如
methods、components和directives合并为同一个对象,如果有重名冲突,取值组件的
36. 相同的路由组件如何重新渲染
- router-view上加一个key
37. vue插件机制
如同element-ui一样,我们在使用的时候通过Vue.use(),Vue也提供了一个方法支持自定义插件,使我们可以添加一些全局的方法或属性、指令、mixin以及 为Vue实例添加一些方法。插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}