观察者模式是前端运用场景最多的,在各大类库以及框架中都能看到它的身影.
一.特点:
- 发布&&订阅
- 一对N(一对一,一对多)
二.实现
- UML类图
Subject类内部保存了一个其订阅者的列表同时还有当前状态:可以通过setState方法改变内部的状态,在状态发生变更的同时执行notifyAllObservers方法,遍历取出每个订阅者,执行update方法.Subject类暴露一个attach方法给Observer类来允许其进行订阅 - 代码如下
//要被订阅的主题
class Subject {
constructor() {
this.state = 0
this.observers = []
}
getState() {
return this.state
}
setState(state) {
this.state = state
this.notifyAllObservers()
}
attach(observer) {
this.observers.push(observer)
}
notifyAllObservers() {
this.observers.forEach(observer=> {
observer.update()
})
}
}
//观察者
class Observer {
constructor(name,subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
update() {
console.log(`${this.name} is updata, subject state is ${this.subject.state}`)
}
}
const s1 = new Subject()
const o1 = new Observer('o1', s1)
s1.setState(1)
const o2 = new Observer('o2', s1)
const o3 = new Observer('o2', s1)
s1.setState(2)
调用结果如下
o1 is updata, subject state is 1
o1 is updata, subject state is 2
o2 is updata, subject state is 2
o2 is updata, subject state is 2
三.使用场景介绍
- jquery callback
- promise
- node.js自定义事件(stream,http请求处理,多进程通讯)
- vue中的wacher
- vue和react中的生命周期钩子函数
1.jquery callback
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>jQuery callbacks</p>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var callbacks = $.Callbacks() // 注意大小写
callbacks.add(function (info) {
console.log('fn1', info)
})
callbacks.add(function (info) {
console.log('fn2', info)
})
callbacks.add(function (info) {
console.log('fn3', info)
})
callbacks.fire('gogogo')
callbacks.fire('fire')
</script>
</body>
</html>
结果如下
fn1 gogogo
fn2 gogogo
fn3 gogogo
fn1 fire
fn2 fire
fn3 fire
2. promise
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
//then方法调用
promise.then(function(value) {
// success
}, function(error) {
// failure
});
可以猜到的是,这里的then方法相当于attach,对消息进行了订阅,而我们的resolve和reject方法相当于notifyAllObservers,当结果返回促使promise的state发生之后遍历执行订阅的队列,调用update方法
3.node.js中的自定义事件
node.js中有一个底层的Events模块,被其他的模块大量使用,可以说是node非常核心的一个模块了.其本质还是一个观察者模式
const eventEmitter = require('events').EventEmitter
const emitter1 = new eventEmitter()
emitter1.on('some', info=> {
console.log('fn1', info)
})
emitter1.on('some', info=> {
console.log('fn2', info)
})
emitter1.emit('some', 'xxxx')
输入结果如下
fn1 xxxx
fn2 xxxx
EventEmitter模块能被其他类继承,比如我们可以声明一个Dog类来继承EventEmitter类
class Dog extends EventEmitter {
constructor(name) {
super()
this.name = name
}
}
var simon = new Dog('simon')
simon.on('bark', function () {
console.log(this.name, ' barked')
})
setInterval(() => {
simon.emit('bark')
}, 500)
node.js中实现的stream数据结构继承了EventEmitter类,在读写文件的时候以流的形式进行文件的读写,on(‘data’),on(‘end’)等就是一种事件的订阅
var fs = require('fs')
var readStream = fs.createReadStream('./data/file1.txt') // 读取文件的 Stream
var length = 0
readStream.on('data', function (chunk) {
length += chunk.toString().length
})
readStream.on('end', function () {
console.log(length)
})
在http请求中也是如此
var http = require('http')
function serverCallback(req, res) {
var method = req.method.toLowerCase() // 获取请求的方法
if (method === 'get') {
}
if (method === 'post') {
// 接收 post 请求的内容
var data = ''
req.on('data', function (chunk) {
// “一点一点”接收内容
console.log('chunk', chunk.toString())
data += chunk.toString()
})
req.on('end', function () {
// 接收完毕,将内容输出
console.log('end')
res.writeHead(200, {'Content-type': 'text/html'})
res.write(data)
res.end()
})
}
}
http.createServer(serverCallback).listen(8081)
console.log('监听 8081 端口……')
vue中的watch函数
var vm = new Vue({
el: '#app',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar',
}
watch: {
firstName: function(val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function(val) {
this.fullName = this.firstName + ' ' + val
}
}
})
还有vue与react中的生命周期钩子,下面是vue源码,在new Vue的时候使用callHook方法执行我们定义的方法
我们来看看callHook,可以看到的是每个勾子名后面对应的都是一个handlers列表,执行callHook的时候遍历这个列表执行
function callHook (vm, hook) {
pushTarget();
var handlers = vm.$options[hook]; //取得handlers列表
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm); //遍历执行
} catch (e) {
handleError(e, vm, (hook + " hook"));
}
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
popTarget();
}