面试题:vue数据绑定的实现原理?

258 阅读4分钟

1、概括回答

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

官网的图:

image.png

vue的响应式基本原理:

  1. vue会遍历此data中对象所有的属性,
  2. 并使用Object.defineProperty把这些属性全部转为getter/setter,
  3. 而每个组件实例都有watcher对象,
  4. 它会在组件渲染的过程中把属性记录为依赖,
  5. 之后当依赖项的 setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

image.png

2、亮点回答

概括回答我们只回答了使用ES5的方法 Object.defineProperty 实现数据的监听的,那么具体是如何实现还是没有讲的很清楚。

这时候我们需要问自己,如何找亮点?

vue的响应式原理设计三个重要对象:Observer,Watcher,Dep。

  • Observer对象:vue中的数据对象在初始化过程中转换为Observer对象。
  • Watcher对象:
    • 将模板和Observer对象结合在一起生成Watcher实例
    • Watcher是订阅者中的订阅者。
  • Dep对象:
    • Watcher对象和Observer对象之间纽带,
    • 每一个Observer都有一个Dep实例,用来存储订阅者Watcher。
    • 当属性变化会执行主题对象Observer的dep.notify方法, 这个方法会遍历订阅者Watcher列表向其发送消息, Watcher会执行run方法去更新视图。

依赖关系图如下,更能方面我们的理解 image.png

接着我们需要补充的是:模板编译过程中的指令和数据绑定都会生成Watcher实例,实例中的watch属性也会生成Watcher实例。

说的这些有没有觉得有点乱,那我们总结一下如何亮点回答

  1. 在生命周期的initState方法中将data,prop,method,computed,watch中的数据劫持, 通过observe方法与Object.defineProperty方法将相关对象转为换Observer对象。
  2. 然后在initRender方法中解析模板,通过Watcher对象,Dep对象与观察者模式将模板中的 指令与对象的数据建立依赖关系,使用全局对象Dep.target实现依赖收集。
  3. 当数据变化时,setter被调用,触发Object.defineProperty方法中的dep.notify方法, 遍历该数据依赖列表,执行器update方法通知Watcher进行视图更新。

然后写一个使用Object.defineProperty实现监听变量

  • vue是无法检测到对象属性的添加和删除,但是可以使用全局Vue.set方法(或vm.$set实例方法)。
  • vue无法检测利用索引设置数组,但是可以使用全局Vue.set方法(或vm.$set实例方法)。
  • 无法检测直接修改数组长度,但是可以使用splice
var obj = {};
var a;
Object.defineProperty(obj, 'a', {
  get: function () {
    console.log('get val');
    return a;
  },
  set: function (newVal) {
    console.log('set val:' + newVal);
    a = newVal;
  }
});
obj.a; // get val
obj.a = 'saucxs' //set val

3、进阶回答

因为现在vue已经到3了,不再是停留在2的时候,这个时候,可以把3的原理简单说一下。

这个时候不应该是ES6的proxy特性上场了,proxy是ES6的新增的功能,可以用来定义对象中的操作。

let p = new Proxy(target, handler);
// `target` 代表需要添加代理的对象
// `handler` 用来自定义对象中的操作

可以很方便的使用 Proxy 来实现一个数据绑定和监听.

image.png

为啥在数据监听上做了升级?

  • vue为什么对数组对象的深层监听无法实现,
  • 因为组件每次渲染都是将data里的数据通过defineProperty进行响应式或者双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染。

对比vue2和vue3的区别是什么?

1、语法层面上

  • defineProperty只能响应首次渲染时候的属性,
  • Proxy需要的是整体监听,不需要关心里面有什么属性,而且Proxy的配置项有13种,可以做更细致的事情,这是之前的defineProperty无法达到的。

2、兼容层面上

  • vue2.x之所以只能兼容到IE8就是因为defineProperty无法兼容IE8,其他浏览器也会存在轻微兼容问题。
  • proxy的话除了IE,其他浏览器都兼容,这次vue3还是使用了它,说明vue3直接放弃了IE的兼容考虑。