vue数据双向绑定原理, 和简单的实现,本文将实现mvvm的 Watcher
上面的步骤已经实现了监听器, 和订阅器, 当属性发生改变,发出通知, 那么这个通知 是通知谁呢,肯定是订阅者 watcher.
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则释放自己。// Watcherfunction Watcher(vm, exp, cb) { this.cb = cb; this.$vm = vm; this.exp = exp; // 此处为了触发属性的getter,从而在dep添加自己,结合Observer更易理解 this.value = this.get(); // 将自己添加到订阅器的操作}Watcher.prototype = { update: function() { this.run(); // 属性值变化收到通知 }, run: function() { var value = this.get(); // 取到最新值 var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.$vm, value, oldVal); // 执行Compile中绑定的回调,更新视图 } }, get: function() { Dep.target = this; // 将当前订阅者指向自己,缓存 var value = this.$vm[this.exp]; // 强制触发监听的getter,添加自己到属性订阅器中 Dep.target = null; // 添加完毕,重置释放 return value; }};
订阅者要缓存自己, 并且告诉监听器, 要把我加到订阅器里面去. 所以还要改造下 监听器
function defineReactive(data, key, val) { var dep = new Dep() observe(val); // 监听子属性 Object.defineProperty(data, key, { ....... get: function () { // 由于需要在闭包内添加watcher,所以可以在Dep定义一个全局target属性,暂存watcher, 添加完移除 Dep.target && dep.addDep(Dep.target); return val; },....... });}
实例化Watcher的时候,调用get()方法,通过Dep.target = watcherInstance标记订阅者是当前watcher实例,强行触发属性定义的getter方法,getter方法执行的时候,就会在属性的订阅器dep添加当前watcher实例,从而在属性值有变化的时候,watcherInstance就能收到更新通知。
实现MVVM
到这儿先将监听器 Observer 和监听者Watcher 连起来, 先模拟一些数据,实现简单的数据绑定这可以看到 div的和内容初始为 hello world , 每隔一秒之后变换为 chuchur 加时间戳,虽然是实现了, 但是与想象的还差很多. 是vue.name 不是 vue.data.name ,所以这里需要给Vue实例添加一个属性代理的方法,使访问vm的属性代理为访问vm.data的属性,改造后的代码如下:
function Vue(options) { this.$options = options || {}; this.data = this.$options.data; // 属性代理,实现 vm.xxx -> vm.data.xxx var self = this; Object.keys(this.data).forEach(function (key) { self.proxy(key); // 绑定代理属性 }); observe(this.data, this); el.innerHTML = this.data[exp]; // 初始化模板数据的值 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this;}Vue.prototype = { proxy: function (key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); }}
然后就可以通过vue.name,直接改版模板的数据了, 下一步就要实现 解析器Complie
[完]