面试-vue相关

184 阅读6分钟

1.watch与computed的区别

watch(侦听器),当需要在数据变化时执行异步或开销较大的操作时需要用的watch。无缓存。

computed(计算属性),在模板中如果有复杂的表达式而且多次用的,可以使用computed。计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。

2.vue生命周期及对应的行为

vue生命周期有beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed

  • beforeCreate: 在Vue实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  • created: 在实例创建完成后被立即调用,此时数据观测 (data observer),属性和方法的运算,watch/event 事件回调已经完成。然后挂载阶段还没开始
  • beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。
  • mounted:实例被挂载后调用,这时内部子组件不一定一起被挂载,可以使用==$nextTick==方法,执行要在所有视图渲染完毕后的操作。该钩子在服务器端渲染期间不被调用。
mounted: function () {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}
  • beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
  • updated: 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。此时组件 DOM 已经更新。该钩子不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用 ==vm.$nextTick==
  • activated:被 keep-alive 缓存的组件激活时调用
  • deactivated:被 keep-alive缓存的组件停用时调用。
  • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
  • errorCaptured:当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

3.vue父子组件生命周期执行顺序

加载渲染过程:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

数据更新:父beforeUpdate->子beforeUpdate->子updated->父updated

销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

4.组件间通讯方法

  1. props父组件向子组件传递参数,自定义事件子组件向父组件传递参数($emit)
  2. 注册eventBus(就是一个vue实例),在实例上注册自定义事件,进行组件间的通讯(emit,on)
  3. Vuex(State、Getter、Mutation、Action、Module)
  4. attrs 和listeners

attrs: 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="attrs" 传入内部组件——在创建高级别的组件时非常有用 listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners" 传入内部组件——在创建更高层次的组件时非常有用。

  1. provide 和 inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象(使用Vue.observable),那么其对象的属性还是可响应的。

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}
  1. ref、parent 和children

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

parent /children:访问父 / 子实例

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。

5.如何实现一个指令

可以使用Vue.directive注册全局自定义指令,也可以在组件中使用directives选项注册局部自定义指令。

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()
  }
})

//组件
directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

指令对象的钩子函数bind、inserted、update、componentUpdated、unbind

  1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
  3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。
  4. componentUpdated:指令所在组件的 VNode 及其子 VNode全部更新后调用
  5. unbind:只调用一次,指令与元素解绑时调用。

钩子函数参数el、binding、vnode、oldVnode

  1. el:指令所绑定的元素,可以用来直接操作 DOM 。
  2. binding:一个对象,包含以下属性:
  • name:指令名,不包括 v- 前缀。
  • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
  • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
  • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
  • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
  • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  1. vnode:Vue 编译生成的虚拟节点。
  2. oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

6.vue.nextTick实现原理

利用event loop的原理,当数据发生改变,触发DOM修改,到UI render是一个事件循环。Vue.nextTick方法的参数回调函数,会被放入一个队列中,等待下次事件循环执行flushCallbacks函数遍历执行队列中的回调函数(希望在DOM渲染后执行的操作)。Vue.nextTick方法实现模块中还声明了microTimerFunc 和 macroTimerFunc两个方法,对应Macrotask和Microtask

对于 macro task 的实现,优先检测是否支持原生setImmediate,这是一个高版本 IE 和 Edge 才支持的特性,不支持的话再去检测是否支持原生的MessageChannel,如果也不支持的话就会降级为 setTimeout 0;而对于 micro task的实现,则检测浏览器是否原生支持 Promise,不支持的话直接指向 macro task 的实现。

7.Vue的diff算法

8.双向绑定

Vue数据双向绑定实际上就是利用Object.defineProperty和存取描述符,对属性的存取进行监听,从而进行相应的操作,示例:

// Vue老版本实现
Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    var value = getter ? getter.call(obj) : val;
    if (Dep.target) {
      dep.depend();
      if (childOb) {
        childOb.dep.depend();
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
    }
    return value
  },
  set: function reactiveSetter (newVal) {
    var value = getter ? getter.call(obj) : val;
    /* eslint-disable no-self-compare */
    if (newVal === value || (newVal !== newVal && value !== value)) {
      return
    }
    /* eslint-enable no-self-compare */
    if (customSetter) {
      customSetter();
    }
    // #7981: for accessor properties without setter
    if (getter && !setter) { return }
    if (setter) {
      setter.call(obj, newVal);
    } else {
      val = newVal;
    }
    childOb = !shallow && observe(newVal);
    dep.notify();
  }
})
// Vue-next版本
Proxy(data, {
  get(target, key) {
    return target[key];
  },
  set(target, key, value) {
    let val = Reflect.set(target, key, value);
      _that.$dep[key].forEach(item => item.update());
    return val;
  }
})

然后通过发布订阅者模式,当数据发生改变,触发视图上的渲染变化

9.Proxy与Object.defineProperty的优劣对比

Object.defineProperty

(1). 无法监听数组变化,arr[indexOfItem] = newValue这种是无法检测的,push、pop、shift、unshift、splice、sort、reverse方法可以监听到,是因为Vue内部对数组的原型对象上的这些方法进行了处理修改。

(2). 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历,如果属性值也是对象那么需要深度遍历

Proxy

(1). Proxy可以直接监听对象而非属性

(2). Proxy可以直接监听数组的变化

(3). Proxy返回的是一个新对象,我们可以只操作新的对象达到目的

(4). Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的。

(5).Proxy的劣势就是兼容性问题,而且无法用polyfill磨平