Vue监听数据对象变化源码(2)

那如何讲 Observer、Watcher 两者关联起来呢?全局变量!这个全局变量,只有 Watcher 才做修改,Observer 只是读取判断,根据这个全局变量的值不同而判断是否 Watcher 对数据进行读取,这个全局变量可以附加在 dep 上:

dep.target = null;

根据以上所述,简单整理下,代码如下:

function Watcher(data, exp, cb) { this.data = data; this.exp = exp; this.cb = cb; this.value = this.get(); } Watcher.prototype.get = function() { // 给 dep.target 置值,告诉 Observer 这是 Watcher 调用的 getter dep.target = this; // 调用 getter,触发相应响应 var value = this.data[this.exp]; // dep.target 还原 dep.target = null; return value; }; Watcher.prototype.update = function() { this.cb(); }; function Observer(value) { this.value = value; this.walk(value); } Observer.prototype.walk = function(obj) { var keys = Object.keys(obj); for(var i = 0; i < keys.length; i++) { // 给所有属性添加 getter、setter defineReactive(obj, keys[i], obj[keys[i]]); } }; var dep = []; dep.target = null; function defineReactive(obj, key, val) { // 有自定义的 property,则用自定义的 property var property = Object.getOwnPropertyDescriptor(obj, key); if(property && property.configurable === false) { return; } var getter = property && property.get; var setter = property && property.set; // 递归的方式实现给属性的属性添加 getter、setter var childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function() { var value = getter ? getter.call(obj) : val; // 如果是 Watcher 监听的,就把 Watcher 对象压入 dep if(dep.target) { dep.push(dep.target); } return value; }, set: function(newVal) { var value = getter ? getter.call(obj) : val; // set 值与原值相同,则不更新 if(newVal === value) { return; } if(setter) { setter.call(obj, newVal); } else { val = newVal; } // 给新赋值的属性值的属性添加 getter、setter childOb = observe(newVal); // 按序执行 dep 中元素的 update 方法 for(var i = 0; i < dep.length; i++) { dep[i].update(); } } }); } function observe(value) { if(!value || typeof value !== 'object') { return; } return new Observer(value); }

var data = {a: 1}; new Observer(data); new Watcher(data, 'a', function(){console.log('it works')}); data.a =12; data.a =14;

上面基本实现了数据的监听,bug 肯定有不少,不过只是一个粗糙的 demo,只是想展示一个大概的流程,没有扣到非常细致。

Dep

上面几个例子,dep 是个全局的数组,但凡 new 一个 Watcher,dep 中就要多一个 Watcher 实例,这时候不管哪个 data 更新,所有的 Watcher 实例的 update 都会执行,这是不可接受的。

Dep 抽象出来,单独搞一个构造函数,不放在全局,就能解决了:

function Dep() { this.subs = []; } Dep.prototype.addSub = function(sub) { this.subs.push(sub); }; Dep.prototype.notify = function() { var subs = this.subs.slice(); for(var i = 0; i < subs.length; i++) { subs[i].update(); } }

利用 Dep 将上面的代码改写下就好了(当然,此处的 Dep 代码也不完全,只是一个大概的意思罢了)。

Vue 实例代理 data 对象

官方文档中有这么一句话:

每个 Vue 实例都会代理其 data 对象里所有的属性。

var data = { a: 1 }; var vm = new Vue({data: data}); vm.a === data.a // -> true // 设置属性也会影响到原始数据 vm.a = 2 data.a // -> 2 // ... 反之亦然 data.a = 3 vm.a // -> 3

这种代理看起来很麻烦,其实也是可以通过 Object.defineProperty 来实现的:

function Vue(options) { var data = this.data = options.data; var keys = Object.keys(data); var i = keys.length; while(i--) { proxy(this, keys[i]; } } function proxy(vm, key) { Object.defineProperty(vm, key, { configurable: true, enumerable: true, // 直接获取 vm.data[key] 的值 get: function() { return vm.data[key]; }, // 设置值的时候直接设置 vm.data[key] 的值 set: function(val) { vm.data[key] = val; } }; }

捏出一个 Vue,实现最初目标

var Vue = (function() { var Watcher = function Watcher(vm, exp, cb) { this.vm = vm; this.exp = exp; this.cb = cb; this.value = this.get(); }; Watcher.prototype.get = function get() { Dep.target = this; var value = this.vm._data[this.exp]; Dep.target = null; return value; }; Watcher.prototype.addDep = function addDep(dep) { dep.addSub(this); }; Watcher.prototype.update = function update() { this.run(); }; Watcher.prototype.run = function run() { this.cb.call(this.vm); } var Dep = function Dep() { this.subs = []; }; Dep.prototype.addSub = function addSub(sub) { this.subs.push(sub); }; Dep.prototype.depend = function depend() { if(Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify() { var subs = this.subs.slice(); for(var i = 0; i < subs.length; i++) { subs[i].update(); } }; Dep.target = null; var Observer = function Observer(value) { this.value = value; this.dep = new Dep(); this.walk(value); }; Observer.prototype.walk = function walk(obj) { var keys = Object.keys(obj); for(var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]); } }; function defineReactive(obj, key, val) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if(property && property.configurable === false) { return; } var getter = property && property.get; var setter = property && property.set; var childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; if(Dep.target) { dep.depend(); if(childOb) { childOb.dep.depend(); } } return value; }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; if(newVal === value) { return; } if(setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = observe(newVal); dep.notify(); } }); } function observe(value) { if(!value || typeof value !== 'object') { return; } return new Observer(value); } function Vue(options) { var vm = this; this._el = options.el; var data = this._data = options.data; var keys = Object.keys(data); var i = keys.length; while(i--) { proxy(this, keys[i]); } observe(data); var elem = document.getElementById(this._el); elem.innerHTML = vm.message; new Watcher(this, 'message', function() { elem.innerHTML = vm.message; }); } function proxy(vm, key) { Object.defineProperty(vm, key, { configurable: true, enumerable: true, get: function proxyGetter() { return vm._data[key]; }, set: function proxySetter(val) { vm._data[key] = val; } }); } return Vue; })();

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

转载注明出处:https://www.heiqu.com/wwwxsj.html