成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

速通 JavaScript 代理模式和發布訂閱模式

開發 前端
發布訂閱模式的優點非常明顯,一為時間上的解耦,二為對象之間的解耦,但如果過度使用的話,對象和對象之間的必要聯系也將被深埋在背后,會導致程序難以跟蹤維護和理解。

1. 前言

JavaScript 是一門動態語言,在實現設計模式的時候,往往會比 Java 等靜態語言更簡便,本文將介紹在 JavaScript 中如何實現代理模式和發布訂閱模式。

2. 代理模式

2.1. 定義

在介紹定義時還是以類圖為主,雖然 JavaScript 實現設計模式時可能不會使用到類,但是類圖提供了一種通用的設計模式實現思想。

代理模式定義:為其他對象提供一種代理以控制對這個對象的訪問。

其類圖如下:

圖片圖片

類圖中的三個角色:

  • Subject 抽象主題角色:定義了具體主題和代理主題的共同接口,這樣在任何使用具體主題的地方都可以使用代理主題。
  • RealSubject 具體主題角色:邏輯的具體執行者。
  • Proxy 代理主題角色:實現了抽象主題接口,并持有對具體主題的引用。

2.2. 實現

在 JavaScript 中,你可以使用 Proxy 輕松實現代理模式,比如可以通過代理模式實現一個只接收 number 類型值的數組。

const arr = []
const numArr = new Proxy(arr, {
  set(target, key, value, proxy) {
    if (typeof value !== 'number') {
      throw Error("屬性只能是 number 類型");
    }
    return Reflect.set(target, key, value, proxy);
  }
})
numArr.push(0)
numArr.push('1') // Uncaught Error: 屬性只能是 number 類型
console.log(numArr) // Proxy(Array) {0: 0}

利用 Proxy,你還可以實現響應式編程。

const data = { userName: '' }

const render = (info) => {
  console.log(info)
  // 根據數據渲染界面
}

const proxyData = new Proxy(data, {
  set(target, key, value, receiver) {
    // 設置值
    Reflect.set(target, key, value, receiver)
    // 重新觸發渲染
    render(target)
  }
})

data.userName = 'xiaoming'// 控制臺輸出 { userName: 'xiaoming' }

當然你也可以利用 Proxy 來實現日志功能,用于跟蹤函數調用情況。

function add(a, b) {
  return a + b;
}

// 日志記錄函數
function log(message) {
  console.log(message);
}

// 創建代理對象
const proxy = newProxy(add, {
// 攔截函數調用
  apply(target, thisArg, args) {
    const result = Reflect.apply(target, thisArg, args);
    log(`函數 ${target.name} 被調用,參數: [${args.join(', ')}],返回值: ${result}`);
    return result;
  }
});

const sum1 = proxy(1, 2);  // 輸出: 函數 add 被調用,參數: [1, 2],返回值: 3
const sum2 = proxy(3, 4);  // 輸出: 函數 add 被調用,參數: [3, 4],返回值: 7

2.3. 小結

最后提一下代理模式和裝飾模式的異同點,兩者的共同點是代理類或裝飾類和原本類都具有相同的接口,不同點則是代理模式著重對代理過程的控制,而裝飾模式則是對類的功能進行加強或減弱。

3. 發布訂閱模式

3.1. 定義

發布訂閱模式的定義:定義對象間一種一對多的依賴關系,使得每當一個對象改變狀態,則所有依賴于它的對象都會得到通知并被自動更新。

其類圖如下:

圖片圖片

發布訂閱模式經常會和觀察者模式做對比,兩個設計模式廣義上設計理念是一致的,在實現上有些差別,本文更注重實際應用,故不展開此內容,借用一張圖來說明。

圖片圖片

3.2. 實現

接著來完成發布訂閱模式的簡單實現,主要是實現 subscribe 和 publish 方法。

const event = { 
  listeners: [],  // 所有訂閱者集合
  // 訂閱函數
  subscribe: function(fn) { 
    this.listeners.push(fn)
  },
  // 發布函數
  publish: function() {
    for(let i = 0; i < this.listeners.length; i++) { 
      this.listeners[i]()
    } 
  },
  // 移除訂閱函數
  unsubcribe: function(fn) { 
    const fns = this.listeners;
    // 倒序訪問方便使用 splice 移除訂閱函數
    for (let l = fns.length - 1; l >=0; l--) {
      const _fn = fns[l]; 
      if (_fn === fn){ 
        fns.splice(l, 1);
      } 
    } 
  }
}

const fn1 = () => { console.log('trigger1') }
const fn2 = () => { console.log('trigger2') }
event.subscribe(fn1)
event.subscribe(fn2)
event.publish() // 控制臺打印 trigger1, trigger2
event.unsubcribe(fn1)
event.publish() // 控制臺打印 trigger2

到此我們實現了一個簡單版本的發布訂閱。

接下來我們基于發布訂閱模式,在 React 中實現一個類似 Zustand 的狀態管理功能。

首先我們需要了解一個 React 官方 Hook useSyncExternalStore,這個 Hook 可以讓你訂閱一個外部數據源,當其中數據發生變化時,React 會觸發重新渲染。

const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)

可以看到該 Hook 參數有三個,這里我們主要關注前兩個,第一個參數即訂閱函數,第二個參數為獲取數據源的函數,第三個和服務端渲染相關。

接下來我們要結合發布訂閱模式和 useSyncExternalStore 實現一個簡單版本的 Zustand。

