Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

订阅-发布模式 #65

Open
amandakelake opened this issue Jan 16, 2019 · 0 comments
Open

订阅-发布模式 #65

amandakelake opened this issue Jan 16, 2019 · 0 comments

Comments

@amandakelake
Copy link
Owner

初学者都会用的订阅-发布模式

只要我们操作过DOM,都已经使用过订阅发布模式了,即使初学那会并不知道我们用了这种模式,看下这段代码:

document.getElementById("myId").addEventListener('click', () => {
  //... do something
})

只要用户点击了id是myId的DOM元素,就会自动执行注册的匿名函数

这里相当于订阅了这个DOM节点的click事件,然后等待用户发布事件

当然,我们也可以随意的增加多个订阅者,或者删除订阅者

const ele = document.getElementById("myId");
ele.addEventListener('click', () => alert('hi'));
ele.addEventListener('click', () => alert('hello'));
ele.addEventListener('touchmove', () => alert('world'));

// 注意,通过 addEventListener() 来添加的匿名函数无法移除
ele.removeEventListener('click', handler);

订阅-发布的简单实现

除了常见的DOM事件,在日常业务开发中,我们经常会碰到需要实现自定义事件的场景,或者我们常用的vue框架也是利用订阅发布模式来实现的

下面我们一步步来实现一个简单的订阅发布模式,以下是核心步骤

  • 创建一个全局对象作为发布者,发布者需要维护一个缓存列表hub,用于存放回调函数
  • 实现订阅功能on,订阅事件,并注册回调函数,保存到发布者的缓存列表中
  • 实现发布功能emit,发布事件,执行对应事件的回调函数
  • 实现取消订阅功能off
export const createEventHub = () => ({
  // 生成一个空对象,该对象不继承Object.prototype的属性,比如toString这些它都不需要
  // hub的格式: { [event1]: [handler1, handler2, handler3, ...], [event2]: [], [event3]: [], ...}
  hub: Object.create(null),
  // 订阅事件,并注册回调函数handler
  on(event, handler) {
    // 如果事件不存在,创建一个event空数组
    if (!this.hub[event]) {
      this.hub[event] = [];
    }
    this.hub[event].push(handler);
  },
  // 发布事件
  emit(event, data) {
    (this.hub[event] || []).forEach(handler => handler(data));
  },
  // 取消订阅
  off(event, handler) {
    // 寻找hub[event]里是否存在这个handler
    const i = (this.hub[event] || []).findIndex(h => h === handler);
    if (i > -1) {
      // 如果存在,移除该事件所绑定的handler
      this.hub[event].splice(i, 1);
    }
  }
})

使用方式

const eventHub = createEventHub();
let createEventHubVariable = 1;

const handler = data => {
  console.log(data);
}
// hub订阅了myEvent, myEvent2 两个事件
// 其中myEvent事件注册了两个回调handler 和 匿名函数
eventHub.on('myEvent', handler);
eventHub.on('myEvent', () => {
  console.log('I am handler 2');
  console.log('eventHub', eventHub)
});
eventHub.on('myEvent2', () => {
  createEventHubVariable++;
  console.log('eventHub', eventHub);
});

// 某些用户或者数据变化产生的交互:发布
// 传入不同的参数,执行回调handler, 同时也会执行上面注册的匿名函数
eventHub.emit('myEvent', 'I am a string');
eventHub.emit('myEvent', { arg: 'I am a object'});
eventHub.emit('myEvent2');

// 只移除了handler, 匿名函数还是在的
eventHub.off('myEvent', handler)

Demo

下面的完整代码都可以在这里找到codepen: 订阅-发布模式, 自己随意改改代码就知道来龙去脉了

dec879a8-6948-4ea1-9b9d-0ae4acc0f6ea

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>FE-environment</title>
  <style>
    button {
      background-color: #e6e6e6;
      padding: 5px 10px;
      border-radius: 2px;
      margin: 5px 10px;
    }
  </style>
</head>

