同样的,展示我们可以跑起来的测试代码。当然这是翻看各个类后,按我自己理解整理出来的。原文有export default都取消了,方便一个文件测试。新增一个文件vuetest02.js,将以下代码拷贝
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; i < subs.length; i++) { subs[i].update(); } } } const bailRE = /[^w.$]/ function parsePath(path){ if (bailRE.test(path)) { return; } const segments = path.split('.'); return function (obj) { if (!obj) return; //console.log(obj); for (let i = 0; i < segments.length; i++) { //console.log(segments[i]); obj = obj[segments[i]]; } return obj; } } var window = {};//模拟页面全局属性 class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm; // 执行this.getter(),就可以获取到data.a.b.c的内容 this.getter = parsePath(expOrFn); this.cb = cb; this.value = this.get(); console.log("this.value..." + this.value); } get() { window.target = this; let value = this.getter.call(this.vm, this.vm); window.target = undefined; return value; } update() { const oldValue = this.value; this.value = this.get(); this.cb.call(this.vm, this.value, oldValue); } } class Observer { constructor(value) { this.value = value; if (!Array.isArray(value)) { this.walk(value); } } walk(obj) { const keys = obj.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj.keys[i]) } } } function defineReactive(data, key, val) { // 新增,递归子属性 if (typeof val == 'object') { new Observer(val); } let dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { dep.depend(); return val; }, set: function (newVal) { if (val === newVal) { return; } val = newVal; dep.notify(); } }) } //与01测试一样的数据准备。 var base = {}; defineReactive(base, "name", "kevin"); //准备一个修改DOM的方法。就是说只要属性变化了,就帮我调用这个方法 function updateVm(vm, newVal, oldValue) { console.log(`将控件的外观按新值变化...newVal=${newVal},oldValue=${oldValue}`); } let watch = new Watcher(base, "name", updateVm); base.name = "witty"; base.name = "witty2"; base.name = "witty3"; console.log("测试完成。")
在命令窗口运行:node vuetest02.js
你会发现有两个问题,以下是测试的截图
第一个问题,updateVm为什么newVal是一个旧值,其实是我并不了解call的方法是什么含义
Function.call(obj,[param1[,param2[,…[,paramN]]]]) obj:这个对象将代替Function类里this对象 params:这个是一个参数列表
原来第一个参数实际上是代表this,于是调用updateVm如下:
//准备一个修改DOM的方法。就是说只要属性变化了,就帮我调用这个方法 function updateVm(newVal, oldValue) { console.log(`this=${this}`); console.log(`将控件的外观按新值变化...newVal=${newVal},oldValue=${oldValue}`); }
第二个问题,是书本上的代码就有这个问题存在,其实每get一次都会加一次“依赖收集”,解决也很容易,把window.target=this这一句从get方法中删除,改放在构造函数调用get的语句之前就行了。