const createImpl = (createState) => {
  // 相比發布訂閱模式,多了個狀態值
  let state
  let initialState
  const listeners = newSet()

  // 類似發布訂閱模式中的 publish 方法,最終會觸發訂閱者
  const setState = (nextState) => {    
    // 對比狀態值是否有變化
    if (!Object.is(nextState, state)) {
      const previousState = state
      state = Object.assign({}, state, nextState)
      // 觸發訂閱函數
      listeners.forEach((listener) => listener(state, previousState))
    }
  }

  const getState = () => state

  const getInitialState = () => initialState

  const subscribe = (listener) => {
    listeners.add(listener)
    // 返回一個取消訂閱的方法
    return() => {
      listeners.delete(listener)
    }
  }

  // 清空訂閱
  const destory = () => listeners.clear()

  const api = {
    setState,
    getState,
    getInitialState,
    subscribe,
    destory
  }

  // 調用 createState 方法返回初始狀態值,createState 參數為 set、get 和 api 對象
  initialState = (state = createState(setState, getState, api))

  return api
}

const create = (createState) => {
  const api = createImpl(createState)
  // 傳入訂閱方法和獲取數據方法到 useSyncExternalStore
  const useStore = () => useSyncExternalStore(api.subscribe, api.getState)
  // 把 api 合并到 Hook 上
  Object.assign(useStore, api)
  return useStore
}

exportdefault create

先來看下 createImpl 函數,相比于我們實現的簡單版發布訂閱模式,createImpl 內部多維護了一個狀態值,在調用發布方法(setState)時,會更新狀態值,并觸發訂閱函數,訂閱函數入參為新舊狀態值。

最后我們看下如何使用自己的狀態管理功能。

// create 方法接收一個函數參數,內部會調用函數初始化狀態值,最終返回一個 Hook
const useStore = create((set) => ({
  num: 1,
  // 通過 set 方法更新狀態值,更新后觸發所有訂閱函數的調用
  random: () =>set({ num: Math.round(Math.random() * 1000) }),
}))

function Counter() {
  // 調用 useStore,useStore 會調用 React useSyncExternalStore
  const { num, random } = useStore();
  return (
    <div>
      <p>{`Number: ${num}`}</p>
      <button onClick={random}>Random</button>
    </div>
  )
}

create 方法接收一個函數參數,用于初始化狀態,最終 create 會返回一個 Hook。在狀態值中, random 方法會調用發布方法(setState)觸發更新,因為 useSyncExternalStore 會使用第一個參數完成訂閱動作,所以此時它能接收到數據更新,隨后便返回最新的狀態值,并觸發重新渲染。

在線代碼示例:https://stackblitz.com/edit/react-9nvjhwhx?file=demo.tsx

至此我們實現了一個簡單版本 Zustand。

3.3. 小結

發布訂閱模式的優點非常明顯,一為時間上的解耦,二為對象之間的解耦,但如果過度使用的話,對象和對象之間的必要聯系也將被深埋在背后,會導致程序難以跟蹤維護和理解。

3. 總結

設計模式大體思想是要把系統中不變和變化的部分分開,封裝不變的部分,根據業務靈活替換變化的部分,這樣就可以保證系統的健壯性和可拓展性。同時在實現設計模式的同時,你通常也會很好的遵守了設計模式原則,如單一職責、依賴倒置、開閉原則、迪米特原則等。

責任編輯:武曉燕 來源: 栗子前端
相關推薦

2012-02-29 09:41:14

JavaScript

2023-11-10 09:22:06

2022-06-27 13:56:10

設計模式緩存分布式系統

2022-12-02 07:28:58

Event訂閱模式Spring

2009-11-05 10:07:37

WCF設計模式

2021-09-08 07:18:30

代理模式對象

2012-01-13 15:59:07

2021-06-29 08:54:23

設計模式代理模式遠程代理

2010-03-25 08:52:30

PHP設計模式代理模式

2011-04-06 11:41:25

Java動態代理

2024-07-29 08:34:18

C++訂閱者模式線程

2021-08-02 17:21:08

設計模式訂閱

2024-02-26 11:52:38

代理模式設計

2022-11-30 17:05:33

代碼程序場景

2024-04-10 12:27:43

Python設計模式開發

2015-09-08 13:39:10

JavaScript設計模式

2023-12-04 08:24:23

2022-09-07 08:25:08

代理模式設計模式代碼

2023-11-02 21:11:11

JavaScript設計模式

2011-03-23 10:40:51

java代理模式
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 九九热精品视频 | av中文字幕网站 | 在线a视频网站 | 北条麻妃视频在线观看 | 中文字幕国产高清 | 久久久夜色精品亚洲 | 国产视频久久 | 91视频三区 | 97国产精品 | 在线免费观看毛片 | av电影一区 | 精品乱码一区二区 | 神马影院一区二区三区 | 91精品国产99久久 | 亚洲欧美日韩精品久久亚洲区 | 亚洲激情一级片 | 老子午夜影院 | 久久精品99久久 | 亚洲国产一区二区三区 | 亚洲成人av一区二区 | 国产精品久久久久一区二区三区 | 亚洲国产精品人人爽夜夜爽 | 综合二区| 青春草国产 | 成人精品视频 | 夜夜爽99久久国产综合精品女不卡 | 在线观看视频一区二区三区 | 久久99精品久久久久久琪琪 | 久久蜜桃资源一区二区老牛 | 亚洲人成网站777色婷婷 | 色爽女| jizz在线免费观看 | 91精品国产色综合久久 | 亚洲国产成人精品女人久久久 | 精品日韩一区二区 | 日韩免费网站 | 国产成人99久久亚洲综合精品 | 日本午夜免费福利视频 | 国产片淫级awww | 日韩中文字幕在线视频观看 | 精品国产1区2区3区 一区二区手机在线 |