Vue 动态数据绑定(三)-- 深层数据变化向上传播

任务说明地址

任务描述
这是“动态数据绑定”的第三题。在第二题的基础上,我们再多考虑一个问题:”深层次数据变化如何逐层往上传播”。举个例子。

let app2 = new Observer({
    name: {
        firstName: 'shaofeng',
        lastName: 'liang'
    },
    age: 25
});

app2.$watch('name', function (newName) {
    console.log('我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。')
});

app2.data.name.firstName = 'hahaha';
// 输出:我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。
app2.data.name.lastName = 'blablabla';
// 输出:我的姓名发生了变化,可能是姓氏变了,也可能是名字变了。

观察到了吗?firstName 和 lastName 作为 name 的属性,其中任意一个发生变化,都会得出以下结论:”name 发生了变化。”这种机制符合”事件传播“机制,方向是从底层往上逐层传播到顶层。
这现象想必你们也见过,比如:“点击某一个DOM元素,相当于也其父元素和其所有祖先元素。”(当然,你可以手动禁止事件传播) 所以,这里的本质是:”浏览器内部实现了一个事件传播的机制”,你有信心自己实现一个吗?


所以重点是:深层次数据变化如何逐层往上传播

在任务二中,只能注册监听对象的第一层属性,深层属性的监听未完成。因为每一层的 event 都是独立的,深层对象属性触发的时候可以看出 this.events 里为空。所以要实现 event 之间的通信,使得可以向上传播。 一种方式: 每次进行 new Observer 的时候记录路径 set 时解析路径,虽然可以实现,但是也太残暴了,不符合向上传播的思想。

重点在于“冒泡”,让父级事件知道子级触发了事件,怎么实现事件冒泡呢,冒泡啊冒泡。也就是当触发子级事件时(也就是 set 方法里),能拿到父级对象,触发父级的 Event 对象的 emit 方法。所以思路就理清了,在创建对象的时候,记录父级对象,将父级对象传入子级中,再对属性路径进行整合解析,当触发set时进行冒泡,触发父级相应的事件(又是一个递归))。所以构造函数就需要多加两个参数,一个是路径 path,一个是父级对象 $parent。

最终代码(Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
function Observer(data, path, $parent) {
this.data = data;
this.$parent = (typeof $parent === 'undefined') ? null : $parent;
this.makeObserver(data, path);
this.eventsBus = new Event();
}

Observer.prototype.makeObserver = function(data, path) {
var self = this;
for (var i in data) {
if (data.hasOwnProperty(i)) {
var new_path = (typeof path === 'undefined') ? i : (path + '.' + i);
if (typeof data[i] === 'object') {
new Observer(data[i], new_path, self);
} else {
this.getset(i, data[i], new_path);
}
}
}
}


Observer.prototype.getset = function (i, value, path) {
let val = value;
let self = this;
Object.defineProperty(this.data, i, {
configurable: true,
enumerable: true,
get: function () {
console.log('你访问了' + i);
return val;
},
set: function (newval) {
self.bubble(self, path, val, newval); // 事件冒泡
if(typeof newval === 'object') {
new Observer(val);
}
console.log('你设置了' + i + ',新的值为' + newval);
val = newval;
}
})
}

// 往上层传递 沿着父对象往上传递
Observer.prototype.bubble = function (self, path, val, newval) {
if (self === null)
return;
self.eventsBus.emit(path, val, newval);
var parent_path = path.split('.').slice(0, -1).join('.'); // 拆出父级的路径
self.bubble(self.$parent, parent_path, val, newval); //关键点 递归触发父级的emit
}

function Event() {
this.events = {};
}
Event.prototype.on = function (attr, callback) {
if (this.events[attr]){
this.events[attr].push(callback);
} else {
this.events[attr] = [callback];
}
}
Event.prototype.emit = function (attr, val, newval) {
this.events[attr] && this.events[attr].forEach(function(item) {
item(newval);
})
}

Observer.prototype.$watch = function (attr,callback){
// 注册一个监听事件
this.eventsBus.on(attr, callback);
}

let app1 = new Observer({
age: 25,
name: {
firstName: 'rui',
lastName: 'sun'
}
})
app1.$watch('age', function(age) {
console.log(`我的年纪变了,现在已经是:${age}岁了`)
});

app1.$watch('name', function(newName) {
console.log('我的名字变了,新的值是' + newName);
});

console.log(app1.data.age = 100);
console.log(app1.data.name.lastName = 'ruirui');