ife.baidu笔记 | 动态数据绑定(二)

你踩过的坑会帮助你日后走得更快

ife.baidu笔记 | 动态数据绑定(二)_第1张图片
Awesome Vuejs.png

动态数据绑定(二)

任务二主要涉及两大块:访问引用类型的属性,利用发布-订阅模式实现事件监听。

先讲讲如何解决“比较深”的属性

看到任务二,发现了在任务一写的代码有一点问题,就是无法对“比较深”的对象的进行有效访问:

ife.baidu笔记 | 动态数据绑定(二)_第2张图片
无法对“比较深”的对象的进行有效访问

任务一的代码中,为了使实例通过person1.data.进行属性访问,新建了原型对象属性Observer.prototype.data = {},但是当传入参数对象是一个“比较深”的对象(属性值也是对象),就无法建立如例子中的person1.data.address.add1属性访问,因此任务一的代码需要重构。踩坑,会沉没一点时间成本,但也是一个自我发现和进步的机会。你踩过的坑会帮助你日后走得更快。

任务二中,关键的是在function函数中定义this.data = obj;令每个实例对象的data与传入的对象obj指向相同的内存空间(不要忘了,dataobj都是引用类型,它们的值都是指向同一个地址的"指针")。

当遍历到较深的属性时,利用递归new Observer(obj[key])实现较深的属性的getter/setter定义。同时,在对象实例设置新的值是一个对象的时候,也是利用递归new Observer(newValue)对新增的"较深"属性定义getter/setter响应。
不多说,上代码,只是任务一的修正和拓展。

function Observer(obj){
  this.data = obj;
  this.walk(obj);
}

Observer.prototype.walk = function(obj) {
  Object.keys(obj).forEach(key => {
    let val = obj[key]
    console.log(key)
    if(typeof obj[key] === "object"){
      new Observer(obj[key])
    }

    Object.defineProperty(this.data,key,{
     enumerable: true,
     configurable: true,
     get:function(){ 
       console.log("You are visiting the attribute: "+ key +" - " + val)
       return val },
     set:function(newValue) {
       if(typeof newValue === 'object'){
       new Observer(newValue)
       } 
       console.log("You are updating the attribute: "+ key +" - "+ newValue)
       val = newValue
      }
  })
 })
}
ife.baidu笔记 | 动态数据绑定(二)_第3张图片
访问"较深"的属性值

