vue面试题总结

275 阅读4分钟

一、vue

1.1 vue基本使用

  • vue组件如何进行通信?
    父->子组件:子组件通过props接收父组件传递的值;父组件设置refs,子组件通过this.$refs.xx访问父组件实例; 子->父组件:父级自定义事件,子级通过emit触发事件并且传递参数;
// child
this.$emit('add', good)
// parent
<Cart @add="cartAdd($event)"></Cart>

兄弟组件:通过共同的祖辈组件搭桥,$parent或$root。

// brother1
this.$parent.$on('foo', handle)
// brother2
this.$parent.$emit('foo')

祖先和后代之间:provide/inject:能够实现祖先给后代传值; 任意两个组件:vuex通过store管理数据并且通知组件状态变更、创建一个事件总线event-bus管理事件派发、监听和回调处理。将event-bus绑定在vue的原型上。

// Bus:事件派发、监听和回调管理
class Bus{
    constructor(){
        // {
        // eventName1:[fn1,fn2],
        // eventName2:[fn3,fn4],
        // }
        this.callbacks = {}
    }
    $on(name, fn){
        this.callbacks[name] = this.callbacks[name] || []
        this.callbacks[name].push(fn)
    }
    $emit(name, args){
        if(this.callbacks[name]){
        this.callbacks[name].forEach(cb => cb(args))
        }
    }
}
// main.js
Vue.prototype.$bus = new Bus()
// child1
this.$bus.$on('foo', handle)
// child2
this.$bus.$emit('foo')
  • 生命周期有哪些?父子组件生命周期顺序?
    生命周期如下:
    • beforeCreate:实例创建前调用;
    • created:实例创建后,数据初始化最好在这个阶段完成;
    • beforeMount:实例挂载前;
    • mounted:实例挂载完成,vm.$el可以调用,不能保证所有子组件被挂载,如果要等视图全部更新完毕可用vm.$nextTick();
    • beforeUpdate:数据更新前;updated:数据更新完成;
    • updated:数据更新完成。
    • beforeDestroy:实例销毁之前调用,此时实例仍然可用;
    • destroyed:实例销毁之后调用,此时实例的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
      父子组件生命周期顺序:
      加载渲染过程:父beforeCreated->父created->父bedoreMount->子beforeCreated-> created->子bedoreMount->子mounted->父mounted; 子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated; 销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed。
  • computed和watch
    缓存 set 深度监听 引用类型 oldval
  • 如何自己实现v-model?在组件上使用v-model?
<input :value="val" @input="val=$event.target.value">
  • vue常用的修饰符有哪些?
    • 事件修饰符:.stop:阻止冒泡;.prevent:阻止默认事件;.capture:阻止事件捕获;.self:只在event.target是自身时才触发函数;.once:只触发一次函数;.native:监听根元素的原生事件。
    <base-input v-on:focus.native="onFocus"></base-input>
    
    • 按键修饰符:v-on:keyup.enter、v-on:keyup.page-down等(keycode或者别名都可以)。
    • v-bind 指令常用修饰符:假如父组件传给子组件的值,子组件接受之后,想要改变父组件传过来的值,就可以使用sync。.sync是vue中用于实现简单的“双向绑定”的语法糖,在平时的开发中是非常使用的。vue的prop是单向下行绑定:父级的prop的更新会向下流动到子组件中,但是反过来不行。可是有些情况,我们需要对prop进行“双向绑定”。这个时候,就可以用.sync来解决
    //父组件
     <vue-statisTotal
        :list.title="doc.title">
    </vue-statisTotal>
    //等同于
    <vue-statisTotal
        @update:title.sync="doc.title = $event">
    </vue-statisTotal>
    //子组件触发
    this.$emit('update:title', newTitle)
    
    • v-model 指令常用修饰符: .lazy:- 取代 input 监听 change 事件; .number:- 输入字符串转为数字; .trim:- 输入首尾空格过滤;
  • $nextTick的原理是什么,使用场景是什么?
    vue采用异步渲染,简单概括,$nextTick的回调函数会被添加到任务队列中,逐个判断promise.then => MutationObserver => setImmediate => setTimeout是否兼容。确保异步事件在DOM更新循环(同步事件)完成后执行延迟回调。 在 created 和 mounted 阶段,如果需要操作渲染后的视图,也要使用 nextTick 方法。
  • slot是什么?
    父组件向子组件插入内容,常用的有具名插槽和作用域插槽。
#具名插槽
<!--子组件:header-top-->
<slot name="header"></slot>
<!--父组件-->
<header-top>
    <template v-slot:header>
        <div>header content</div>
    </template>
</header-top>
#作用域插槽
<!--子组件传递插槽属性user-->
<slot name="header" :user="user"></slot>
<!--父组件-->
<header-top>
    <template v-slot:header="slotProps">
        <div>header content</div>
    </template>
</header-top>
  • vue动态组件是什么?
    ,修改currentTabComponent的值,即可切换到对应名字的组件,结合keep-alive使用,可以实现切换tab的功能。
  • 如何异步加载组件? import函数,按需加载,异步加载大组件
components:{
    MyComponent:()=>{import ('./MyComponent.vue')}
}
  • vue如何缓存组件?
    利用keep-alive缓存组件,频繁切换,不需要重新渲染,vue常见性能优化。
  • 如何抽离公共逻辑?
    利用mixin,多个组件有共同的逻辑,抽离出来。数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
