观察者模式
Posted 沿着路走到底
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了观察者模式相关的知识,希望对你有一定的参考价值。
概念
发布 & 订阅
一对多
示例
例如你在星巴克点了咖啡,此时你并不需要在吧台坐等,你只需要回到位子上玩手机,等咖啡好了服务员会叫你。不光叫你,其他人的咖啡好了,服务员也会叫他们来取。
还有,网页事件就是最常用的观察者模式
```html
<button id="btn1">btn</button>
<script>
$('#btn1').click(function ()
console.log(1)
)
$('#btn1').click(function ()
console.log(2)
)
$('#btn1').click(function ()
console.log(3)
)
</script>
```
另外,JS 异步,只要用到 callback 函数,都是观察者模式
```js
setTimeout(function ()
alert(100)
, 1000)
```
UML类图
代码演示
// 主题,接收状态变化,触发每个观察者
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 update, state: $this.subject.getState()`)
// 测试代码
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('o2', s)
let o3 = new Observer('o3', s)
s.setState(1)
s.setState(2)
s.setState(3)
使用场景
网页事件
<button id="btn1">btn</button>
<script>
$('#btn1').click(function ()
console.log(1)
)
$('#btn1').click(function ()
console.log(2)
)
$('#btn1').click(function ()
console.log(3)
)
</script>
Promise
一开始说到异步有 callback 的都是观察者模式,而 Promise 作为异步的解决方案,也避免不了要使用。
```js
function loadImg(src)
var promise = new Promise(function (resolve, reject)
var img = document.createElement('img')
img.onload = function ()
resolve(img)
img.onerror = function ()
reject('图片加载失败')
img.src = src
)
return promise
var src = 'https://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img)
console.log('width', img.width)
return img
).then(function (img)
console.log('height', img.height)
)
```
`resolve`和`reject`就相当于之前的`setState`,状态改变,其实这也是 Promise 的真实状态变化:pending -> fulfilled 或者 pending -> rejected 。两个`then`就是观察者,状态变化就会触发观察者`update` 。
jQuery callbacks
jQuery callbacks 是 jQuery 的内部底层功能,服务于对外的 API 如 ajax deferred 等。jQuery 这么通用的 lib 都有必要维护一个通用的观察者功能,可见观察者模式在 JS 中的应用之广泛
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')
nodejs 自定义事件
const EventEmitter = require('events').EventEmitter
const emitter1 = new EventEmitter()
emitter1.on('some', () =>
// 监听 some 事件
console.log('some event is occured 1')
)
emitter1.on('some', () =>
// 监听 some 事件
console.log('some event is occured 2')
)
// 触发 some 事件
emitter1.emit('some')
以上代码中,先引入 nodejs 提供的`EventEmitter`构造函数,然后初始化一个实例`emitter1`。实例通过`on`可监听事件,`emit`可以触发事件,事件名称可以自定义,如`some`。
自定义事件触发的时候还可传递参数,例如
const EventEmitter = require('events').EventEmitter
const emitter = new EventEmitter()
emitter.on('sbowName', name =>
console.log('event occured ', name)
)
emitter.emit('sbowName', 'zhangsan') // emit 时候可以传递参数过去
上文说到`EventEmitter`实例有`on`和`emit`接口,其实自定义 class 的实例也可以有,只不过需要继承`EventEmitter`。使用 ES6 的继承语法很容易实现
const EventEmitter = require('events').EventEmitter
// 任何构造函数都可以继承 EventEmitter 的方法 on emit
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)
和 jQuery callbacks 一样,自定义事件也是 nodejs 中底层、通用的功能,很多其他功能要继承`EventEmitter`以实现自定义事件功能,下文会讲到,也能看出观察者模式在 nodejs 中应用的广泛。
nodejs stream
stream 是 nodejs 的基础模块,就是把大数据(一次性读取内存放不开)的操作当做一个流,来一点一点的读取,直到读取完毕。
例如一个大文件,几百万行(一般是日志文件),想要得知它的字符长度,就需要用到 stream 。既然是一点一点的读取,那么每次读取一点就得知道读取的是什么,读取完毕也得得到通知,这就需要观察者模式。
// Stream 用到了自定义事件
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)
)
nodejs 还专门指定了 readline ,跟上述的模式一样,只不过是一行一行读取文件,例如要知道上述文件一共有多少行,可以使用
// readline 用到了自定义事件
var readline = require('readline');
var fs = require('fs')
var rl = readline.createInterface(
input: fs.createReadStream('./data/file1.txt')
);
var lineNum = 0
rl.on('line', function(line)
lineNum++
);
rl.on('close', function()
console.log('lineNum', lineNum)
);
nodejs 中:处理 http 请求;多进程通讯
```js
var http = require('http')
function serverCallback(req, res)
var method = req.method.toLowerCase() // 获取请求的方法
if (method === 'get')
console.log('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) // 注意端口别和其他 server 的冲突
console.log('监听 8081 端口……')
```
使用 curl 模拟一下即可
```shell
curl -H "Content-Type:application/json" -X POST -d '"user": "admin", "passwd":"12345678"' http://127.0.0.1:8081/
```
## nodejs 多进程通讯
```js
// parent.js
var cp = require('child_process')
var n = cp.fork('./sub.js')
n.on('message', function (m)
console.log('PARENT got message: ' + m)
)
n.send(hello: 'workd')
// sub.js
process.on('message', function (m)
console.log('CHILD got message: ' + m)
)
process.send(foo: 'bar')
```
**同理于浏览器端的 webworker**
Vue 和 React 组件生命周期触发
vue React 使用组件化,每个组件实例都有固定的生命周期,生命周期的意思就是在实例运行的某个特定的节点,执行你要做的操作,例如`created`生命周期,打印一句话
new Vue(
data:
a: 1
,
created: function ()
// `this` 指向 vm 实例
console.log('a is: ' + this.a)
)
这些生命周期的函数,其实也都是观察者,当组件实例运行到某个阶段时,就会触发这个观察者。这是 vue 源码中的某个片段
function callHook (vm, hook)
var handlers = vm.$options[hook]; // 获取生命周期的所有函数
if (handlers)
for (var i = 0, j = handlers.length; i < j; i++)
handlers[i].call(vm); // 遍历,挨个触发
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate'); // 触发 beforeCreate 生命周期
initInjections(vm);
initState(vm);
initProvide(vm);
callHook(vm, 'created'); // 触发 created 生命周期
1
以上是关于观察者模式的主要内容,如果未能解决你的问题,请参考以下文章