Appearance
定义: 用于定义基本操作的自定义行为
proxy
修改的是程序默认形为,就形同于在编程语言层面上做修改,属于元编程(meta
programming
)
- 元编程(英语:Metaprogramming,又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。
这段程序每执行一次能帮我们生成一个名为program的文件,文件内容为1024行echo
,如果我们手动来写1024行代码,效率显然低效。
元编程优点:与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译。
proxy
译为代理,可以理解为在操作目标对象前架设一层代理,将所有本该我们手动编写的程序交由代理来处理,生活中也有许许多多的“proxy”, 如代购,中介,因为他们,所有的行为都不会直接触达到目标对象。
正文
本篇文章作为 Vue3
源码系列前置篇章之一,Proxy
的科普文,跟Vue3
并没有绝对关系,但是当你静下心读完了前置篇章,再去读后续的源码系列,感受定会截然不同。
语法
- target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
- handler 一个通常以函数作为属性的对象,用来定制拦截行为
const proxy = new Proxy(target, handle)
举个例子
const origin = {}
const obj1 = new Proxy(origin, {
get: function (target, propKey, receiver) {
return '10'
}
});
console.log(obj1.a );// 10
console.log(obj1.b);
origin.a // undefined
origin.b // undefined
上方代码我们给一个空对象的get架设了一层代理,所有get
操作都会直接返回我们定制的数字10,需要注意的是,代理只会对proxy
对象生效,如上方的origin
就没有任何效果
Handler 对象常用的方法
方法 | 描述 |
---|---|
handler.has() | in 操作符的捕捉器。 |
handler.get() | 属性读取操作的捕捉器。 |
handler.set() | 属性设置操作的捕捉器。 |
handler.deleteProperty() | delete 操作符的捕捉器。 |
handler.ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。 |
handler.apply() | 函数调用操作的捕捉器。 |
handler.construct() | new 操作符的捕捉器 |
下面挑handler.get
重点讲一下,其它方法的使用也都大同小异,不同的是参数的区别
授受三个参数 get(target, propKey, ?receiver)
- target 目标对象
- propkey 属性名
- receiver Proxy 实例本身
const person = {
like: "vuejs"
}
const obj2 = new Proxy(person, {
get: function(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
}
}
})
console.log(obj2.like );// vuejs
console.log(obj2.test);// Uncaught ReferenceError: Prop name "test" does not exist.
上面的代码表示在读取代理目标的值时,如果有值则直接返回,没有值就抛出一个自定义的错误
注意:
- 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同
- 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined
const obj3 = {};
Object.defineProperty(obj3, "a", {
configurable: false,
enumerable: false,
value: 10,
writable: false
})
const p = new Proxy(obj3, {
get: function(target, prop) {
return 20;
}
})
console.log(p.a ); // Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable..
可撤销的proxy
proxy
有一个唯一的静态方法,Proxy.revocable(target, handler)
Proxy.revocable()
方法可以用来创建一个可撤销的代理对象
该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}
- proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
- revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。
该方法常用于完全封闭对目标对象的访问, 如下示例
const handler = {
get(target, prop) {
return prop in target ? target[prop] : `Property ${prop} does not exist`;
}
};
const target = { name: 'vuejs'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值输出 vuejs
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked
Proxy的应用场景
校验器
想要一个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, {
set(target, propkey, value, proxy) {
// 访问外部的 validator,而不是 this._validator
const isValid = validator[propkey](value);
if (isValid) {
return Reflect.set(target, propkey, value, proxy);
} else {
throw new Error(`Cannot set ${propkey} to ${value}. Invalid type.`);
}
}
});
}
const proxy = createValidator(target, validators);
try {
proxy.name = 'vue-js.com'; // 'vue-js.com' is valid
console.log(proxy.name); // Output: 'vue-js.com'
} catch (error) {
console.error(error);
}
try {
proxy.name = 10086; // Invalid type
} catch (error) {
console.error(error); // Output: Error: Cannot set name to 10086. Invalid type.
}
try {
proxy._id = 1025; // 1025 is valid
console.log(proxy._id); // Output: 1025
} catch (error) {
console.error(error);
}
try {
proxy._id = 22; // Invalid _id value
} catch (error) {
console.error(error); // Output: Error: Cannot set _id to 22. Invalid type.
}
私有属性
在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私‘私有化’,下面使用Proxy轻松实现私有属性拦截
const target = {
_id: '1024',
name: 'vuejs'
}
const proxy = new Proxy(target, {
get(target, propkey, proxy) {
console.log(proxy);
if(propkey[0] === '_') {
throw Error(`${propkey} is restricted`)
}
return Reflect.get(target, propkey, proxy);
},
-
set(target, propkey, value, proxy) {
if(propkey[0] === '_') {
throw Error(`${propkey} is restricted`)
}
return Reflect.set(target, propkey, value, proxy);
}
});
console.log(proxy.name); // Output: vuejs
为什么要用Proxy重构
在 Proxy
之前,JavaScript
中就提供过 Object.defineProperty
,允许对对象的 getter/setter
进行拦截
Vue3.0之前的双向绑定是由 defineProperty
实现, 在3.0重构为 Proxy
,那么两者的区别究竟在哪里呢?
首先我们再来回顾一下它的定义
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
上面给两个词划了重点,对象上,属性,我们可以理解为是针对对象上的某一个属性做处理的
语法
- obj 要定义属性的对象
- prop 要定义或修改的属性的名称或 Symbol
- descriptor 要定义或修改的属性描述符
const obj = {}
Object.defineProperty(obj, "a", {
value : 1,
writable : false, // 是否可写
configurable : false, // 是否可配置
enumerable : false // 是否可枚举
})
// 上面给了三个false, 下面的相关操作就很容易理解了
obj.a = 2 // 无效
delete obj.a // 无效
for(key in obj){
console.log('key',key) // 无效
}