Proxy

MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

1
const p = new Proxy(target, handler)
  • target:要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数、甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p的行为。

方法

1
Proxy.revocable()

创建一个可撤销的Proxy对象。

handler对象的方法

handler对象是一个容纳一批特定属性的占位符对象。它包含有Proxy的各个捕获器(trap)。

所有的捕获器是可选的。如果没有定义某个捕获器,那么就会保留源对象的默认行为。

getPrototypeOf()

handler.getPrototypeOf() 是一个代理(Proxy)方法,当读取代理对象的原型时,该方法就会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const monster1 = {
eyeCount: 4
};

const monsterPrototype = {
eyeCount: 2
};

const handler = {
getPrototypeOf(target) {
return monsterPrototype;
}
};

const proxy1 = new Proxy(monster1, handler);

console.log(Object.getPrototypeOf(proxy1) === monsterPrototype);
// Expected output: true

console.log(Object.getPrototypeOf(proxy1).eyeCount);
// Expected output: 2

setPrototypeOf()

handler.setPrototypeOf() 方法主要用来拦截 Object.setPrototypeOf().

1
2
3
4
const p = new Proxy(target, {
setPrototypeOf: function(target, prototype) {
}
});

isExtensible()

handler.isExtensible() 方法用于拦截对对象的 Object.isExtensible()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const monster1 = {
canEvolve: true
};

const handler1 = {
isExtensible(target) {
return Reflect.isExtensible(target);
},
preventExtensions(target) {
target.canEvolve = false;
return Reflect.preventExtensions(target);
}
};

const proxy1 = new Proxy(monster1, handler1);

console.log(Object.isExtensible(proxy1)); // true

console.log(monster1.canEvolve); // true

Object.preventExtensions(proxy1); // true

console.log(Object.isExtensible(proxy1)); // true

console.log(monster1.canEvolve); // true

preventExtensions()

handler.preventExtensions() 方法用于设置对Object.preventExtensions()的拦截。

Object.preventExtensions()方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。

1
2
3
4
var p = new Proxy(target, {
preventExtensions: function(target) {
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const monster1 = {
canEvolve: true
};

const handler1 = {
preventExtensions(target) {
target.canEvolve = false;
Object.preventExtensions(target);
return true;
}
};

const proxy1 = new Proxy(monster1, handler1);

console.log(monster1.canEvolve); // true

Object.preventExtensions(proxy1);

console.log(monster1.canEvolve); // fasle

getOwnPropertyDescriptor()

handler.getOwnPropertyDescriptor() 方法是 Object.getOwnPropertyDescriptor() 的钩子。

1
2
3
4
var p = new Proxy(target, {
getOwnPropertyDescriptor: function(target, prop) {
}
});
1
2
3
4
5
6
7
8
9
10
const p = new Proxy({ a: 20 }, {
getOwnPropertyDescriptor(target, prop) {
console.log('called: ' + prop)
return { configurable: true, enumerable: true, value: 10 }
}
})

console.log(Object.getOwnPropertyDescriptor(p, 'a').value)
// called: a
// 10

defineProperty()

handler.defineProperty() 用于拦截对象的 Object.defineProperty() 操作。

vue2的双向绑定就是通过 Object.defineProperty() 实现的。

1
2
3
4
var p = new Proxy(target, {
defineProperty: function(target, property, descriptor) {
}
});

has()

handler.has() 方法是针对 in 操作符的代理方法。

示例,_开头的属性为私有属性,使用in判断的时候返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const handler1 = {
has(target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};

const monster1 = {
_secret: 'easily scared',
eyeCount: 4
};

const proxy1 = new Proxy(monster1, handler1);

console.log('eyeCount' in proxy1); // true

console.log('_secret' in proxy1); // false

console.log('_secret' in monster1); // true

get()

handler.get() 方法用于拦截对象的读取属性操作。

1
2
3
4
var p = new Proxy(target, {
get: function(target, property, receiver) {
}
});
1
2
3
4
5
6
7
const p = new Proxy({ a: 10 }, {
get(target, prop, receiver) {
return target[prop] * 2
}
})

console.log(p.a) // 20

set()

handler.set() 方法是设置属性值操作的捕获器。

1
2
3
4
const p = new Proxy(target, {
set: function(target, property, value, receiver) {
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const monster1 = { eyeCount: 4 };

const handler1 = {
set(obj, prop, value) {
if ((prop === 'eyeCount') && ((value % 2) !== 0)) {
console.log('必须是偶数');
} else {
return Reflect.set(...arguments);
}
}
};

const proxy1 = new Proxy(monster1, handler1);

proxy1.eyeCount = 1; // 必须是偶数

console.log(proxy1.eyeCount); // 4

proxy1.eyeCount = 2;
console.log(proxy1.eyeCount); // 2

deleteProperty()

handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作。

1
2
3
4
var p = new Proxy(target, {
deleteProperty: function(target, property) {
}
});

ownKeys()

handler.ownKeys() 方法用于拦截 Reflect.ownKeys().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const monster1 = {
_age: 111,
[Symbol('secret')]: 'I am scared!',
eyeCount: 4
}

const proxy1 = new Proxy(monster1, {
ownKeys(target) {
return Reflect.ownKeys(target)
}
})

for (const key of Object.keys(proxy1)) {
console.log(key)
// Expected output: "_age"
// Expected output: "eyeCount"
}

apply()

handler.apply() 方法用于拦截函数的调用。

1
2
3
4
5
6
7
8
9
10
const sum = (a, b) => a + b
const handler = {
apply: (target, _, args) => {
return target(...args) * 10
},

}
const proxy = new Proxy(sum, handler)
console.log(sum(1, 2)) // 3
console.log(proxy(1, 2)) // 30

construct()

handler.construct() 方法用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target 必须是有效的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
function sum(a, b) {
return { a, b }
}
const handler = {
construct: (target, args) => {
for (let index = 0; index < args.length; index++) {
args[index] += 1
}
return new target(...args)
}
}
const proxy = new Proxy(sum, handler)
console.log(new proxy(1, 2)) // {a: 2, b: 3}

上例中,sum不能是只能是普通函数,不能是箭头函数,因为箭头函数不能new。