前言
用本文来总结一下Vue初级面试题。
一、什么是MVVM
- MVVM 是 Model-View-ViewModel 的缩写。
- Model代表数据模型,也可以在Model中定义操作数据变化的业务逻辑;
- View 代表UI视图,它负责将数据模型转化成UI 展现出来;
- ViewModel 监听Model中数据的改变和控制View层的展现;
- 在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上;
- ViewModel通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
二、钩子函数有哪些,简单介绍一下
beforeCreate
:实例创建前被调用;created
:实例创建后被调用,完成数据观测,属性和方法的运算,watch/event事件回调,模板渲染成html前(vm.$el未定义)故数据初始化最好在这阶段完成;beforeMount
:在$el
挂载前被调用,相关的 render 函数首次被调用,期间将模块渲染成html,此时vm.$el
还是未定义;mounted
:在$el
挂载后被调用,此时vm.$el
可以调用,不能保证所有的子组件都挂载,要等视图全部更新完毕用vm.$nextTick()
;beforeUpdate
:数据更新时调用;updated
:数据更新后调用;activated
:<keep-alive></keep-alive>
包裹的组件激活时调用;deactivated
:<keep-alive></keep-alive>
包裹的组件离开时调用;beforeDestroy
:实例销毁之前调用,此时实例仍然完全可用;destroyed
:实例销毁之后调用,此时实例的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
三、生命周期的实例方法有哪些,简单介绍一下
vm.$mount()
,返回vm
,可链式调用其它实例方法;(不常用)const myVue = Vue.extend({ template: '<div>Hello!</div>' }) new myVue().$mount('#warp') //同上 new myVue({el:'#warp'}) // 在文档之外渲染并且随后挂载($mount不传参数) const component = new myVue().$mount() document.getElementById('app').appendChild(component.$el)
vm.$forceUpdate()
,强制Vue实例重新渲染,不是重新加载组件,会触发beforeUpdate和updated这两个钩子函数,不会触发其他的钩子函数。它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件;vm.$nextTick()
,参数为callback,等待视图全部更新后执行,回调函数的this自动绑定到调用它的实例上;vm.$destroy()
,销毁一个实例。清理它与其它实例的连接,解绑全部指令及事件监听器,但不能清理实例的DOM和data,会触发beforeDestroy和destroyed两个钩子函数。
四、vue有哪些指令,简单介绍一下,最少说出10个
-
v-show
,切换元素的display属性,来控制元素显示隐藏,初始化会渲染,适用频繁显示隐藏的元素,不能用在<template>
上; -
v-if
,通过销毁并重建组件,来控制组件显示隐藏,初始化不会渲染,不适用频繁显示隐藏的组件,可以用在<template>
上; -
v-else-if
,必须和v-if
一起使用; -
v-else
,必须和v-if
一起使用; -
v-for
,将Array、Object、Number、String数据循环渲染元素或者组件,渲染组件必须带上key,key要为数据中每项特定值比如ID;<div v-for="(item,index) in Array">{{index}}-{{item}}</div> <div v-for="(item,key,index) in Object">{{index}}-{{key}}--{{item}}</div> <div v-for="item in Number">{{item}}</div> <div v-for="(item,index) in String">{{index}}-{{item}}</div> <myVue v-for="(item,key,index) in Object" :title="item.title" :key="item.id"></myVue>
-
v-on
,缩写@
,监听事件,如:@click
、@submit
、@dblclick
- 怎么获取div上点击的鼠标位置
<div @click="a"></div> <div @click="b(1,2,$event)"></div> methods:{ a(){ console.log(event.clientX,event.clientY) }, b(num1,num2,$event){ console.log($event.clientX,$event.clientY) }, }
- 怎么阻止冒泡,怎么阻止默认事件
//阻止冒泡 <div @click.stop="a"></div> //阻止默认 <div @click.prevent="b"></div> //阻止冒泡阻止默认 <div @click.stop.prevent="c"></div>
- 怎么监听组件根元素的原生事件
<myVue @click.native="d"></myVue>
- 怎么监听组件自定义事件
<myVue @diy-event="f"></myVue> //组件中这样触发 this.$emit('diyEvent',data)
- 怎么获取div上点击的鼠标位置
-
v-bind
,缩写:
,绑定动态属性; -
v-model
,限制应用在<input>
<textarea>
<select>
表单 元素和组件上创建双向绑定,修饰符v-model.lazy
懒监听、v-model.number
将值转成有效的数字、v-model.trim
过滤首尾空格;以上8个非常常用指令没说出来,可以考虑终止面试了
-
v-text
,<div v-text="data"></div>
等同<div>{{data}}</div>
; -
v-html
,直接输出HTML,不会按Vue模板编译,会有XSS攻击分析,不要用在用户提交内容上; -
v-once
,只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过,用于优化更新性能; -
v-per
,跳过这个元素和它的子元素的编译过程。可以用来显示原始Mustache标签。跳过大量没有指令的节点会加快编译; -
v-cloak
,防止页面加载时出现 vuejs 的变量名,需要css配合[v-cloak] { display: none }
,几乎没怎么用; -
v-slot
,在组件插槽里面详解。
五、怎么监听数据变化
data(){
return(){
a:{
b:{
c:1,
d:2
}
}
e:'dfe'
}
},
watch:{
a:{
handler:function(val,oldVal){},
deep:true
},
'a.b':{
handler:function(val,oldVal){},
deep:true
},
// 该回调将会在侦听开始之后被立即调用,
d: {
handler: 'handlewacthD',
immediate: true
},
},
methods:{
handlewacthD(val,oldVal){}
}
六、计算属性
该问题必问,这关系到后期维护的,如果对这个不熟悉,那他在插值中可能会写过多的逻辑计算,导致模板不简洁会导致后期维护困难,会联想到此人是否注重虑代码简洁和维护性。
-
为什么要使用计算属性?
答:避免在模板中放入太多的逻辑,导致模板过重且难以维护。
-
计算属性有什么特性?
答:计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值。
-
计算属性的getter和setter是什么,有什么用。
答:
<template> <div> <span>{{a}}</span><span>加</span><span>{{b}}</span><span>等于</span><span>{{c}}</span> <span>{{a}}</span><span>乘</span><span>{{b}}</span><span>等于</span><span>{{d}}</span> <button @click="add">加1</button> </div> </template> <script> export default { data() { return { a: 1, b: 2 }; }, computed: { c: function() { return this.a + this.b; }, d: { get: function() { return this.a * this.b; }, set: function(val) { this.a = val/this.b; } } }, methods: { add(){ this.d=this.d*2; } } }; </script>
七、怎么动态绑定Class和Style
- 将test、active、active-click三个className,绑到div上,渲染成
<div class="test active active-click"></div>
其中test是固定的,active受data中actived控制,active-click受data中actived和clicked控制,请用4种写法实现。template> <div> <!--第一种对象语法 --> <div class="test" :class="{ active: actived , 'active-click': clicked && actived }"></div> <!-- 第二种数组语法 --> <div class="test" :class="[ actived? activeClass : '', clicked && actived ? activeClickClass : '' ]"></div> <!-- 第三种对象和数组混合 --> <div :class="[ testClass , {active: actived} , {'active-click': clicked && actived} ]"></div> <!-- 第四种对象和计算属性(推荐) --> <div :class="classObject"></div> </div> </template> <script> export default { data() { return { actived: true, clicked: true, testClass: 'test', activeClass: 'active', activeClickClass: 'active-click', } }, computed: { classObject: function() { return { test: true, active: this.actived, 'active-click': this.actived && this.clicked, } } }, } </script>
- 点击“测试”两个字,字体颜色变成红色,字体大小变成14px,字体加粗,再次点击,字体颜色变成黑色,字体大小变成12px,字体不加粗,用动态绑定Style方法实现,两种动态绑定Style写法。
<template> <div> <span @click="handleClick" :style="{ color:color, fontSize:fontSize, fontWeight:fontWeight }">测试</span> <span @click="handleClick" :style="[styleObject]">测试</span> <span @click="handleClick" :style="styleObject">测试</span> </div> </template> <script> export default { data() { return { color: '#000', fontSize:'12px', fontWeight: 'normal', } }, computed: { styleObject:function(){ return { color: this.color, fontSize:this.fontSize, fontWeight: this.fontWeight, } } }, methods:{ handleClick(){ this.color = this.color=='#000'? 'red' : '#000'; this.fontSize = this.fontSize=="12px"?'14px' : '12px'; this.fontWeight=this.fontWeight='normal'?'bold' :'normal'; } } } </script>
八、 Vue中操作data中数组的方法中哪些可以触发视图更新,哪些不可以,不可以的话有什么解决办法?
push()、pop()、shift()、unshift()、splice()、sort()、reverse()这些方法会改变被操作的数组; filter()、concat()、slice()这些方法不会改变被操作的数组,返回一个新的数组; 以上方法都可以触发视图更新。
-
利用索引直接设置一个数组项,例:
this.array[index] = newValue
-
直接修改数组的长度,例:
this.array.length = newLength
以上两种方法不可以触发视图更新;
-
可以用
this.$set(this.array,index,newValue)
或this.array.splice(index,1,newValue)
解决方法1 -
可以用
this.array.splice(newLength)
解决方法2
九、怎么对data中的对象进行属性的添加或删除可以触发视图更新
由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除,
const vm = new Vue({
el:'#app',
data:{
a:{
b:1,
c:2,
e:{
f:3
}
}
}
})
//这样Vue是不能检测到a对象的更新
vm.a.d=3;
//这样Vue也是不能检测到a对象的更新
delete a.c;
//应该这么做
vm.$set(this.a , d, 3);
vm.$set(this.a.e, f, 4);
vm.$set(this.a, e, {g:5});
this.a=Object.assign({}, this.a, {d:3});
vm.$delete(this.a, 'c');
十、v-for和v-if能共同使用吗?
能,但是要看应用场景,举个例子
在处于同一节点上,因为v-for 的优先级比 v-if 更高,v-if 将分别重复运行于每个 v-for 循环中。如果要实现渲染满足条件的li节点时,可以这样用
<ul>
<li v-for="item in items" v-if="item.show">{{item}}</li>
</ul>
如果要实现有条件地跳过循环的执行,应该这么做
<ul v-if="items.length">
<li v-for="item in items">{{item}}</li>
</ul>
十一、混入
-
全局混入在项目中怎么用?
在main.js中写入
import Vue from 'vue'; import mixins from './mixins'; Vue.mixin(mixins);
之后,全局混入可以写在mixins文件夹中index.js中,全局混入会影响到每一个之后创建的 Vue 实例(组件);
-
局部混入在项目中怎么用
局部混入的注册,在mixins文件中创建一个a_mixin.js文件,然后再a.vue文件中写入
<script> import aMixin from 'mixins/a_mixin' export default{ mixins:[aMixin], } </script>
局部混入只会影响a.vue文件中创建的Vue实例,不会影响到其子组件创建的Vue实例;
-
组件的选项和混入的选项是怎么合并的
-
数据对象【data选项】,在内部进行递归合并,并在发生冲突时以组件数据优先;
-
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用;
-
watch对象合并时,相同的key合成一个对象,且混入监听在组件监听之前调用;
-
值为对象的选项【filters选项、computed选项、methods选项、components选项、directives选项】将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
-
十二、过滤器
-
用一个局部过滤器过滤器实现人民币汇率转换功能,支持美元(0.15汇率)、英镑(0.12汇率)
<template> <div> <input v-model="money" type="number" />人民币 <div><span>{{money | moneyFilter(0.15)}}</span>美元</div> <div><span>{{money | moneyFilter(0.12)}}</span>英镑</div> </div> </template> <script> export default { data() { return { money: 1 }; }, filters: { moneyFilter: function(val, ratio) { return Number(val * ratio).toFixed(2); } } }; </script>
-
一个插值可以连续使用两个过滤器吗?
可以,
{{ message | filterA | filterB }}
-
过滤器除了在插值上使用,还可以用在那个地方?
还可以v-bind 表达式 上,如:
<div :id="rawId | formatId"></div>
十三、父子组件怎么通信
-
父组件到子组件的通信用props来完成。
/* 父组件 */ <template> <child :title="childTitle"></child> </template> <script> export default { data() { return { childTitle: '子组件的标题' }; }, }; </script>
/* 子组件 */ <template> <h2>{{title}}</h2> </template> <script> export default { props:['title'], data() { return { }; }, }; </script>
父组件渲染出
<h2>子组件的标题</h2>
-
子组件到父组件的通信,通过在父组件中自定义事件,在子组件用
this.$emit('父组件自定义事件','要传到父组件的数据')
实现。/* 父组件 */ <template> <child :title="childTitle" @set-title="setTitle"></child> </template> <script> export default { data() { return { childTitle: "子组件的标题" }; }, methods:{ setTitle(data){ this.childTitle=data; } } }; </script>
/* 子组件 */ <template> <h2 @click="changeTitle">{{title}}</h2> </template> <script> export default { props:['title'] data() { return {}; }, methods:{ changeTitle(){ this.$emit('set-title','我子组件想改一下标题') } } }; </script>
点击
<h2>子组件的标题</h2>
后渲染出<h2>我子组件想改一下标题</h2>
总结
以上总结的十三道面试题,适用于Vue初级工程师,既是能在项目负责人搭建好已有框架和模板的环境下胜任业务开发。在我司,上面这些题目至少要正确回答十道以上才会考虑录用。