觀察者模式是前端運用場景最多的,在各大類庫以及框架中都能看到它的身影.
一.特點:
- 發佈&&訂閱
- 一對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();
}