题目

深层次数据变化如何逐层往上传播?

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函数内部解析一下 ,触发datanamefirstName即可。

有个问题,现在函数的执行顺序是由上到下了,明天写个setTimeout,刚好可以加深理解js的任务循环机制?

写的很乱,脖子和腰都在抗议了,抽空从第一个任务写起来,今天先合电脑睡觉??


发条橙子
399 声望14 粉丝

我爱吃西瓜