david2tdw / blog

学习记录
1 stars 1 forks source link

[Pattern] 发布-订阅模式 #149

Open david2tdw opened 4 years ago

david2tdw commented 4 years ago

定义:

让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。

场景:

DOM事件。

实现:

let event = {
  clientList: {}, // 所有的事件挂载到clientList对象上
  listen(key, fn) {
    if (!this.clientList[key]) {
      // 队列不存在,创建一个新的空事件队列
      this.clientList[key] = [] // 值是个数组,可以放多个事件
    }
    this.clientList[key].push(fn) // 订阅的消息添加进缓存列表
  },
  trigger(type, money) {
    let fns = this.clientList[type]
    if (!fns || fns.length === 0) {
      // 如果没有绑定对应的消息
      return false
    }
    fns.forEach(fn => {
      fn.apply(this, [money]) // apply()参数是个数组
    })
  }
}

let installEvent = obj => { // 将event对象上的所有属性挂载到不同的对象上面(salesOffices)
  for (let i in event) {
    // console.log(i, event[i])
    obj[i] = event[i]
  }
}

let salesOffices = {}
installEvent(salesOffices) // salesOffices拥有clientList,listen和trigger属性。

// 小明订阅信息
salesOffices.listen('squareMeter88', price => {
  console.log('小明,你看中的88平方的房子,价格=' + price)
})

// 小光订阅信息
salesOffices.listen('squareMeter88', price => {
  console.log('小光,你看中的88平方的房子,价格=' + price)
})
// 小红订阅信息
salesOffices.listen('squareMeter100', price => {
  console.log('小红,你看中的100平方的房子,价格=' + price)
})

salesOffices.trigger('squareMeter88', 20000)
salesOffices.trigger('squareMeter100', 80000)

JS发布-订阅模式

david2tdw commented 4 years ago

发布-订阅模式的代码封装

var event  = {
  list: [],
  // 添加注册事件到队列中
  listen: function (key, fn) {
    if (!this.list[key]) { // 对象内,用this调用对象的属性
      this.list[key] = []
    }
    this.list[key].push(fn);
  },
  // 通过trigger去查找已经注册过的事件,找到后去执行事件的具体函数,输出对应的内容。
  trigger: function () {
    console.log(arguments)
    // shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
    // 绑定arguments对象
    // arguments是一个数组, 想当与arguments.shift().
    var key = Array.prototype.shift.apply(arguments); // 使用call / apply都可以
    var fns = this.list[key];
    if (!fns || fns.length === 0) {
      return;
    }
    fns.forEach(fn => {
      fn.apply(this, arguments); // 执行对应的注册函数
    })
  }

}

var initEvent  = function (obj) {
  for (var i in event) {
    obj[i] = event[i]
  }
}

var shoesOjb = {}
initEvent(shoesOjb);

shoesOjb.listen('red', size => {
  console.log("red尺码是:"+size);
})

shoesOjb.listen('black', size => {
  console.log("black尺码是:"+size); 
})

shoesOjb.trigger('red', 20);
shoesOjb.trigger('black', 60);

Javascript中理解发布--订阅模式

david2tdw commented 4 years ago

取消订阅事件

var event  = {
  list: [],
  // 添加注册事件到队列中
  listen: function (key, fn) {
    if (!this.list[key]) { // 对象内,用this调用对象的属性
      this.list[key] = []
    }
    this.list[key].push(fn);
  },
  // 通过trigger去查找已经注册过的事件,找到后去执行事件的具体函数,输出对应的内容。
  trigger: function () {
    var key = Array.prototype.shift.apply(arguments); // 使用call / apply都可以
    var fns = this.list[key];
    if (!fns || fns.length === 0) {
      return;
    }
    fns.forEach(fn => {
      fn.apply(this, arguments); // 执行对应的注册函数
    })
  },
  // 取消订阅事件
  remove: function (key, fn) {
    var fns = this.list[key]; // 返回对应key的所有函数
    if (!fns) { // 如果key对应的消息没有订阅过的话,则返回
      return false
    }
    if (!fn) { // 如果没有传入具体的回调函数,表示需要取消key对应消息的所有订阅
      fns && (fns.length = 0)
    } else  {
      fns.forEach((vFn, index) => {
        if (vFn === fn) {
          fns.splice(index, 1); //  删除订阅者的回调函数
        }
      })
    }
  }
}

var initEvent  = function (obj) {
  for (var i in event) {
    obj[i] = event[i]
  }
}

var shoesOjb = {}
initEvent(shoesOjb);

shoesOjb.listen('red', fn1 = size => {
  console.log("red尺码是:"+size);
})

shoesOjb.listen('black', fn2 = size => {
  console.log("black尺码是:"+size); 
})

shoesOjb.trigger('red', 20);
shoesOjb.trigger('black', 30);

console.log('移除监听事件red.')
shoesOjb.remove('red', fn1);
console.log('再次触发事件red black.')
shoesOjb.trigger('red', 40);
shoesOjb.trigger('black', 50);
david2tdw commented 4 years ago

模块间通信

监听页面点击事件,并显示点击结果。

event.js

// 全局发布-订阅模式对象, 通过自执行函数return一个对象,将内部方法暴露出去。
var Event = (function() {
  var list = {},
    listen,
    trigger,
    remove
  listen = function(key, fn) {
    if (!list[key]) {
      list[key] = []
    }
    list[key].push(fn)
  }
  trigger = function() {
    var key = Array.prototype.shift.call(arguments),
      fns = list[key]
    if (!fns || fns.length === 0) {
      return false
    }
    for (var i = 0, fn; (fn = fns[i++]); ) {
      fn.apply(this, arguments)
    }
  }
  remove = function(key, fn) {
    var fns = list[key]
    if (!fns) {
      return false
    }
    if (!fn) {
      fns && (fns.length = 0)
    } else {
      for (var i = fns.length - 1; i >= 0; i--) {
        var _fn = fns[i]
        if (_fn === fn) {
          fns.splice(i, 1)
        }
      }
    }
  }
  // 通过自执行函数return一个对象,将内部方法暴露出去
  return {
    listen: listen,
    trigger: trigger,
    remove: remove
  }
})()

a.js

// a.js 负责处理点击操作及发布消息
// 不需要引入event.js, 会在主页面引入
var a = (function () {
  var count = 0 ;
  var button = document.getElementById('count')
  button.onclick = function () {
    Event.trigger('add', count++)
  }
})();

b.js

// b.js 负责监听add这个消息,并把点击的总次数显示到页面上来
// 不需要引入event.js, 会在主页面引入
var b = (function() {
  var div = document.getElementById('showcount')
  Event.listen('add', function(count) {
    div.innerHTML = count
  })
})()

main.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>发布-订阅模式,模块间通信</title>
  <script src="event.js"></script>
</head>
<body>
  <button id="count">click me</button>
  <div id="showcount"></div>
  <script src='a.js'></script>
  <script src="b.js"></script>
</body>
</html>

Javascript中理解发布--订阅模式