#全局混入       
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})
#组件混入
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义一个使用混入对象的组件
var Component = Vue.extend({
  mixins: [myMixin]
})

1.2 vue原理

  • 如何理解MVVM?
    MVVM指的是数据驱动视图,M代表model、v代表view,VM指的是viewmodel,viewmodel利用双向数据绑定将将视图层和数据连接起来,view变化之后,viewmodel会通知model更新,而Model 数据的变化也会立即反应到View 上。

  • 什么是双向绑定,原理是什么? 双向绑定指的是模型(model)和视图(view)的双向绑定;其原理采用的是数据劫持结合发布者-订阅者模式,通过observer(数据监听器)劫持监听data的属性,读取数据时会触发getter,修改数据时触发setter;每个属性对应一个new Dep(),Dep负责收集订阅者和通知订阅者更新。组件挂载过程中都会new watcher(订阅者),watcher相当于observer和compile的桥梁,watcher的第二个参数updateComponent的作用是更新和渲染节点,因此会去读取data,触发getter,调用dep收集依赖。当数据更新时,就会触发setter,调用dep.notify(),通知watcher执行update对视图进行更新。

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
 new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }
  • 如何监听数组的变化?
    将改变数组的七个方法重写在数组原型上,当数组调用这些方法时,相当于会触发mutation方法,在mutator方法中,调用数组的原始方法,然后通知dep更新。(详情见vue源码解析二)
  • 什么是虚拟DOM?
    虚拟DOM是对真实DOM的一种抽象描述,简单来说,可以把Virtual DOM 理解为一个简单的JS对象,并且最少包含标签名( tag)、属性(attrs)和子元素对象( children)三个属性。通过状态生成虚拟节点树,然后将虚拟节点树渲染成真正的DOM,在渲染之前,会使用新生成的虚拟节点树和上一次虚拟节点树进行对比,只渲染不同的部分。
  • Vue中如何实现一个虚拟DOM?
    首先构建一个VNode的类,DOM元素上的所有属性在VNode实例对象上存在对应的属性。通过编译将模板转换为渲染函数render,执行render函数,创建vnode节点树。通过patch将新旧节点对比,最后生成真正的DOM。
  • 你了解Vue的diff算法吗?
    执行patch函数,首先判断新老节点是否存在,都存在则进行类型比较。 diff运算的核心为updatechildren。 updatechildren会将新旧vnode的首尾节点进行两两交叉对比,类型相同则执行patchNode,如果是首尾对比,需要移动真实DOM节点的位置。接下来指针向中间移动,重复对比。
    如果首尾交叉对比,没有找到类型相同的节点,则在oldvnode中的其他节点找与newStartVnode,满足同类型则patchNode,并将对应DOM移到oldStartVnode的前面。也有可能找不到满足sameNode的节点,那么会调用createElm创建新的DOM节点。
    当结束时oldStartIdx > oldEndIdx,说明还剩下新的vnode节点,需要将对应的DOM插入。当结束时newStartIdx > newEndIdx时,则删除多余的老节点。
  • 说说你对Vue的template编译的理解?
    通过parse将template模板字符串转换成AST(抽象语法树),optimize深度遍历AST树,去检测是否为静态节点,如果是静态节点则它们生成 DOM 永远不需要改变。AST通过generate得到render函数,render返回VNode。
  • Vue实例挂载的过程是什么?
    • 首先,mount对el做了限制,Vue 不能挂载在 body、html 这样的根节点上。
    • 判断options选项中是否有render函数这个属性,有则直接调用原始的\mount方法。如果没有,则判断template是否存在,转换成render,并赋值给options。最后调用原始的$mount方法。
    • 在原始的mount方法中,触发beforeMount钩子,并实例化一个watcher,在第二个参数updateComponent这个函数中调用vm._updated。该函数是首次渲染和更新渲染作用,参数为render函数(vnode),如果vm._vnode不存在则进行首次渲染。同时vnode中被劫持的数据自动收集依赖。当vnode中被劫持的数据变化时候触发对应的依赖,从而触发vm._update进行更新渲染。
    • 最后触发mounted钩子函数。

1.3 高级

  • 使用Vue后怎么针对搜索引擎做SEO优化?
  • SSR解决了什么问题?有做过SSR吗?你是怎么做的?

1.4 axios

  • axios是什么?怎样使用它?怎么解决跨域的问题?
    axios 是一个基于 promise的HTTP库,先封装在使用。使用proxyTable配置解决跨域问题。

1.5 vue-router

  • 怎样实现路由权限控制?
    router.addRoutes(routes: Array),常用于权限控制,例如会员访问的菜单区分。 关键点:接口传过来的component为字符,并不是对象,需要映射为对象。
  api.getRoutes.then(res=>{
    const routesConfig = res.routes.map(route=>{
        mapComponent(route);
    })
    this.$router.addRoutes(routesConfig)
})
let mapCom = function(com){
  return {
      com:()=>import(`@/view/${com}`);
  }  
}
let mapComponent = function(route){
    const route.component = mapCom(route.component);
  //注意判断嵌套的子路由
    if(route.children){
        route.children = route.children.map(child=>{
            mapComponent(child);
        })
    }
}