在 Proxy 之前,JavaScript 中就提供过 Object.defineProperty,允许对对象的 getter/setter 进行拦截,在vue3之前,vue的双向绑定是通过defineProperty实现的,在vue3之后重构为proxy,那么,什么是proxy呢。
proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,属于一种“元编程”,即对编程语言进行编程。
- 元编程(英语:Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。这意味着可以编写出这样的程序:它能够
读取、生成、分析或者转换其它程序,甚至在运行时修改程序自身(反射)。
概述
proxy译为代理,可以理解成在目标对象之前架设一层拦截,将所有本该我们手动编写的程序交由代理来处理。
语法
- target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
- handler 一个通常以函数作为属性的对象,用来定制拦截行为
const proxy = new Proxy(target, handle)
举个栗子:
const origin = {}
const obj = new Proxy(origin, {
get: function (target, propKey, receiver) {
return '10'
}
});
obj.a // 10
obj.b // 10
origin.a // undefined
origin.b // undefined
这段代码中我们给一个空对象的get架设了一层代理,所有get操作都会直接返回我们定制的数字10,需要注意的是,代理只会对proxy对象生效,如上方的origin就没有任何效果
常用方法
| 标题 | 描述 |
|---|---|
| get() | 属性读取操作的捕捉器 |
| set() | 属性设置操作的捕捉器 |
| has() | in 操作符的捕捉器 |
| deleteProperty() | delete 操作符的捕捉器 |
| ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器 |
| apply() | 函数调用操作的捕捉器 |
| construct() | new 操作符的捕捉器 |
还有一些其他方法,不再举例。
基础示例,我们举例讲下get和set方法
get():
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
}
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
set():
let 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');
}
}
// The default behavior to store the value
obj[prop] = value;
// 表示成功
return true;
}
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age); // 100
person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer
person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid
可撤销的Proxy
proxy有一个唯一的静态方法,Proxy.revocable(target, handler)
Proxy.revocable()方法可以用来创建一个可撤销的代理对象
该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}
- proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
- revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。
该方法常用于完全封闭对目标对象的访问, 如下示例:
const target = { name: 'qingshan'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值输出 qingshan
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked
实用场景
有时我们想要一个number类型的数据,但是拿回来的却是string类型,我们可以proxy实现一个逻辑分离的数据格式验证器
const target = {
_id: '1024',
name: 'vuejs'
}
const validators = {
name(val) {
return typeof val === 'string';
},
_id(val) {
return typeof val === 'number' && val > 1024;
}
}
const createValidator = (target, validator) => {
return new Proxy(target, {
_validator: validator,
set(target, propkey, value, proxy){
let validator = this._validator[propkey](value)
if(validator){
return Reflect.set(target, propkey, value, proxy)
}else {
throw Error(`Cannot set ${propkey} to ${value}. Invalid type.`)
}
}
})
}
const proxy = createValidator(target, validators)
proxy.name = 'vue-js.com' // vue-js.com
proxy.name = 10086 // Uncaught Error: Cannot set name to 10086. Invalid type.
proxy._id = 1025 // 1025
proxy._id = 22 // Uncaught Error: Cannot set _id to 22. Invalid type
this问题
proxy会改变target中的this指向,一旦proxy代理了arget,target内部的this则指向了proxy,而不是target。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
上面代码中,一旦proxy代理target,target.m()内部的this就是指向proxy,而不是target。所以,虽然proxy没有做任何拦截,target.m()和proxy.m()返回不一样的结果。
有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate();
// TypeError: this is not a Date object.
另外,Proxy 拦截函数内部的this,指向的是handler对象。
const handler = {
get: function (target, key, receiver) {
console.log(this === handler);
return 'Hello, ' + key;
},
set: function (target, key, value) {
console.log(this === handler);
target[key] = value;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.foo
// true
// Hello, foo
proxy.foo = 1
// true