题目
深层次数据变化如何逐层往上传播?
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元素,相当于也其父元素和其所有祖先元素。”(当然,你可以手动禁止事件传播) 所以,这里的本质是:"浏览器内部实现了一个事件传播的机制",你有信心自己实现一个吗?
我的代码
function Observer(obj) {
this.data = obj; // 对象挂载点
this.$p = Array.prototype.slice.call(arguments,1)[0] || 'data'; // 实现事件冒泡储存父级 名字
this.transformAll(obj); // 对obj对象进行 遍历, 然后调用convat 进行defineProperty改写
}
Observer.prototype.transformAll = function(obj) {
var keyarr = Object.keys(obj);
for (var i=0,len=keyarr.length;i<len;i++) {
var key = keyarr[i];
var value = obj[keyarr[i]];
if (value instanceof Object ) {
new Observer(value,this.$p + '.' + key); // value此时为对象,new Observer()会修改此对象,修改的结果展示在了obj当中。
} else {
this.convat(key, value, this.$p); // $p 传入父key
}
}
}
Observer.prototype.content = {}
Observer.prototype.convat = function(key, val, $p) {
var self = this;
Object.defineProperty(self.data, key, {
get: function(){
console.log('你访问了 ' + key);
return val;
},
set: function(newval){
var allkey = $p+ '.' + key;
console.log('你设置了 '+ key + ', ' + '新的值为 ' + newval);
self.emit(allkey, newval); // 触发形式为 father.child newval为传入信息
if (newval instanceof Object ) {
new Observer(newval, allkey); // 如果改写为对象
}
val = newval
},
enumerable : true,
configurable : true
});
}
Observer.prototype.$watch = function(name, fn) {
if (!this.content[name]) {
this.content[name] = [];
}
this.content[name].push(fn);
return this;
}
Observer.prototype.emit = function(name) {
if (name.indexOf('.') !== -1) {
var parent = name.split('.');
for (var i =0;i<parent.length;i++){
this.emit(parent[i]);
}
}
var info = Array.prototype.slice.call(arguments,1);
if (this.content[name]) { // 遍历同名事件仓库 执行
for (var i =0,len=this.content[name].length;i<len;i++) {
var fn = this.content[name][i];
fn.apply(this, info);
}
}
return this;
}
Observer.prototype.off = function(name, fn) { // 解除绑定,如果不传入函数名则取消所有同名事件
if (!fn) {
this.content[name] = null;
return this;
}
var index = this.content[name].indexOf(fn);
this.content[name].splice(index, 1);
return this;
}
实现方式
在动态数据绑定(二)中需要实现一个事件系统,我的实现是在原型链上建立一个content
属性保存所有需要绑定的事件名称
和触发函数
.如下:
Observer.prototype.content = {}
事件系统应该是能监听所有的实例化对象绑定的函数,在判断改写和深度convat
当中都会创建一个新的实例化对象,如果写到this
上就无法通用了。
还有一个痛点是如何知道父级
对象的key
值,好在函数里面对基本类型和对象类型的区分十分明了,只要在检测到是对象类型的哪一条路上多传入一个参数,传入当前的属性的key
给下层,下层再利用这个key
就好了。
想要得到冒泡,触发事件的时候就一定要携带上父级的key
信息,我使用了
new Observer(value,this.$p + '.' + key);
set: function(newval){
var allkey = $p+ '.' + key;
console.log('你设置了 '+ key + ', ' + '新的值为 ' + newval);
self.emit(allkey, newval); // 触发形式为 father.child newval为传入信息
if (newval instanceof Object ) {
new Observer(newval, allkey); // 如果改写为对象
}
val = newval
}
emit($p+ '.' + key, newval)
,触发的时候就变成了data.name.firstName
的形式,传入的$p
一定是保存着所有上层的key
值,接着再在emit
函数内部解析一下 ,触发data
、name
、firstName
即可。
有个问题,现在函数的执行顺序是由上到下了,明天写个setTimeout
,刚好可以加深理解js的任务循环机制?
写的很乱,脖子和腰都在抗议了,抽空从第一个任务写起来,今天先合电脑睡觉??
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。