#######~~(╯﹏╰)b一不小心又是自己挖的坑
在写Object.definePropertygetter/setter的时候,遇到了一个Uncaught RangeError: Maximum call stack size exceeded,其中代码是这样的

 Object.defineProperty(this.data,key,{
 get:function(){ 
       console.log("You are visiting the attribute: "+ key +" - " + obj[key])
       return obj[key] },
....
}

getter里访问obj[key]就相当于陷入死循环无限调用get()方法,直到超过最大的栈数。


解决了任务二中的问题一和问题二,

现在了解发布-订阅模式
ife.baidu笔记 | 动态数据绑定(二)_第4张图片
发布-订阅模式

观察者模式又叫做发布-订阅模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察着对象。
看完定义有点懵逼,我把发布-订阅模式定义为以下三点:

  • 发布-订阅模式由两个角色组成:(事件)发布者和订阅者。
  • 订阅者向(事件)发布者进行注册(订阅),在事件发布时被通知。
  • 发布者在事件发生时向(之前在他这里进行注册/订阅的)订阅者发送通知。

这个情景是不是似曾相识?其实存在于我们生活的方方面面。

例如订阅周刊

小明(订阅者)对前端周刊(主题/发布者)有兴趣,于是交钱订阅每期的前端周刊(订阅/注册事件),并计划把每期收到的前端周刊(事件发布)带回学校阅读(订阅者在事件发生后所进行的动作)。

小红(订阅者)也对前端周刊(主题/发布者)有兴趣,于是也交钱订阅每期的前端周刊(订阅/注册事件),但小红只是想收藏书籍,所以就把每期收到的前端周刊(事件发布)放到家里书柜(订阅者在事件发生后所进行的动作)。

例如Github上的watch按钮

小明(订阅者)在github上建立了一个开源项目,他想在有人start了他的项目/有人向他提出issue/有人pull request的时候(主题/发布者)及时收到邮件通知,于是他点击了watch按钮(订阅/注册事件)进行通邮件知设置,以便他及时处理issue/PR(订阅者在事件发生后所进行的动作)。

代码如下:

// 主题发布者
function Publisher() {  
    this.subscribers = {};
    // 用于存储某事件对应的订阅者列表....
}

// 添加一个发布功能(方法),在事件发生时通知订阅者名单里的每一个人 
Publisher.prototype.publish = function(eventType) {  
    if(eventType in this.subscribers){
       this.subscribers[eventType].forEach(function(subscriberCb){
         subscriberCb();
       })
    }
}

// 观察者/订阅者
function Observer() {

}

// 订阅者有订阅/注册能力
Observer.prototype.subscribe = (publisher,eventType,cb) =>{    
  if(!publisher.subscribers.hasOwnProperty(eventType)){
    publisher.subscribers[eventType] = []
  }
  publisher.subscribers[eventType].push(cb)
}

测试
// 实例化
// 例1.
var techbook_Publisher = new Publisher()
var xiaoming = new Observer()
var xiaohong = new Observer()

//读者进行订阅/注册
xiaoming.subscribe(techbook_Publisher,"web-book",function(){
                   console.log("小明要把期刊带回学校阅读")
})
xiaohong.subscribe(techbook_Publisher,"web-book",function(){
                   console.log("小红要把期刊在家里收藏")
})

//出版社出版期刊,事件发生
techbook_Publisher.publish("web-book")

ife.baidu笔记 | 动态数据绑定(二)_第5张图片
例1

再看看例2

// 例2.
var github = new Publisher()
var xiaoming = new Observer()

//用户对该仓库的动态进行订阅/注册
xiaoming.subscribe(github,"watch-this-repo",function(){console.log("小明的github-repo在有人提issue/PR时收到邮件通知")})

github.publish("watch-this-repo")
ife.baidu笔记 | 动态数据绑定(二)_第6张图片
例2

当然,订阅者还会拥有取消订阅功能,以及在事件发生后不同订阅者可以有不同反应(或者有参数传入)。
因此,可以总结出发布-订阅模式的流程:

  1. 订阅者订阅某个主题(或者关注某个发布者)
  2. 某个主题(某个发布者)将该订阅者记录进待通知名单
  3. 在某个情景下主题发布,通知在待通知名单上的各个读者,各个读者进行各自的后续操作。

回归到任务二
实现订阅者的订阅功能Observer.prototype.$watch以及某时刻事件触发(发布)功能Observer.prototype.$change,并在对象实例属性值变化的时候调用Observer.prototype.$change

Observer.prototype.oberseredList = {}  //subscriber list,shared property with oberser&publisher
let oberseredList = Observer.prototype.oberseredList

Observer.prototype.$watch = (oberseredKey,cb) =>{    //subscriber register
  if(!oberseredList.hasOwnProperty(oberseredKey)){
    oberseredList[oberseredKey] = []
  }
  oberseredList[oberseredKey].push(cb)
}

Observer.prototype.$change = function(oberseredKey,newValue){  //Event Trigger
  let params = Array.prototype.slice.call(arguments,1)
  if(oberseredList.hasOwnProperty(oberseredKey)) { 
     oberseredList[oberseredKey].forEach(cb => {
      cb.apply(null,params)
     })
  }
  
}

还有关键在set里调用Observer.prototype.$change

 set:function(newValue) {
       ......
       Observer.prototype.$change.call(this,key,newValue)  //Event Trigger 
      ....
      }

/*Test Case*/
var person1 = new Observer({name:"xiaoming", age:20, 
address:{add1:"China",add2:"UK"} });
person1.data.age
person1.$watch('age', function(age) {
         console.log(`我的年纪变了,现在已经是:${age}岁了`)
 });
person1.data.age = 55
ife.baidu笔记 | 动态数据绑定(二)_第7张图片
Test Case
Reference
  • 谈谈 JavaScript 的观察者模式(自定义事件)
  • 理解 javascript 观察者模式 (订阅者与发布者)

系列目录

ife.baidu笔记 | 动态数据绑定(一)

原创文章

HelloCherry
Github: CaiYiLiang
Girhub / vue-demos:

如果觉得有一点点帮助,一个❤❤就是鼓励(。⌒∇⌒)

你可能感兴趣的:(ife.baidu笔记 | 动态数据绑定(二))