一步一步实现Vue的响应式(对象观测)(3)

读取属性值的目的就是为了收集依赖,比如我们要观测obj.a.b.c,那么目的就达到了。 既然知道了getter是一个函数,那么在get方法中执行getter,就可以获取值了。

测试下代码:

一步一步实现Vue的响应式(对象观测)

这里有个细节,我们看Watcher类的get方法:

get() { Dep.target = this; const value = this.getter.call(this.vm); Dep.target = null; return value; }

在执行this.getter函数的时候,Dep.target的值一直都是当前依赖,而this.getter函数中一层一层读取属性值,在这路径之中的所有属性其实都收集了当前依赖。比如上面的例子来说,属性'a.b.c'的依赖,被收集到obj.a、obj.a.b、obj.a.b.c的dep中,那么修改obj.a或obj.b都是会触发当前依赖的:

一步一步实现Vue的响应式(对象观测)

避免重复收集依赖

观测表达式

在Vue中,$watch方法的第一个参数是可以传函数的:

this.$watch(() => { return this.a + this.b; }, (val, oldVal) => { console.log(val, oldVal); });

这种写法相当于观测一个表达式,类似与Vue中computed,依赖会被收集到属性a与属性b的dep中,无论修改其中任一,只要表达式的值发生变化,依赖都将会触发。

为了兼容函数的传入,我们稍微修改下Watcher类:

class Watcher { constructor(data, pathOrFn, cb) { this.vm = data; this.cb = cb; this.getter = typeof pathOrFn === 'function' ? pathOrFn : parsePath(pathOrFn); this.value = this.get(); } ... update() { const oldValue = this.value; this.value = this.get(); this.cb.call(this.vm, this.value, oldValue); } }

对于第二个参数pathOrFn,我们优先判断其本身是否已经是函数,是则直接赋值给this.getter,否则调用parsePath函数解析。在update方法中,再次调用了get方法来获取被修改后的值。

测试下代码:

一步一步实现Vue的响应式(对象观测)

结果好像有点不对?输出了1949次!而且还在增加之中,一定是某个陷入无限循环了。仔细回看我们修改的点,在update方法中,我们再次调用了get方法,这又会触发一次依赖的收集。然后我们在Dep类的notify方法中遍历依赖集合,每次触发依赖都会导致依赖的再次收集,这就是个无限循环了!

发现了问题,就来解决问题。我们要对依赖做唯一性校验:

let uid = 1; class Watcher { constructor(data, pathOrFn) { this.id = uid++; ... } } class Dep() { construct() { this.subs = []; this.subIds = new Set(); } ... addSub(sub) { const id = sub.id; if (!this.subIds.has(id)) { this.subs.push(sub); this.subIds.add(id); } } ... }

既然要做唯一性校验,我们给Watcher类实例增加了独一无二的id。在Dep类中,我们给构造函数里增加了属性subIds,其初始值为空Set,作用是存储依赖的id。然后在addSub方法中,在将依赖添加到subs之前,先判断这个依赖的id是否已经存在。

测试下代码:

一步一步实现Vue的响应式(对象观测)

只输出了一次,完全ok。

在Vue中的意义

防止依赖的重复收集,除了防止上面提到的陷入无限循环,在Vue中还有更重要的意义,比如一下模板:

<template> <div> <p>{{ a }}</p> <p>{{ a }}</p> <p>{{ a }}</p> </div> </template>

在Vue中,除了watch选项的依赖,还有一个特殊依赖叫渲染函数的依赖,其作用就是当模板中的变量发生变化时,更新VNode,重新生成DOM。在我们上面定义的模板中,一共使用a变量3次,当a变量被修改,如果没有防止重复依赖的收集,渲染函数就会被执行3次!这是完全必要的!并且3次只是个例子,实际可能会更多!

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/35c49c0e1b6051883e45d6f0af26478e.html