Proxy Pattern

前言

大家好,我是 Johnny,今天要紀錄分享的是 Patterns 筆記系列的 Proxy Pattern

介紹

所謂 Proxy 即是代理,而 Proxy Pattern 討論到對物件進行代理,我們不直接對目標進行操作,而是透過代理間接來操作目標,而透過代理可以讓我們對於物件的操作能力進一步提升,比如常見基本的取值、賦值動作等

假設我們有一個物件

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

在 javascript 中透過 Proxy 操作,可以把目標物件作為目標後,產生一個代理物件

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};
// 給定一個目標,產生出一個代理物件
const personProxy = new Proxy(person, {});

其中 Proxy 類的第二個參數代表 handler,我們可以在其中去定義「對物件的特定操作」進行類似監聽的行為,雖然可以在 handler 內定義的動作類別非常多,最常用的還是 get, set 這兩個,其中 get 在我們對物件取值時觸發,set 則是賦值時觸發,下面是一個基本的範例

const personProxy = new Proxy(person, {
  get(obj, prop) {
    console.log(`屬性 ${prop} 的值是 ${obj[prop]}`);
  },
  set(obj, prop, value) {
    console.log(`屬性 ${prop}${obj[prop]} 變更為 ${value}`);
    obj[prop] = value;
    return true;
  },
});

const name = personProxy.name; // 屬性 name 的值是 John Doe
personProxy.age = 43; // 屬性 age 從 42 變更為 43
console.log(name); // undefined

常見用途

Validation 驗證

一個常見的 Proxy 用途是對於物件中某些值的驗證,比如下面這樣

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log("Oops.. 該屬性似乎不存在於此物件中");
    } else {
      console.log(`屬性 ${prop} 的值是 ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`屬性 age 必須是 number`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`請提供一個合法的名子`);
    } else {
      console.log(`屬性 ${prop}${obj[prop]} 變更為 ${value}.`);
      obj[prop] = value;
    }
  }
});

Reflect

Javascript 中提供了一個原生物件 Reflect,透過他我們可以更方便的對物件進行 Proxy 操作,在過去我們必須自己對 obj 進行系列操作,而 Reflect 則提供了與 Proxy 中我們使用的方法相同的方法名稱,比如 Reflect.get(), Reflect.set(),並且接收相同的 Props,如此我們就不用自己對原來的一些物件操作進行手動處理,透過 Reflect 可以快速做到我們對原本物件的操作行為

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`屬性 ${prop} 的值是 ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`屬性 ${prop}${obj[prop]} 變更為 ${value}`);
    Reflect.set(obj, prop, value);
  }
});

Vue Proxy Reactive

相信前端工程師都知道 Vue 框架,而 Vue3.x 實際上也使用到了 Proxy Pattern 在核心的響應式模組當中,當我們在對 vue 的響應式物件取值、賦值時,實際是對代理的物件進行操作,而 Vue 則替我們在 Proxy 物件中定義一系列的魔術操作,簡化後可以簡單表現如下:(詳細可以參考我的 Vue 源碼解析相關文章open in new window

const original = {
  msg: 'Good morning',
};

const deps = {};

function appendDep() {}
function triggerDep() {}

function reactive(target) {
  return new Proxy(target, {
    get: (obj, prop) => {
      // 添加對象依賴
      appendDep(obj, prop);
      return Reflect.get(obj, prop);
    },
    set: (obj, prop, value) => {
      Reflect.set(obj, prop);
      // 觸發依賴更新
      triggerDep(obj, prop);
      return true;
    },
  })
}

結論

Proxy Pattern 是一個很強大的模式,除了可以用在檢驗值以外,甚至格式、通知、除錯都有相應的應用場景,但同樣的,能力越強責任越大,雖然他很強大,但過度使用呼叫 Proxy handler 也很容易造成應用程式的效能問題,使用上需要謹慎使用,避免過度添加 handler 導致程式崩潰

感謝收看,下一篇見拉~

Last Updated:
Contributors: johnnywang