js設計模式:觀察者模式

觀察者模式是前端運用場景最多的,在各大類庫以及框架中都能看到它的身影.

一.特點:

  • 發佈&&訂閱
  • 一對N(一對一,一對多)

二.實現

  1. UML類圖
    在這裏插入圖片描述
    Subject類內部保存了一個其訂閱者的列表同時還有當前狀態:可以通過setState方法改變內部的狀態,在狀態發生變更的同時執行notifyAllObservers方法,遍歷取出每個訂閱者,執行update方法.Subject類暴露一個attach方法給Observer類來允許其進行訂閱
  2. 代碼如下
//要被訂閱的主題
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,對消息進行了訂閱,而我們的resolvereject方法相當於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();
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章