Skip to content

代理模式

跟踪属性访问

通过捕获getsethas 等操作,可以知道对象属性在何时何处被访问。

ts

const user = {
  name: "Jack",
};

const traceAttributeProxy = new Proxy(user, {
  get(target, prop, receiver) {
    console.log(`getting ${String(prop)}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`setting ${String(prop)} to ${value}`);
    return Reflect.set(target, prop, value, receiver);
  },
});

traceAttributeProxy.name; // getting name
traceAttributeProxy.name = "John"; // setting name to John

隐藏属性

代理的内部实现对外部实现不可见,因此可以隐藏对象的属性。

ts
const hiddenProperties = ["id", "password"];
const targetObj = {
  id: 1,
  name: "John",
  password: "password",
};

const hiddenAttributeProxy = new Proxy(targetObj, {
  get(target, prop, receiver) {
    if (hiddenProperties.includes(String(prop))) {
      return undefined;
    } else return Reflect.get(target, prop, receiver);
  },
  has(target, prop) {
    if (hiddenProperties.includes(String(prop))) {
      return false;
    } else return Reflect.has(target, prop);
  },
});

console.log(hiddenAttributeProxy.id); // undefined
console.log(hiddenAttributeProxy.password); // undefined
console.log(hiddenAttributeProxy.name); // John

console.log("id" in hiddenAttributeProxy); // false
console.log("password" in hiddenAttributeProxy); // false
console.log("name" in hiddenAttributeProxy); // true

属性验证

所有的赋值操作都会触发 set 捕获器,因此可以根据情况决定允许赋值或拒绝赋值。

ts
const target: { [key: string]: any } = {
  onlyNumber: 0,
};

const validateAttributeProxy = new Proxy(target, {
  set(target, prop, value, receiver) {
    if (typeof value !== "number") {
      throw new Error(`Error: ${String(prop)} must be a number`);
    } else return Reflect.set(target, prop, value, receiver);
  },
});

validateAttributeProxy.onlyNumber = 1;
console.log(validateAttributeProxy.onlyNumber); // 1

try {
  validateAttributeProxy.onlyNumber = "2";
} catch (e: any) {
  console.error(e.message); // Error: onlyNumber must be a number
}
console.log(validateAttributeProxy.onlyNumber); // 1

函数参数验证

ts
function median(...nums: any[]) {
  return nums.sort()[Math.floor(nums.length / 2)];
}

const validateFunctionParameterProxy = new Proxy(median, {
  apply(target, thisArg, argArray) {
    for (const arg of argArray) {
      if (typeof arg !== "number") {
        throw new Error(`Error: ${arg} must be a number`);
      }
    }
    return Reflect.apply(target, thisArg, argArray);
  },
});

console.log(validateFunctionParameterProxy(4, 1, 7)); // 4
try {
  validateFunctionParameterProxy(4, 1, "7");
} catch (e: any) {
  console.error(e.message); // Error: 7 must be a number
}

构造函数参数验证

ts
class User {
  private _id?: number;
  constructor(id?: number) {
    this._id = id;
  }
}

const validateConstructorParameterProxy = new Proxy(User, {
  construct(target, argArray, newTarget) {
    if (argArray[0] === undefined) {
      throw new Error("Error: id is required");
    } else return Reflect.construct(target, argArray, newTarget);
  },
});

new validateConstructorParameterProxy(1);

try {
  new validateConstructorParameterProxy();
} catch (e: any) {
  console.error(e.message); // Error: id is required
}

数据绑定

ts
const studentList: Student[] = [];

class Student {
  private _name: string;
  constructor(name: string) {
    this._name = name;
  }
}

const dataBindingProxy = new Proxy(Student, {
  construct(target, argArray, newTarget) {
    const student = Reflect.construct(target, argArray, newTarget);
    studentList.push(student);
    return student;
  },
});

new dataBindingProxy("John");
new dataBindingProxy("Jane");
new dataBindingProxy("Jack");

console.log(studentList);
/**
[
  Student { _name: 'John' },
  Student { _name: 'Jane' },
  Student { _name: 'Jack' }
]
 */

可观察对象

ts
const productList: string[] = [];

function emit(product: string) {
  console.log(`Product ${product} added`);
}

const observableObjectProxy = new Proxy(productList, {
  set(target, prop, value, receiver) {
    const res = Reflect.set(target, prop, value, receiver);
    if (res && prop !== "length") {
      emit(Reflect.get(target, prop, receiver));
    }
    return res;
  },
});

observableObjectProxy.push("Apple"); // Product Apple added
observableObjectProxy.push("Banana"); // Product Banana added