Proxy Pattern trong Javascript
Tags: javascript
Published: 2022-07-30 17:38:14

Với một Proxy object, chúng ta có quyền kiểm soát các tương tác với một object nhất định, ví dụ: getting một value hoặc setting một value.

Hãy tạo một object `person`, đại diện là John Doe.
```
const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};
```
Thay vì tương tác trực tiếp với object này, tôi muốn tương tác với một `proxy object`. Trong javascript, tôi có thể tạo một proxy bằng cách sử dụng từ khóa `new Proxy`
```
const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {});
```
Argument thứ 2 của proxy là một object đại diện được gọi là `handler`. Trong handler object, tôi có thể xác định các hành vi cụ thể dựa trên loại tương tác. Mặc dù có nhiều methods mà tôi có thể thêm vào Proxy handler, 2 method phổ biến nhất là `get` và `set`:
- `get`: được gọi khi cố gắng access một property
- `set` được gọi khi muốn modify một property
Quy trình sẽ xảy ra như xau:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qa83j4abxlrkm66ci1x4.png)
Thay vì tương tác trực tiếp với object person, tôi sẽ tương tác với `personProxy`.
Hãy thêm các handlers vào proxyHandler. Khi muốn modify một property, do đó gọi method `set`  trên proxy, tôi muốn proxy in value cũ và value mới, sau đó thực hiện ghi đè value của property.
Khi cố gắng access một property, hãy gọi method `get` trên Proxy, tôi muốn proxy ghi lại một  contains chứa key và value của property.
```
const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
  }
});
```
Tuyệt hãy xem đoạn code dưới dây, điều gì sẽ xảy ra?
```
const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
    return true;
  }
});

personProxy.name; //The value of name is John Doe 
personProxy.age = 43; //Changed age from 42 to 43 
```
Khi access `name`, proxy trả về một câu: `The value of name is John Doe`
khi modify `age`, proxy in `Changed age from 42 to 43` 

Ngoài ra có thể bắt các validation của method proxy
```
const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(
        `Hmm.. this property doesn't seem to exist on the target object`
      );
    } else {
      console.log(`The value of ${prop} is ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`Sorry, you can only pass numeric values for age.`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`You need to provide a valid name.`);
    } else {
      console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
      obj[prop] = value;
    }
  }
});
```
Hãy xem đoạn code dưới dây, điều gì sẽ xảy ra?
```
const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(`Hmm.. this property doesn't seem to exist`);
    } else {
      console.log(`The value of ${prop} is ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`Sorry, you can only pass numeric values for age.`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`You need to provide a valid name.`);
    } else {
      console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
      obj[prop] = value;
    }
    return true;
  }
});

personProxy.nonExistentProperty; //Hmm.. this property doesn't seem to exist
personProxy.age = "44"; //Sorry, you can only pass numeric values for age. 
personProxy.name = ""; //You need to provide a valid name. 
```
Proxy made sure tôi không sủa đổi object person với các value không hợp lệ, điều này giúp tôi giữ cho dữ liệu của tôi tinh khiết(data pure)
**Reflect**
Js cung cấp một object gọi là `Reflect`, giúp tôi thao tác với đối tượng đích dễ dàng hơn khi làm việc với proxy.
Trước đây, tôi muốn modify và acces property trên một object bên trong proxy thông qua `get`, `set` bằng ky hiệu {}. Thay vào đó, tôi có thể sử dụng Reflect. Methods trên Reflect có cùng tên với các methods trên object đang xử lý.
Thay vì access property thông qua `obj[prop]`, tôi có thể access hoặc modify property thông qua `Reflect.get()` và `Reflect.set()`. Các methods nhận các arguments dưới dạng methods trên handle obejct.
```
const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    Reflect.set(obj, prop, value);
  }
});
```
Hãy xem đoạn code dưới dây:
```
const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    return Reflect.set(obj, prop, value);
  }
});

personProxy.name; //The value of name is John Doe 
personProxy.age = 43; //The value of name is John Doe 
personProxy.name = "Jane Doe"; //The value of name is John Doe 
```
Proxy là một cách hiệu quả để control các behavior của một object. Một proxy có thể có nhiều use-cases: nó giúp validation, formatting, notifications, hoặc debugging.
Việc lạm dụng proxy hoặc thực hiện các thao tác nặng trên mỗi handler method có thể ảnh hưởng đến performance của app. **Tốt nhất cũng không nên sử dụng proxy cho các đoạn code quan trọng về hiệu suất.**