让数据变得可观测 我们都知道 Vue2 中是通过Object.defineProperty
来实现的数据劫持,也就是让数据变得可观测 例如:
let car = {};let val = 3000 ;Object .defineProperty (car, "price" , { enumerable : true , configurable : true , get ( ) { console .log ("price属性被读取了" ); return val; }, set (newVal ) { console .log ("price属性被修改了" ); val = newVal; }, });
依赖收集 让 object 数据变的可观测。变的可观测以后,我们就能知道数据什么时候发生了变化,那么当数据发生变化时,我们去通知视图更新就好了。那么问题又来了,视图那么大,我们到底该通知谁去变化?总不能一个数据变化了,把整个视图全部更新一遍吧,这样显然是不合理的。此时,你肯定会想到,视图里谁用到了这个数据就更新谁呗。对!你想的没错,就是这样.
在 Vvue 中是通过实现的一个 Dep 类来实现依赖收集 源码如下
export default class Dep { constructor ( ) { this .subs = []; } addSub (sub ) { this .subs .push (sub); } removeSub (sub ) { remove (this .subs , sub); } depend ( ) { if (window .target ) { this .addSub (window .target ); } } notify ( ) { const subs = this .subs .slice (); for (let i = 0 , l = subs.length ; i < l; i++) { subs[i].update (); } } } export function remove (arr, item ) { if (arr.length ) { const index = arr.indexOf (item); if (index > -1 ) { return arr.splice (index, 1 ); } } }
在上面的依赖管理器 Dep 类中,我们先初始化了一个 subs 数组,用来存放依赖,并且定义了几个实例方法用来对依赖进行添加,删除,通知等操作。
有了依赖管理器后,我们就可以在 getter 中收集依赖,在 setter 中通知依赖更新了,代码如下:
function defineReactive (obj, key, val ) { if (arguments .length === 2 ) { val = obj[key]; } if (typeof val === "object" ) { new Observer (val); } const dep = new Dep (); Object .defineProperty (obj, key, { enumerable : true , configurable : true , get ( ) { dep.depend (); return val; }, set (newVal ) { if (val === newVal) { return ; } val = newVal; dep.notify (); }, }); }
在上述代码中,我们在 getter 中调用了 dep.depend()方法收集依赖,在 setter 中调用 dep.notify()方法通知所有依赖更新
Watcher 谁用到了数据,谁就是依赖,我们就为谁创建一个Watcher
实例。在之后数据变化时,我们不直接去通知依赖更新,而是通知依赖对应的 Watch 实例,由Watcher
实例去通知真正的视图
export default class Watcher { constructor (vm, expOrFn, cb ) { this .vm = vm; this .cb = cb; this .getter = parsePath (expOrFn); this .value = this .get (); } get ( ) { window .target = this ; const vm = this .vm ; let value = this .getter .call (vm, vm); window .target = undefined ; return value; } update ( ) { const oldValue = this .value ; this .value = this .get (); this .cb .call (this .vm , this .value , oldValue); } } const bailRE = /[^\w.$]/ ;export function parsePath (path ) { if (bailRE.test (path)) { return ; } const segments = path.split ("." ); return function (obj ) { for (let i = 0 ; i < segments.length ; i++) { if (!obj) return ; obj = obj[segments[i]]; } return obj; }; }
Watcher
先把自己设置到全局唯一的指定位置(window.target),然后读取数据。因为读取了数据,所以会触发这个数据的 getter。接着,在 getter 中就会从全局唯一的那个位置读取当前正在读取数据的 Watcher,并把这个 watcher 收集到 Dep 中去。收集好之后,当数据发生变化时,会向 Dep 中的每个 Watcher 发送通知。通过这样的方式,Watcher 可以主动去订阅任意一个数据的变化
以上,就彻底完成了对 Object 数据的侦测,依赖收集,依赖的更新等所有操作