PROXIES AND REFLECT
Proxy Fundamentals
Creating a Passthrough Proxy
语法格式
const target = {
id: "target",
};
const handler = {};
const proxy = new Proxy(target, handler);
Defining Traps
定义 Traps
const target = {
foo: "bar",
};
const handler = {
// Traps are keyed by method name inside the handler object
get() {
return "handler override";
},
};
const proxy = new Proxy(target, handler);
调用机制
- 每当 proxy 调用 get() 函数时,
- 会使用 handler 中的 get() 代替调用,
- 使用 target 调用 get() 函数时一切正常;
console.log(target.foo); // bar
console.log(proxy.foo); // handler override
console.log(target["foo"]); // bar
console.log(proxy["foo"]); // handler override
console.log(Object.create(target)["foo"]); // bar
console.log(Object.create(proxy)["foo"]); // handler override
作用
- 提供代码编写者改变几乎任何原生方法的能力;
Trap Parameters and the Reflect API
Reflect object
- target 上的同名原生方法定义在 Reflect object 上,
- 任何可以在 handle 中定义为 trap 的方法在 Reflect object 都有对应的 API,
- 具有相同的名字和参数;
const target = {
foo: "bar",
};
const handler = {
get: Reflect.get,
};
const proxy = new Proxy(target, handler);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
定义 passthrough proxy
const target = {
foo: "bar",
};
const proxy = new Proxy(target, Reflect);
console.log(proxy.foo); // bar
console.log(target.foo); // bar
Trap Invariants
Trap Invariants
- 约束 trap 的准则;
Revocable Proxies
Proxy.revocable() 方法
- Proxy.revocable(target, handler);
- 创建一个可销毁的 proxy;
- 返回值: object;
- proxy 属性: 等同于 new Proxy(target, handler) 创建的 proxy;
- revoke 属性: revoke() 函数, 用于销毁 proxy;
Utility of the Reflect AP
Reflect API vs. Object API
- reflect 对于 trap handler 没有限制;
Status Flags
- 一些 Reflect 方法返回 boolean,
- Reflect.defineProperty
- Reflect.preventExtensions
- Reflect.setPrototypeOf
- Reflect.set
- Reflect.deleteProperty
- 表示该方法是否执行成功,
- 在一些场景极其有用;
// Initial code
const o = {};
try {
Object.defineProperty(o, "foo", "bar");
console.log("success");
} catch (e) {
console.log("failure");
}
// Refactored code
const o = {};
if (Object.defineProperty(o, "foo", "bar")) {
console.log("success");
} else {
console.log("failure");
}
Supplanting Operators with First-Class Functions
Safe Function Application
Function.prototype.apply.call(myFunc, thisVal, argumentList);
Reflect.apply(myFunc, thisVal, argumentsList);
Proxying a Proxy
const target = {
foo: "bar",
};
const firstProxy = new Proxy(target, {
get() {
console.log("first proxy");
return Reflect.get(...arguments);
},
});
const secondProxy = new Proxy(firstProxy, {
get() {
console.log("second proxy");
return Reflect.get(...arguments);
},
});
console.log(secondProxy.foo);
// second proxy
// first proxy
// bar
Proxy Considerations and Shortcomings
' this' Inside a Proxy
Proxies and Internal Slots
Proxy Traps and Reflect Methods
get()
handler.get() 方法
- handler.get(target, property, receiver);
- 检索某个属性值;
- 返回值: 任何值;
Interceptions
- 略;
Trap invariants
- 略;
set()
handler.set() 方法
- handler.set(target, property, value, receiver);
- 设置某个属性值;
- 返回值;
- true: 设置成功;
- TypeError: 设置失败;
Interceptions
- 略;
Trap invariants
- 略;
has()
handler.has() 方法
- handler.has(target, prop);
- in 操作符的 trap 形式;
- 返回值: boolean;
Interceptions
- 略;
Trap invariants
- 略;
defineProperty()
handler.defineProperty() 方法
- handler.defineProperty(target, property, descriptor);
- Object.defineProperty() 的 trap 形式;
- 返回值: boolean;
Interceptions
- 略;
Trap invariants
- 略;
getOwnPropertyDescriptor()
handler.getOwnPropertyDescriptor() 方法
- handler.getOwnPropertyDescriptor(target, prop);
- Object.getOwnPropertyDescriptor() 的 trap 形式;
- 返回值: object/undefined;
Interceptions
- 略;
Trap invariants
- 略;
deleteProperty()
handler.deleteProperty() 方法
- handler.deleteProperty(target, property);
- delete 操作符的 trap 形式;
- 返回值: boolean;
Interceptions
- 略;
Trap invariants
- 略;
ownKeys()
handler.ownKeys() 方法
- handler.ownKeys(target);
- Reflect.ownKeys() 的 trap 形式 ;
- 返回值: enumerable object;
Interceptions
- 略;
Trap invariants
- 略;
getPrototypeOf()
handler.getPrototypeOf() 方法
- handler.getPrototypeOf(target);
- [[GetPrototypeOf]] 的 trap 形式;
- 返回值: object/null;
Interceptions
- 略;
Trap invariants
- 略;
setPrototypeOf()
handler.setPrototypeOf() 方法
- handler.setPrototypeOf(target, prototype);
- Object.setPrototypeOf() 的 trap 形式;
- 返回值: boolean;
Interceptions
- 略;
Trap invariants
- 略;
isExtensible()
handler.isExtensible() 方法
- handler.isExtensible(target);
- Object.isExtensible() 的 trap 形式;
- 返回值: boolean;
Interceptions
- 略;
Trap invariants
- 略;
preventExtensions()
handler.preventExtensions() 方法
- handler.set(target, property, value, receiver);
- Object.preventExtensions() 的 trap 形式;
- 返回值: boolean;
Interceptions
- 略;
Trap invariants
- 略;
apply()
handler.apply() 方法
- handler.apply(target, thisArg, argumentsList);
- function call 的 trap 形式;
- 返回值: 任何值;
Interceptions
- 略;
Trap invariants
- 略;
construct()
handler.construct() 方法
- handler.construct(target, argumentsList, newTarget);
- new 关键字的 trap 形式;
- 返回值: object;
Interceptions
- 略;
Trap invariants
- 略;
Proxy Patterns
Tracking Property Access
const user = {
name: "Jake",
};
const proxy = new Proxy(user, {
get(target, property, receiver) {
console.log("Getting ${property}");
return Reflect.get(...arguments);
},
set(target, property, value, receiver) {
console.log("Setting ${property}=${value}");
return Reflect.set(...arguments);
},
});
proxy.name; // Getting name
proxy.age = 27; // Setting age=27
Hidden Properties
const hiddenProperties = ["foo", "bar"];
const targetObject = {
foo: 1,
bar: 2,
baz: 3,
};
const proxy = new Proxy(targetObject, {
get(target, property) {
if (hiddenProperties.includes(property)) {
return undefined;
} else {
return Reflect.get(...arguments);
}
},
has(target, property) {
if (hiddenProperties.includes(property)) {
return false;
} else {
return Reflect.has(...arguments);
}
},
});
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log("foo" in proxy); // false
console.log("bar" in proxy); // false
console.log("baz" in proxy); // true
Property Validation
const target = {
onlyNumbersGoHere: 0,
};
const proxy = new Proxy(target, {
set(target, property, value) {
if (typeof value !== "Number") {
return false;
} else {
return Reflect.set(...arguments);
}
},
});
proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = "2";
console.log(proxy.onlyNumbersGoHere); // 1
Function and Constructor Parameter Validation
function median(...nums) {
return nums.sort()[Math.floor(nums.length / 2)];
}
const proxy = new Proxy(median, {
apply(target, thisArg, ...argumentsList) {
for (const arg of argumentsList) {
if (typeof arg !== "number") {
throw "Non-number argument provided";
}
}
return Reflect.apply(...arguments);
},
});
console.log(proxy(4, 7, 1)); // 4
console.log(proxy(4, "7", 1));
// Error: Non-number argument provided
class User {
constructor(id) {
this.id_ = id;
}
}
const proxy = new Proxy(User, {
construct(target, argumentsList, newTarget) {
if (argumentsList[0] === undefined) {
throw "User cannot be instantiated without id";
} else {
return Reflect.construct(...arguments);
}
},
});
new proxy(1);
new proxy();
// Error: User cannot be instantiated without id
Data Binding and Observables
const userList = [];
class User {
constructor(name) {
this.name_ = name;
}
}
const proxy = new Proxy(User, {
construct() {
const newUser = Reflect.construct(...arguments);
userList.push(newUser);
return newUser;
},
});
new proxy("John");
new proxy("Jacob");
new proxy("Jingleheimerschmidt");
console.log(userList); // [User {}, User {}, User{}]
const userList = [];
function emit(newValue) {
console.log(newValue);
}
const proxy = new Proxy(userList, {
set(target, property, value, receiver) {
const result = Reflect.set(...arguments);
if (result) {
emit(Reflect.get(target, property, receiver));
}
return result;
},
});
proxy.push("John");
// John
proxy.push("Jacob");
// Jacob