Vue初级前端工程师面试必备

14,890 阅读6分钟

前言

用本文来总结一下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)
      
  • 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初级工程师,既是能在项目负责人搭建好已有框架和模板的环境下胜任业务开发。在我司,上面这些题目至少要正确回答十道以上才会考虑录用。

后续