<body>
  <h1>FE-environment</h1>
  <div>发布订阅简易版本</div>
  <div>
    <button id="log-hub">log hub</button>
    <button id="log-va">log变量</button>
  </div>
  <div>
    <span>变量createEventHubVariable</span>
    <span id="createEventHubVariable"></span>
  </div>
  <div>
    <button id="subscribe-myEvent-h">订阅myEvent handler</button>
    <button id="subscribe-myEvent-u">订阅myEvent 匿名函数</button>
    <button id="subscribe-myEvent2">订阅myEvent2</button>
  </div>
  <div>
    <button id="emit-myEvent">发布myEvent</button>
    <button id="emit-myEvent2">发布myEvent2</button>
  </div>
  <div>
    <button id="off-myEvent-handler">移除myEvent handler</button>
  </div>

</body>

</html>
   button {
      background-color: #e6e6e6;
      padding: 5px 10px;
      border-radius: 2px;
      margin: 5px 10px;
    }
const createEventHub = () => ({
  // 生成一个空对象,该对象不继承Object.prototype的属性,比如toString这些它都不需要
  // hub的格式: { [event1]: [handler1, handler2, handler3, ...], [event2]: [], [event3]: [], ...}
  hub: Object.create(null),
  // 订阅事件,并注册回调函数handler
  on(event, handler) {
    // 如果事件不存在,创建一个event空数组
    if (!this.hub[event]) {
      this.hub[event] = [];
    }
    this.hub[event].push(handler);
  },
  // 发布事件
  emit(event, data) {
    (this.hub[event] || []).forEach(handler => handler(data));
  },
  // 取消订阅
  off(event, handler) {
    // 寻找hub[event]里是否存在这个handler
    const i = (this.hub[event] || []).findIndex(h => h === handler);
    if (i > -1) {
      // 如果存在,移除该事件所绑定的handler
      this.hub[event].splice(i, 1);
    }
  }
})
const eventHub = createEventHub();

let createEventHubVariable = 1;
document.getElementById("createEventHubVariable").innerHTML = createEventHubVariable;

const handler = data => {
  console.log(data);
}
/*
// hub订阅了myEvent, myEvent2 两个事件
// 其中myEvent事件注册了两个回调handler 和 匿名函数
eventHub.on('myEvent', handler);
eventHub.on('myEvent', () => {
  console.log('I am handler 2');
  console.log('eventHub', eventHub)
});
eventHub.on('myEvent2', () => {
  createEventHubVariable++;
  console.log('eventHub', eventHub);
});

// 某些用户或者数据变化产生的交互:发布
// 传入不同的参数,执行回调handler, 同时也会执行上面注册的匿名函数
eventHub.emit('myEvent', 'I am a string');
eventHub.emit('myEvent', { arg: 'I am a object'});
eventHub.emit('myEvent2');

// 只移除了handler, 匿名函数还是在的
eventHub.off('myEvent', handler)
*/
document.getElementById("log-hub").addEventListener('click', () => {
  console.log('eventHub', eventHub);
})

document.getElementById("log-va").addEventListener('click', () => {
  console.log('createEventHubVariable', createEventHubVariable);
})

document.getElementById("subscribe-myEvent-h").addEventListener('click', () => {
  eventHub.on('myEvent', handler);
  console.log('eventHub', eventHub);
})

document.getElementById("subscribe-myEvent-u").addEventListener('click', () => {
  eventHub.on('myEvent', () => console.log('I am handler 2'));
  console.log('eventHub', eventHub)
})

document.getElementById("subscribe-myEvent2").addEventListener('click', () => {
  eventHub.on('myEvent2', () => {
    createEventHubVariable++;
    document.getElementById("createEventHubVariable").innerHTML = createEventHubVariable;
  });
  console.log('eventHub', eventHub)
})

document.getElementById("emit-myEvent").addEventListener('click', () => {
  eventHub.emit('myEvent', 'I am a string');
  eventHub.emit('myEvent', {
    arg: 'I am a object'
  });
})

document.getElementById("emit-myEvent2").addEventListener('click', () => {
  eventHub.emit('myEvent2');
  document.getElementById("createEventHubVariable").innerHTML = createEventHubVariable;
  console.log('createEventHubVariable', createEventHubVariable);
})

document.getElementById("off-myEvent-handler").addEventListener('click', () => {
  eventHub.off('myEvent', handler);
  console.log('eventHub', eventHub);
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant