【06】观察者模式

01-介绍

观察者模式是前端最常用的一个设计模式,也是 UI 编程最重要的思想。

示例

例如你在星巴克点了咖啡,此时你并不需要在吧台坐等,你只需要回到位子上玩手机,等咖啡好了服务员会叫你。不光叫你,其他人的咖啡好了,服务员也会叫他们来取。

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

<button id="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>

还有,Vue React 的生命周期,也是观察者模式

在这里插入图片描述

02-演示

Subject 和 Observer 是一对多的关系

在这里插入图片描述

// 主题
class Subject {
    private state: number = 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() {
        for (const observer of this.observers) {
            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 observer1 = new Observer('A')
sub.attach(observer1)
const observer2 = new Observer('B')
sub.attach(observer2)

sub.setState(1) // 更新状态,触发观察者 update

是否符合设计原则?

5 大设计原则中,最重要的就是:开放封闭原则,对扩展开放,对修改封闭

  • Observer 和 Target 分离,解耦
  • Observer 可自由扩展
  • Target 可自由扩展

03-场景

观察者模式在前端(包括其他 UI 编程领域)应用非常广泛。

DOM 事件

<button id="btn1">btn</button>

<script>
    const $btn1 = $('#btn1')
    $btn1.click(function () {
        console.log(1)
    })
    $btn1.click(function () {
        console.log(2)
    })
</script>

Vue React 组件生命周期

PS:当你开发自己的 lib 时,也要考虑它的完整生命周期,如 wangEditor,负责创建,也得复杂销毁。

在这里插入图片描述

Vue watch

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

PS:面试题 watch 和 watchEffect 有什么区别?—— 请看我的面试课程

Vue 组件更新过程

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

在这里插入图片描述

各种异步的回调

定时器

setTimeout setInterval

Promise then 回调

参考之前 loadImg 代码

nodejs stream
const fs = require('fs')
const readStream = fs.createReadStream('./data/file1.txt')  // 读取文件的 stream

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

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

let lineNum = 0
rl.on('line', function(line){
    lineNum++
})
rl.on('close', function() {
    console.log('lineNum', lineNum)
})
nodejs http server 回调
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

HTML 代码

<div id="container">
    <p>A</p>
    <p>B</p>
</div>

JS 代码

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()

总结

  • DOM 事件
  • 组件生命周期
  • Vue 组件更新过程
  • 异步回调
  • MutationObserver

注意,这里没有自定义事件,这个会在“发布订阅模式”讲解。

04-vs 发布订阅模式

发布订阅模式,没有在传统 23 种设计模式中,它是观察者模式的另一个版本。

// 绑定
event.on('event-key', () => {
    // 事件1
})
event.on('event-key', () => {
    // 事件2
})

// 触发执行
event.emit('event-key')

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

在这里插入图片描述

观察者模式

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

发布订阅模式

  • Publisher 和 Observer 相互不认识,中间有媒介
  • event 自定义事件

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

场景

自定义事件

Vue2 实例本身就支持自定义事件,但 Vue3 不再支持。

Vue3 推荐使用 mitt ,轻量级 200 bytes ,文档 https://github.com/developit/mitt

import mitt from 'mitt'

const emitter = mitt() // 工厂函数

emitter.on('change', () => {
    console.log('change1')
})
emitter.on('change', () => {
    console.log('change2')
})

emitter.emit('change')

但是,mitt 没有 once ,需要可以使用 event-emitter https://www.npmjs.com/package/event-emitter

import eventEmitter from 'event-emitter' // 还要安装 @types/event-emitter

const emitter = eventEmitter()

emitter.on('change', (value: string) => {
    console.log('change1', value)
})
emitter.on('change', (value: string) => {
    console.log('change2', value)
})
emitter.once('change', (value: string) => {
    console.log('change3', value)
})

emitter.emit('change', '张三')
emitter.emit('change', '李四')
postMessage 通讯

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

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

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

可监听 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 时要传入原来的函数,而不能是匿名函数

总结

  • 观察者模式 vs 发布订阅模式
  • 发布订阅模式的场景:自定义事件
  • 注意事项:及时 off

05-总结

内容回归

  • 概念介绍 + 解决的问题
  • UML 类图和代码演示
  • 场景
  • 观察者模式 vs 发布订阅模式

重要细节

  • Vue3 本身没有了自定义事件功能
  • 组件销毁时及时 off 事件,防止内存泄漏

注意事项

  • 观察者模式很重要,本章内容较多,耐心学习
  • 观察者模式场景很多,要抓住重点,不要拘泥细节

另,观察者模式和发布订阅模式,本课程学习时区分明显,但在实际工作中并不会严格区分(减少沟通成本)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值