Proxy
Proxy 用于修改某些操作的默认行为,等同与在语言层面做出修改,属于一种 meta programming。
1 | let obj = new Proxy( |
ES6 提供 Proxy 构造函数,用于生成 Proxy 实例。
1 | let proxy = new Proxy(target, handler) |
将 Proxy 对象设置到 object.proxy 属性,从而可以在 object 对象上调用:
1 | let obj = { proxy: new Proxy(target, handler) } |
Proxy 实例也可以作为其他对象的原型对象:
1 | let proxy = new Proxy( |
同一个 Proxy 可以设置多个拦截属性:
1 | let handler = { |
Proxy 方法
- get(target, propKey, receiver)
拦截对象的属性读取,如 proxy.foo 和 proxy[‘foo’]。最后一个参数是可选的。get 方法可以继承:1
2
3
4
5
6
7
8
9
10
11
12
13
14let person = {
name: "张三",
}
let proxy = new Proxy(person, {
get: function (target, property) {
if (property in target) {
return target[property]
} else {
throw new ReferenceError('Property "' + property + '" does not exist.')
}
},
})
proxy.name //张三
proxy.age //ReferenceError使用 get 实现负数索引:1
2
3
4
5
6
7
8
9
10
11let proto = new Proxy(
{},
{
get(target, propertyKey, receiver) {
console.log("get " + propertyKey)
return target[propertyKey]
},
}
)
let obj = Object.create(proto)
obj.aaa //get aaa将 get 转为执行某个函数,实现属性的链式操作:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey)
if (index < 0) {
propKey = String(target.length + index)
}
return Reflect.get(target, propKey, receiver)
},
}
let target = []
target.push(...elements)
return new Proxy(target, handler)
}
let arr = createArray("a", "b", "c")
arr[-1] //'c'1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var pipe = (function () {
return function (value) {
var funcStack = [];
var oproxy = new Proxy({}, {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
return funcStack.reduce(function (val, fn) { //reduce 接受一个函数作为累加器,从左到右缩减,最终计算为一个值
return fn(val);
}, value);
}
funcStack.push(window[fnName]);
return oproxy;
}
});
return oproxy;
}
}());
var double = n => n _ 2;
var pow = n => n _ n;
var reverseInt = n => n.toString().split('').reverse().join('') | 0;
pipe(3).double.pow.reverseInt.get; //63 - set(target, propKey, value, receiver)
拦截对象的属性设置,如 proxy.foo = 1,返回一个布尔值。给对象设置内部属性:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let validator = {
set: function (obj, prop, value) {
if (prop === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("The age is not an integer")
}
if (value > 200) {
throw new RangeError("The age seems invalid")
}
}
//age < 200 直接保存
obj[prop] = value
},
}
let person = new Proxy({}, validator)
person.age = 100
person.age = 201 //RangeError
person.age = "tom" //TypeError1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var handler = {
get(target, key) {
invariant(key, "get")
return target[key]
},
set(target, key, value) {
invariant(key, "set")
target[key] = value
return true
},
}
function invariant(key, action) {
if (key[0] === "_") {
throw new Error(`Invalid attempt to ${action} private "${key}" property`)
}
}
var target = {}
var proxy = new Proxy(target, handler)
proxy._temp //Error
proxy._temp = 2 //Error - has(target, propKey)
拦截 propKey in proxy 的操作,返回一个布尔值。has 拦截对 for…in 循环不生效 - deleteProperty(target, propKey)
拦截 delete proxy[propKey]的操作,返回一个布尔值。如果这个方法抛出错误或者返回 false,当前属性就不能被 delete 删除。1
2
3
4
5
6
7
8
9
10
11
12
13
14var handler = {
deleteProperty(target, key) {
invariant(key, "delete")
return true
},
}
function invariant(key, action) {
if (key[0] === "_") {
throw new Error(`Invalid attempt to ${action} private "${key}" property`)
}
}
var target = { _temp: "test" }
var proxy = new Proxy(target, handler)
delete proxy._temp //Error - ownKeys(target)
拦截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回所有属性名,而
Object.keys()返回结果仅包括目标对象和自身的可遍历属性。 - getOwnPropertyDescriptor(target, propKey)
拦截 Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象或者 undefined。1
2
3
4
5
6
7
8
9
10
11
12var handler = {
getOwnPropertyDescriptor(target, key) {
if (key[0] === "_") {
return
}
return Object.getOwnPropertyDescriptor(target, key)
},
}
var target = { _foo: "foo", bar: "bar" }
var proxy = new Proxy(target, handler)
Object.getOwnPropertyDescriptor(proxy, "_foo") //undefined
Object.getOwnPropertyDescriptor(proxy, "bar") //{ value : 'bar', writable : true, enumerable : true, configurable : true } - defineProperty(target, propKey, propDesc)
拦截 Object.defineProperty:1
2
3
4
5
6
7
8
9var handler = {
defineProperty(target, key, descriptor) {
return false
},
}
var target = {}
var proxy = new Proxy(target, handler)
proxy.foo = "bar"
proxy.foo //undefined - preventExtensions(target)
拦截 Object.preventExtensions,必须返回布尔值,否则会强制转换为布尔值。 - getPrototypeOf(target)
拦截获取对象原型 - isExtensible(target)
拦截 Object.isExtensible 操作 - setPrototypeOf(target, proto)
拦截 Object.setPrototypeOf 方法 - apply(target, object, args)
拦截函数的调用、call 和 apply 操作,1
2
3
4
5
6
7
8
9
10var target = function () {
return "test"
}
var handler = {
apply: function () {
return "apply"
},
}
let p = new Proxy(target, handler)
p() //apply - construct(target, args)
拦截 new 命令,返回的必须是对象,否则会报错:1
2
3
4
5
6
7
8
9var p = new Proxy(function () {}, {
construct: function (target, args) {
console.log("called " + args.join(","))
return { value: args[0] * 10 }
},
})
new p(1).value
//called 1
//10
Proxy.revocable()
返回一个可取消的 Proxy 实例:
1 | let target = {} |
执行 revoke 函数后再访问 Proxy 实例,就会抛出一个错误。
this 问题
在 Proxy 代理下,目标对象内部的 this 关键字会指向 Proxy 代理:
1 | const _name = new WeakMap() |
此外,有些原生对象内部属性只有通过正确的 this 才能获取,所以 Proxy 也无法代理这些原生对象的属性
1 | const target = new Date() |
这时,this 绑定原始对象就可以解决
1 | const target = new Date("2018-8-31") |
观察者模式
Observer mode:函数自动观察对象,一旦数据有变化,函数就会自动执行。
1 | let hero = { |