「面试」-VUE

896 阅读6分钟

PS:参考一些博客,整理供自己复习使用~

谈谈你对VUE的理解

vue是易用、灵活且高效的渐进式JavaScript框架。它的主要特点是MVVM模式。代码简介体积小,运行效率高,适合移动PC端开发。本身只关注UI,可以轻松引入VUE插件或其他的第三方库进行开发。

详细说下MVVM

MVVM全称为Model-View-ViewModel,Model表示数据模型层、View表示视图层、ViewModel是View和Model层的桥梁,数据绑定到ViewModel层并自动渲染到页面中,视图变化通知ViewModel层更新数据。

image.png

vue是如何实现响应式数据?

vue2.x中是采用数据劫持结合发布者-订阅者模式的方法,通过Object.defineProperty来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。但是有一个弊端,对于对象上新增的属性无能为力。

Object.defineProperty(data, 'count', {
  get() {},
  set() {},
})

vue3.x是通过proxy来实现的。拦截的是「修改data上的任意key」和「读取data上的任意key」。

new Proxy(data, {
  get(key) { },
  set(key, value) { },
})

vue是如何监听数组变化

数据就是使用object.defineProperty重新定义数组的每一项。

  • 用函数劫持的方式,重写了数组方法,具体就是更改了数据的原型,更改成自己的,用户调数组的一些方法时,走的就是自己的方法,然后通知视图去更新。
  • 数组里每一项可能是对象,就会对数组的每一项进行观测。(只有是对象的时候才会观测)。

vue为什么要采用异步渲染

vue是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,为了性能,vue会在本轮数据更新后,在异步更新视图。核心思想是nextTick。

简单说下nextTick

异步方法,异步渲染最后一步,与JS事件循环联系紧密。主要使用宏任务微任务定义了一个异步方法,多次调用nextTick会将方法存入队列,通过异步方法清空当前队列。

父子组件生命周期调用顺序

  • 渲染顺序:先父后子,完成顺序:先子后父。
  • 更新顺序:父更新导致子更新,子更新完成后父更新。
  • 销毁顺序:先父后子,完成顺序:先子后父。

Vue组件通信

大致以下四类

  1. 父子组件之前的通信
  2. 兄弟组件之间的通信
  3. 祖孙与后代组件之间的通信
  4. 非关系组件之间的通信

组件通信方案

  • 父亲提供数据通过属性props传递给儿子;

children.vue

props: {
    // 1、字符串形式
    name: String, // 接受参数类型
    // 2、对象形式
    age: {
        type: Number, // 接受参数类型
        default: 16, // 默认值为16
        require: true, // 是否为必须传递的值
    }
}

father.vue

<children 
    name="33"
    age=18
/>
  • 儿子通过$on绑定父事件,再通过$emit触发自己的事件。

children.vue

this.$emit('add', good)

father.vue

<Children @add="cartAdd($event)" />  
  • 通过使用ref,父组件在使用子组件的时候设置ref,父组件通过设置子组件ref来获取数据

father.vue

<Children ref="foo" />  
  
this.$refs.foo  // 获取子组件实例,通过子组件实例我们就能拿到对应的数据  
  • EventBus,兄弟组件传值。创建一个中央事件总线EventBus,兄弟组件通过emit触发自定义事件,emit触发自定义事件,emit第二个参数为传递的数值,另一个兄弟组件通过$on监听自定义事件。
// Bus.js
  
class Bus {  
  constructor() {  
    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() // 将$bus挂载到vue实例的原型上  
// 另一种方式  
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能  

// Children1.vue  
this.$bus.$emit('foo')  
Children2.vue
this.$bus.$on('foo', this.handle)  
  • attrs与listeners,祖先传递数据给子孙,设置批量向下传属性attrsattrs和listeners,可以通过v-bind="$attrs"传入内部组件
// child:并未在props中声明foo  
<p>{{$attrs.foo}}</p>  
  
// parent  
<HelloWorld foo="foo"/>  

// 给Grandson隔代传值,communication/index.vue  
<Child2 msg="lalala" @some-event="onSomeEvent"></Child2>  
  
// Child2做展开  
<Grandson v-bind="$attrs" v-on="$listeners"></Grandson>  
  
// Grandson使⽤  
<div @click="$emit('some-event', 'msg from grandson')">  
{{msg}}  
</div>  

  • Provide与Inject,给祖先定义provide,返回传递的值,再给后代组件通过inject接收组件传递过来的值
// 祖先组件
provide(){  
    return {  
        foo:'foo'  
    }  
}  

// 后代组件
inject:['foo'] // 获取到祖先组件传递过来的值  
  • $parent$children,通过共同祖辈parent或者parent或者root搭建通信 兄弟组件

this.$parent.on('add',this.add)

另一个兄弟组件

this.$parent.emit('add')

  • Vuex

  • state用来存放共享变量的地方

  • getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值

  • mutations用来存放修改state的方法。

  • actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作

获取父子组件实例的方法:

  • 父组件提供数据,子组件注入。
  • ref获取组件实例,调用组件的属性、方法。
  • 跨组件通信Event Bus
  • vuex状态管理实现通信。

Vuex工作原理

  • Vuex是一个专门为Vue.js应用程序开发的状态管理模式
  • 状态自管理应用包含以下几个部分:
    • state,驱动应用的数据源。
    • view,以声明方式将state映射到视图。
    • actions,响应在view上的用户输入导致的状态变化。
  • Vuex,多组件共享状态,因-单向数据流简洁性很容易被破坏。
    • 多个视图依赖于同一状态。
    • 来自不同视图的行为需要变更同一状态。

如何从真实DOM到虚拟DOM

Vue的模版编译原理,主要过程为:

  1. 将模版转换为AST树,AST用对象来描述真实的JS语法(将真实的DOM转换成虚拟DOM)。
  2. 优化树。
  3. 将AST树生成代码。

虚拟节点就是用一个对象来描述一个真实的DOM元素。首先将template先转成ASTAST树通过codepen生成render函数,render函数里的_c方法将它转为虚拟DOM。

DIFF算法

时间复杂度:一个树的完全Diff算法是一个时间复杂度为O(n*3),vue进行优化转化成O(n)

  • 最小量更新,key很重要,这是节点的唯一标识,告诉diff算法,在更改前后他们是否为同一个DOM节点。
    • v-for为什么要有key,没有的话会暴力复用。加key会减少操作DOM。
  • 只有是同一个虚拟节点才会进行精细化比较,否则就会暴力删除旧的,插入新的。
  • 只同层比较,不会进行跨层比较。

diff算法的优化策略: 四种命中查找,四个指针

  1. 旧前与新前
  2. 旧后与新后
  3. 旧前与新后
  4. 旧后与新前

Computed和Watch

Computed:默认computed也是一个watcher具备缓存,只有当依赖的数据变化时才会计算,当数据没有变化时,它会读取缓存数据。如果一个数据依赖于其他数据,使用computed

watch:每次都需要执行函数。watch更适用于数据变化时的异步操作。如果需要在某个数据变化时做一些事情,使用watch

v-for和v-if不能连用

v-forv-if的优先级更高,连用的话会把v-if的每个元素都添加以下,造成性能问题。

组件中的data为什么是函数

避免组件中的数据互相影响。同一个组件被复用多次会创建多个实例,本质上,这些实例都是同一个构造函数。如果data是一个对象的话,对象属于引用数据类型,会影响到所有的实例。为了保证组件的数据独立,要求每个组件都必须通过data函数返回一个对象作为组件的状态。

keep-alive

keep-alive可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。