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

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

Awesome Vuejs.png

动态数据绑定(二)
  • <a href="http://ife.baidu.com/course/detail/id/20">题目</a>
  • <a href="https://github.com/CaiYiLiang/2017ife-Baidu/blob/master/vue-DynamicDataBinding%2302.js">任务源码</a>
  • 考察知识点:
    递归Recursion
    发布-订阅模式

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

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

看到任务二,发现了在<a href="http://www.jianshu.com/p/3fb7c2a6b047">任务一</a>写的代码有一点问题,就是无法对“比较深”的对象的进行有效访问:

无法对“比较深”的对象的进行有效访问

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

在<a href="https://github.com/CaiYiLiang/2017ife-Baidu/blob/master/vue-DynamicDataBinding%2302.js">任务二</a>中,关键的是在function函数中定义this.data = obj;令每个实例对象的data与传入的对象obj指向相同的内存空间(不要忘了,dataobj都是引用类型,它们的值都是指向同一个地址的"指针")。

当遍历到较深的属性时,利用递归new Observer(obj[key])实现较深的属性的getter/setter定义。同时,在对象实例设置新的值是一个对象的时候,也是利用递归new Observer(newValue)对新增的"较深"属性定义getter/setter响应。
不多说,上代码,只是<a href="https://github.com/CaiYiLiang/2017ife-Baidu/blob/master/vue-DynamicDataBinding%2301.js">任务一</a>的修正和拓展。

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
      }
  })
 })
}
访问"较深"的属性值

#######~~(╯﹏╰)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()方法,直到超过最大的栈数。


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

现在了解发布-订阅模式
发布-订阅模式

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

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

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

例如订阅周刊

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

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

例如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")

例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")
例2

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

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

回归到<a href="https://github.com/CaiYiLiang/2017ife-Baidu/blob/master/vue-DynamicDataBinding%2302.js">任务二</a>,
实现订阅者的订阅功能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
Test Case
Reference

系列目录

<a href="http://www.jianshu.com/p/3fb7c2a6b047">ife.baidu笔记 | 动态数据绑定(一)</a>

原创文章

简书:<a href="http://www.jianshu.com/u/c0600377679d">HelloCherry</a>
Github: <a href="https://github.com/CaiYiLiang">CaiYiLiang</a>
Girhub / vue-demos:

  • <a href="https://github.com/CaiYiLiang/simply-calculator-vuejs" >利用vue.js实现简易计算器</a>
  • <a href="https://github.com/CaiYiLiang/vue-demos/tree/master/wikipediaViewer-vuejs">实现简单的单页面应用(vue2.0,vue-router,vue-cliand ajax(jsonp))</a>
  • <a href="https://github.com/CaiYiLiang/vue-demos/tree/master/shoppingcart-vuejs" >利用vue.js,vuex,vue-router和 Element UI实现购物车场景</a>
    很高兴写的<a href="https://github.com/CaiYiLiang/vue-demos">vue demos</a>被收录到 <a href="https://github.com/vuejs/awesome-vue">awesome-vue</a>中,简直就是一朵小红花😋

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容