class Dep { constructor() { this.subs = []; } addSub(sub) { this.subs.push(sub); } depend() { Dep.taget && this.addSub(Dep.target); } notify() { for (let sub of subs) { sub.update(); } } }
subs就是依赖收集框,当属性值被读取时,在depend方法中将依赖收入到框内;当属性值被修改时,在notify方法中将依赖收集框遍历,每一个依赖的update方法都将被执行。
Observer
defineReactive函数只做了一件事,将数据转换成响应式的,我们定义一个Observer类来聚合其功能:
class Observer { constructor(data, key) { this.value = data; defineReactive(data, key); } } function defineReactive(data, key) { let val = data[key]; const dep = new Dep(); Object.defineProperty(data, key, { configurable: true, enumerable: true, get: function() { dep.depend(); return val; }, set: function(newVal) { if (newVal === val) { return; } dep.notify(); val = newVal; } }); }
dep不再是一个纯粹的数组,而是一个Dep类的实例。get函数中的依赖收集、set函数中的依赖触发的逻辑,分别用dep.depend、dep.update替代,这让defineReactive函数逻辑变得变得更加清晰。但是Observer类只是在构造函数中调用defineReactive函数,没起什么作用?这当然都是为后面做铺垫的!
测试一下代码:
观测所有属性
到目前为止我们都只在针对一个属性,而一个对象可能有n多个属性,因此我们要对做下调整。
观测一个对象的所有属性
观测一个属性主要是要定义其访问器属性,对于我们的代码来说,就是要执行defineReactive函数,所以对Observer类做下修改:
class Observer { constructor(data) { this.value = data; if (isPlainObject(data)) { this.walk(data); } } walk(value) { const keys = Object.keys(value); for (let key of keys) { defineReactive(value, key); } } } function isPlainObject(obj) { return ({}).toString.call(obj) === '[object Object]'; }
我们在Observer类中定义一个walk方法,其作用就是遍历对象的所有属性,然后在构造函数中调用。调用的前提是对象是一个纯对象,即对象是通过字面量或new Object()初始化的,因为像Array、Function等也都是对象。
测试一下代码:
深度观测
我们只要对象是可以嵌套的,即一个对象的某个属性值也可以是对象,我们的代码目前还做不到这一点。其实也很简单,做一下递归遍历的就好了:
class Observer { constructor(data) { this.value = data; if (isPlainObject(data)) { this.walk(data); } } walk(value) { const keys = Object.keys(value); for (let key of keys) { const val = value[key]; if (isPlainObject(val)) { this.walk(val); } else { defineReactive(value, key); } } } }
我们在walk方法中做了判断,如果key的属性值val是个纯对象,那就调用walk方法去遍历其属性值。既然是深度观测,那watcher类中的key的用法也发生了变化,比如说:'a.b.c',那我们就要兼容这种嵌套key的写法:
class Watcher { constructor(data, path, cb) { this.vm = data; this.cb = cb; this.getter = parsePath(path); this.value = this.get(); } get() { Dep.target = this; const value = this.getter.call(this.vm); Dep.target = null; return value; } update() { const oldValue = this.value; this.value = this.getter.call(this.vm, this.vm); this.cb.call(this.vm, this.value, oldValue); } } function parsePath(path) { if (/.$_/.test(path)) { return; } const segments = path.split('.'); return function(obj) { for (let segment of segments) { obj = obj[segment] } return obj; } }
Watcher类实例新增了getter属性,其值为parsePath函数的返回值,在parsePath函数中,返回的是一个匿名函数,匿名函数接收一个参数obj,最后又将obj作为返回值返回,那么这里的重点是匿名函数对obj做了什么处理。
匿名函数内只有一个for...of迭代,迭代对象为segments,segments是通过path对'.'分割得到的一个数组,比如path为'a.b.c',那么segments就为['a', 'b', 'c']。迭代内只有一个语句,obj被赋值为obj的属性值,这相当于一层一层去读取,比如说,obj初始值为:
obj = { a: { b: { c: 1 } } }
那么最后的结果为:
obj = 1