Vue 动态数据绑定(二)-- 深度监听

任务说明地址

任务描述
这是“动态数据绑定”系列的第二题。在第一题的基础上,我们继续考虑以下难点:
如果传入参数对象是一个“比较深”的对象(也就是其属性值也可能是对象),那该怎么办呢?举个例子。

// 一个“比较深”的对象:某些属性的值也是一个对象
let obj = {
 a: 1,
 b: 2,
 c: {
     d: 3,
     e: 4
 }
}

如果设置新的值是一个对象的话,新设置的对象的属性是否能继续响应 getter 和 setter。举个例子。

let app1 = new Observer({
         name: 'youngwind',
         age: 25
 });
app1.data.name = {
         lastName: 'liang',
         firstName: 'shaofeng'
 };
app1.data.name.lastName;
 // 这里还需要输出 '你访问了 lastName '
app1.data.name.firstName = 'lalala';
 // 这里还需要输出 '你设置了firstName, 新的值为 lalala'

考虑传递回调函数。在实际应用中,当特定数据发生改变的时候,我们是希望做一些特定的事情的,而不是每一次都只能打印出一些信息。所以,我们需要支持传入回调函数的功能。举个例子。

let app1 = new Observer({
         name: 'youngwind',
         age: 25
 });
 // 你需要实现 $watch 这个 API
 app1.$watch('age', function(age) {
         console.log(`我的年纪变了,现在已经是:${age}岁了`)
 });
 app1.data.age = 100; // 输出:'我的年纪变了,现在已经是100岁了'

如果传入参数对象是一个“比较深”的对象(也就是其属性值也可能是对象),那该怎么办呢?

即如果传入的又是一个对象呢?需要将“深对象”也绑定 get、set 方法,想到用递归的方法,对属性进行判断,如果属性值是对象,则重新 new 一个 Observer 出来。即:

Observer.prototype.makeObserver = function(data) {
  for(var i in data) {
    if(data.hasOwnProperty(i)) {
      if(typeof data[i] === 'object') {
        new Observer(data[i]);
      }
      this.getset(i, data[i]);
    }
  }
}

如果设置新的值是一个对象的话,新设置的对象的属性是否能继续响应 getter 和 setter 呢?

例如:

let app = new Observer({
    basicInfo: {
        name: 'ruirui',
        age: 25
    },
    address: 'Beijing'
})
//要实现的结果如下
app.data.basicInfo = {like: 'movie'}//你设置了basicInfo,新的basicInfo为{like: 'movie'}
app.data.basicInfo.like //你访问了basicInfo,你访问了like

同上,需要在 set 中进行判断,如果是对象,重新 new 一个 Observer。

考虑传递回调函数。在实际应用中,当特定数据发生改变的时候,我们是希望做一些特定的事情,而不是每一次只能打印出来一些信息,所以,我们需要支持传入回调函数的功能。

对属性绑定回调函数,需要的时候触发。即:每次 $watch 一个属性相当于注册一个监听事件,每次属性发生改变(set)的时候触发该事件。采用发布-订阅模式,实现一个自定义事件,可以对一个属性注册多个事件,触发的时候依次调用。

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, newval) {
  console.log(this.events); // 每一层的event都是独立的,深层对象属性触发的时候可以看出this.events里为空。
  for(var item in this.events) {
    if(item === attr) {
      this.events[attr].forEach(function(item) {
        item(newval);
      })
    }
  }
}

这样的话,每次 new 一个 Observer 实例时,就 new 一个 Event 实例管理所有的事件。在 set 中触发事件
Demo

function Observer(data) {
  this.data = data;
  this.makeObserver(data);
  this.eventsBus = new Event();
}
Observer.prototype.makeObserver = function(data) {
  for(var i in data) {
    if(data.hasOwnProperty(i)) {
      if(typeof data[i] === 'object') {
        new Observer(data[i]);
      }
      this.getset(i, data[i]);
    }
  }
}
Observer.prototype.getset = function(i, value) {
  let val = value;
  let that = this;
  Object.defineProperty(this.data, i, {
    configurable: true,
    enumerable: true,
    get: function () {
      console.log('你访问了' + i);
      return val;
    },
    set: function (newval) {
      console.log('你设置了' + i + ',新的值为' + newval);
      if(typeof newval === 'object') {
        new Observer(newval);
      }
      // 触发
      that.eventsBus.emit(i, newval);
      val = newval;
    }
  })
}
Observer.prototype.$watch = function (attr,callback){
    // 注册一个监听事件
    this.eventsBus.on(attr, callback);
}

至此,完成了事件监听。

目前只能注册监听对象到第一层属性,深层对象属性的监听未完成,看下回分析。