Vue常见的面试题
1. 什么是 MVVM
Model层:数据模型层,指后端传递的数据
View层:视图层,指的是看到的页面
ViewModel层:视图模型层,是连接 View层 和 Model层 的桥梁
主要负责暴露数据给 View层 中的数据绑定声明、指令声明、事件绑定声明,进行实际的业务逻辑实现。
-
数据变化了,试图自动更新 ==> ViewModel 底层会做好监听
Object.defineProperty,当 Model层 数据变化时,View层 会自动更新。 -
视图变化了,绑定的数据自动更新 ==> ViewModel 会监听双向绑定的表单元素的变化,一旦 View层 的视图发生变化,绑定数据的Model层也会得到自动更新。
2. v-show 和 v-if 的区别
相同点
都用于条件渲染,用来控制元素的显示和隐藏
不同点
-
实现本质不同
v-show 通过css的display样式属性进行显示和隐藏
v-if 动态的对DOM元素进行添加和删除 -
编译的区别
v-show 有更高的初始渲染消耗
v-if 有更高的切换性能消耗 -
编译条件的区别
v-show 不管初始条件是什么,元素都会被渲染,并且只是简单的基于css进行切换
v-if 如果在初始渲染时条件为false,则什么也不会执行,一直到第一次条件变为true时,才会开始渲染条件快。 -
性能的比较
v-show 适用于需要频繁切换条件的场景
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景
场景
- 如果元素需要频繁的进行切换,推荐使用 v-show
- 如果元素在运行时很少改变条件,推荐使用 v-if
3.vue中的 key 作用
如果列表中数据的位置会 动态改变 或者有新的项目添加到列表中,并且希望列表中的数据保持自己的特征和状态,例如:input中输入的内容,switch的选中状态,都需要使用 :key 来 指定列表中的数据的唯一标识,提高渲染性能。
代码演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div class="app">
<div>
<label>Id:
<input type="text" v-model="id">
</label>
<label>Name:
<input type="text" v-model="name">
</label>
<input type="button" value="添加" @click="add">
</div>
<p v-for="item in list" :key="item.id">
<input type="checkbox">{{item.id}} --- {{item.name}}
</p>
</div>
</div>
<script>
var vm = new Vue({
el: '.app',
data: {
id: '',
name: '',
list: [
{ id: 1, name: '李斯' },
{ id: 2, name: '嬴政' },
{ id: 3, name: '赵高' },
{ id: 4, name: '韩非' },
{ id: 5, name: '荀子' }
]
},
methods: {
add() {
this.list.unshift({ id: this.id, name: this.name })
}
}
})
</script>
</body>
</html>
4.computed 和 watch 和 methods 的区别
作用
computed:在模版中放入太多的逻辑会让模版过重且难以维护,因此对于任何复杂逻辑,我们都需要放在计算属性中。watch:在数据变化时执行异步或开销较大的操作时,来响应数据的变化。metchods:表示一个具体的操作,主要书写业务逻辑。
区别
-
使用场景的区别
computed:是计算属性,依赖其他的属性计算所得出的最后的值,用于定义基础数据data之上的数据newData。
watch:是去监听一个值的变化,然后执行相应的函数,用于在某个数据变化时做一些操作。
methods:执行相应的业务逻辑。 -
是否支持缓存
computed:支持缓存,只有依赖数据发生变化,才会重新进行计算。
watch:不支持缓存,数据变化,直接会触发相应的操作。
methods:不支持缓存,每当触发重新渲染时,调用方法将会再次执行函数。 -
写法上的区别
computed:通常就是简单的计算,内部必须有返回值。
watch:回调里面会传入监听属性的新值和旧值,通过这两个值可以做一些特定的操作。
methods:执行相关的业务逻辑。
使用场景
-
当我们需要进行数值计算,并且依赖于其他数据时,应该使用
computed,因为可以利用computed的缓存特性,避免每次获取值时,都要重新计算,或者我们需要在插值表达式内放入太多逻辑时,可以对逻辑的处理进行抽取。 -
当有一些数据、功能需要随着其他数据的变化而变化时,或者需要在数据变化时执行异步或开销较大的数据时,应该使用
watch
5.vue 中是如何检测数组变化的
背景
由于JavaScript的限制(性能问题),vue不能检测数组和对象的动态变化,这里的不能检测数组和对象的变化,是指改变对象的某一个属性,例如属性的添加或移除,检测不到属性的动态变化,数组也是同理。因为没有使用Object.defineProperty()对数组、对象的属性动态进行拦截。
场景
vue不能检测以下数组的变动:
-
利用索引直接设置一个数组项,例如:
vm.items[indexOfltem] = newValue -
修改数组长度,例如:
vm.item.length = newLength
解决方案
- 重写了数组原型上的方法,即将data中的数组,进行了原型链重写。指向了自己定义的数组原型方法,这样当调用数组
api时,就可以通知依赖更新,这些重写了原型链的数组方法,我们称之为变更方法。
push()pop()shift()unshift()splice()sort()reverse()
代码演示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>
<span>
<input type="text" v-model='fname'>
<button @click='add'>添加</button>
<button @click='del'>删除</button>
<button @click='change'>替换</button>
</span>
</div>
<ul>
<li :key='index' v-for='(item,index) in list'>{{item}}</li>
</ul>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
Vue数组操作
1、变更方法:会影响数组的原始数据的变化。
2、替换数组:不会影响原始的数组数据,而是形成一个新的数组。
*/
var vm = new Vue({
el: '#app',
data: {
fname: '',
list: ['apple','orange','banana']
},
methods: {
add: function(){
this.list.push(this.fname);
},
del: function(){
this.list.pop();
},
change: function(){
this.list = this.list.slice(0,2);
}
}
});
</script>
</body>
</html>
vue.setvm.$set
向响应式对象中添加一个property,并确保这个新的property同样是响应式的,且触发视图更新。
代码演示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li v-for='item in list'>{{item}}</li>
</ul>
<div>
<div>{{info.name}}</div>
<div>{{info.age}}</div>
<div>{{info.gender}}</div>
</div>
<button @click="change">改变</button>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
动态处理响应式数据
*/
var vm = new Vue({
el: '#app',
data: {
list: ['apple', 'orange', 'banana'],
info: {
name: 'lisi',
age: 12
}
},
methods: {
change () {
this.$set(this.list, 1, '亚瑟')
this.$set(vm.info, 'gender', 'female');
}
}
});
// vm.list[1] = 'lemon';
// Vue.set(vm.list, 2, 'lemon');
// vm.$set(vm.list, 1, 'lemon');
// vm.info.gender = 'male';
// vm.$set(vm.info, 'gender', 'female');
</script>
</body>
</html>
6.对Vue生命周期的理解
1.概念
Vue实例从创建到销毁的过程,就是生命周期,也就是从创建、初始化数据、编译模板、挂载Dom → 渲染、更新→ 渲染、卸载等一系列过程,我们称之为Vue的生命周期。
2.图示
代码演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="app">
<input type="button" value="修改msg" @click="msg='No'">
<h3 id="h3">{{ msg }}</h3>
</div>
<script>
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
el: '#app',
data: {
msg: 'ok'
},
methods: {
show() {
console.log('执行了show方法')
}
},
beforeCreate() { // 这是我们遇到的第一个生命周期函数,表示实例完全被创建出来之前,会执行它
// console.log(this.msg)
// this.show()
// 注意: 在 beforeCreate 生命周期函数执行的时候,data 和 methods 中的 数据都还没有没初始化
},
created() { // 这是遇到的第二个生命周期函数
// console.log(this.msg)
// this.show()
// 在 created 中,data 和 methods 都已经被初始化好了!
// 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
},
beforeMount() { // 这是遇到的第3个生命周期函数,表示 模板已经在内存中编辑完成了,但是尚未把 模板渲染到 页面中
// console.log(document.getElementById('h3').innerText)
// 在 beforeMount 执行的时候,页面中的元素,还没有被真正替换过来,只是之前写的一些模板字符串
},
mounted() { // 这是遇到的第4个生命周期函数,表示,内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了
// console.log(document.getElementById('h3').innerText)
// 注意: mounted 是 实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其它操作的话,这个实例,就静静的 躺在我们的内存中,一动不动
},
// 接下来的是运行中的两个事件
beforeUpdate() { // 这时候,表示 我们的界面还没有被更新【数据被更新了吗? 数据肯定被更新了】
/* console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
console.log('data 中的 msg 数据是:' + this.msg) */
// 得出结论: 当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时 data 数据是最新的,页面尚未和 最新的数据保持同步
},
updated() {
console.log('界面上元素的内容:' + document.getElementById('h3').innerText)
console.log('data 中的 msg 数据是:' + this.msg)
// updated 事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
}
});
</script>
</body>
</html>
7.谈谈你对 keep-alive 的了解
1.理解
keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,实现组件的缓存,当组件切换时不会对当前组件进行卸载,避免重新渲染,它自身不会渲染一个DOM元素,也不会出现在组件的父组件链中。当组件在<keep-alive>内被切换,它的activated和deactivated的两个生命周期钩子函数将会被对应执行。
2.场景
keep-alive一般结合路由和动态组件一起使用,用于缓存组件。
众所周知动态组件是根据不同条件而动态切换组件,当组件由渲染到被销毁,再由销毁变为渲染状态,其内部的数据都要初始化并且所有的声明周期都会重新调用。如果内部的组件存在一些异步调用或者资源申请,会造成性能浪费,如果使用keep-alive,由于其包含的组件都会被缓存到内存中,那么他就会避免被多次销毁和初始化。
8.组件中的data为什么是一个函数
为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象
- 因为组件是用来复用的,且
js里对象是引用关系。 - 如果组件中
data是一个对象,就使得所有组件实例共用了一份data,就会造成一个组件数据改变其他组件也跟着变。 - 如果组件中的
data选项是一个函数,数据以函数返回值的形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每一个组件实例创建一个私有的数据空间,让个组价实例维护各自的数据 - 而
new Vue的实例,是不会被复用的,因此不存在引用对象的问题。
9.Vue 组件间通信有哪几种方式
父传子
子组件内定义 props:['变量名']
父组件的标签内 :变量名 = '父组件需要向子组件传递的数据'
// family父组件
<template>
<div >
<com-article :family="familyList"></com-article>
</div>
</template>
<script>
import home from './test/home.vue'
export default {
name: 'HelloWorld',
components: { home },
data() {
return {
familyList: ['张一一', '冯家祺', '张美丽']
}
}
}
</script>
// 子组件 home.vue
<template>
<div>
<span v-for="(item, index) in family" :key="index">{{item}}</span>
</div>
</template>
<script>
export default {
props: ['family']
}
</script>
子传父
子组件内的定义 this.$emit('自定义事件名',需要传递的数据)
父组件的标签内 @'自定义事件名' = 'methods的方法名'
10.Vuex提供了哪些核心方法
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。它包含 5 个核心的概念
State、Mutations、Getters、Actions、Modules
state:定义了应用中的数据源,即设置全局共享的数据源。mutations:唯一更改store中状态的方法,且必须是同步函数。getters:允许数组从store中获取数据对数据进行格式化或者提取,类似于计算属性。actions:处理Vuex中的异步操作,用于提交mutation,而不是直接变更状态。modules:允许将单一的store拆分为多个store,且同时保存在单一的状态树中。