【05】前端常用的设计模式之观察者模式

111 阅读3分钟

观察者模式

在前端领域应用非常广泛。

概念

当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

UML 类图

观察者截图

代码演示

// src/demo05/observer.ts
class Subject {
  private state = 0;
  private observers: Observer[] = [];

  getState(): number {
    return this.state;
  }

  setState(newState: number) {
    this.state = newState;
    this.notify();
  }

  // 添加观察者
  attach(observer: Observer) {
    this.observers.push(observer);
  }

  // 通知
  private notify() {
    this.observers.forEach((observer) => {
      observer.update(this.state);
    });
  }
}

// 观察者
class Observer {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  update(state: number) {
    console.log(`${this.name} update, state is ${state}`);
  }
}

const sub = new Subject();
const observerA = new Observer('A');
sub.attach(observerA);
const observerB = new Observer('B');
sub.attach(observerB);
sub.setState(1);

export {};

场景

DOM 事件就是最常用的观察者模式

// demo05/dom.html
<buttonid="btn1">btn</button>
<script>
    const $btn1=$('#btn1')
    $btn1.click(function(){
      console.log(1)
    })
    $btn1.click(function(){
      console.log(2)
    })
    $btn1.click(function(){
      console.log(3)
    })
</script>

React、Vue 的生命周期

Vue 的生命周期

cn.vuejs.org/guide/essen…

Vue的生命周期

Vue watch

// Vue组件配置
{
    data(){
        name:'leslie'
    },
    watch:{
        name(newVal,val){
            console.log(newValue,val)
        }
    }
}

Vue 组件更新过程

React 组件更新过程不是这样的,它是通过 setState 主动触发的,而非响应式监听。

异步回调

定时器 setTimeout、setInterval

Promise then 回调

参考:src/demo2/promise.ts

nodejs stream

// src/demo05/stream.js
const fs = require('fs');

const readStream = fs.createReadStream('./data/file.txt'); // 读取文件

let length = 0;
readStream.on('data', function (chunk) {
  length += chunk.toString().length;
});
readStream.on('end', function () {
  console.log(length);
});

nodejs readline

// src/demo05/readline.js
const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
  input: fs.createReadStream('./data/file.txt'),
});

let lineNum = 0;
rl.on('line', function (line) {
  lineNum++;
});
rl.on('close', function () {
  console.log(lineNum);
});

nodejs http server 回调

// src/demo05/http.js
const http = require('http');

function serverCallback(req, res) {
  console.log('get 请求不处理', req.url);
  res.end('hello');
}

http.createServer(serverCallback).listen(8081);
console.log('监听 8081 端口...');

MutationObserver

src/demo05/MutationObserver.html

<div id="container">
  <p>A</p>
  <p>B</p>
</div>
function callback(records: MutationRecord[], observer: MutationObserver) {
  for (let record of records) {
    console.log('record: ', record);
  }
}
const observer = new MutationObserver(callback);

const containerElem = document.getElementById('container');
const options = {
  attributes: true, // 监听属性变化
  attributeOldValue: true, // 变化之后,记录旧属性的值
  childList: true, // 监听子节点变化(新增删除)
  characterData: true, // 监听节点内容或文本变化
  characterDataOldValue: true, // 变化之后,记录旧内容
  subtree: true, // 递归监听所有下级节点
};

// 开始监听
observer.observe(containerElem, options);
// 停止监听
observer.disconnect();

vs 发布订阅模式

它是观察者模式的另一个版本

event.on('event-key', () => {
  // 事件1
});
event.on('event-key', () => {
  // 事件2
});
// 触发执行
event.emit('event-key');

观察者模式 vs 发布/订阅模式

对比

观察者模式

  • Subject 和 Observer 直接绑定,中间无媒介
  • 如 addEventListener 绑定事件

发布/订阅模式

  • Publisher 和 Observer 互不交集,中间有媒介
  • 如 event 自定义事件

一个很明显的特点:发布订阅模式需要在代码里触发 emit,而观察者没有。

场景

自定义事件

  • Vue 2 实例本身就支持自定义时间,但 Vue3 不支持
  • Vue 3 推荐使用 mitt,轻量级 200 bytes
import mitt from 'mitt';

const emitter = mitt();

// listen to an event
emitter.on('foo', (e) => console.log('foo', e));

// listen to all events
emitter.on('*', (type, e) => console.log(type, e));

// fire an event
emitter.emit('foo', { a: 'b' });

// clearing all events
emitter.all.clear();
  • 但是,mitt 没有 once,可以借用 event-emitter

postMessage 通讯

通过 window.postMessage 发送消息。注意第二个参数,可以限制域名, 如发送敏感信息,要限制域名。

// 父页面向 iframe 发送消息
window.iframe1.contentWindow.postMessage('hi', '*');

// iframe 向父页面 发送消息
window.parent.postMessage('wo', '*');

// 可监听 message 来接收消息。可使用 event.origin 来判断消息来源是否合法,可选择不接受
window.addEventListener('message', (event) => {
  console.log('origin', event.origin); // 通过 origin 判断是否来源合法
  console.log('child received', event.data);
});

同类型的还有:

  • nodejs 多进程通讯
  • WebWorker 通讯
  • WebSocket 通讯

注意

在 Vue 和 React 组件中使用,在组件销毁之前,要及时 off(销毁)自定义事件。否则可能会导致内存泄漏。另,off 时要传入原来的函数,而不能是匿名函数。

设计原则验证

  • 主题和观察者分离,不是主动触发而是被动监听,二者解耦
  • 符合开放封闭原则

项目地址