我们说到如果监听的属性是个对象呢?那么这个对象中的其他属性岂不就是监听不了了吗?如下:
倘若user中的name、age属性变化,如何知道它们变化了呢?今儿,就来解决这一问题。
通过走读Vue源码,发现他是利用Observer构造函数为每个对象创建一个Observer对象,来监听数据的,如果数据中的属性又是一个对象,那么就又通过Observer来监听嘛。
其实,核心思想就是树的先序遍历。如我们将上述Demo中的data数据,图形化一下,就更加明白了,如图:
理清了大体思路,下面我们就一起来创建一个Observer。
Observer整体结构如下:
function Observer(data) { //如若this不是Observer对象,即创建一个 if (!(this instanceof Observer)) { return new Observer(data); } this.data = data; this.walk(data);}let p = Observer.prototype = Object.create(null);p.walk = function(data) { /* TODO:监听data数据中的所有属性, 并查看data中属性值是否为对象, 若为对象,就创建一个Observer实例 */}p.convert = function(key, val) { //TODO:通过Object.defineProperty监听数据}
walk
首先,我们在walk方法中实现对data对象中的所有属性监听,如下:
p.walk = function(data) { let keys = Object.keys(data); keys.forEach(key => { let val = data[key]; this.convert(key, val); });}
由于属性中可能又会是一个对象,那么,我们就有必要监听它们。怎么办呢?如果是个对象,再次利用Observer构造函数,处理它不就完了么。如下:
p.walk = function(data) { let keys = Object.keys(data); keys.forEach(key => { let val = data[key]; //如果val为对象,则交给Observer处理 if (typeof val === 'object') { Observer(val); } this.convert(key, val); });}
你可能会有这样的疑问,如果直接利用Observer处理对象,那么不就与父对象失去关联了么?
然而并没有,因为JavaScript对于对象是指向地址关系,所以怎么会失去关联呢。
convert
对于convert方法,就比较简单了,一如既往就是利用Object.defineProperty监听数据,如下:
p.convert = function(key, val) { Object.defineProperty(this.data, key, { get: () => { console.log('访问了' + key + ' 值为' + val); return val; }, set: (newVal) => { console.log('设置了' + key + ' 值为' + newVal); if (newVal !== val) { val = newVal; } } });}
完整代码:
function Observer(data) { //如若this不是Observer对象,即创建一个 if (!(this instanceof Observer)) { return new Observer(data); } this.data = data; this.walk(data);}let p = Observer.prototype = Object.create(null);p.walk = function(data) { let keys = Object.keys(data); keys.forEach(key => { let val = data[key]; //如果val为对象,则交给Observer处理 if (typeof val === 'object') { Observer(val); } this.convert(key, val); });}p.convert = function(key, val) { Object.defineProperty(this.data, key, { get: () => { console.log('访问了' + key + ' 值为' + val); return val; }, set: (newVal) => { console.log('设置了' + key + ' 值为' + newVal); if (newVal !== val) { val = newVal; } } });}
使用: