JavaScript中的Proxy

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

语法:

const p = new Proxy(target, handler)

参数

target

要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler

一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

1.get

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

 1 var person = {
 2   name: "张三"
 3 };
 4 
 5 var proxy = new Proxy(person, {
 6   get: function(target, property) {
 7     if (property in target) {
 8       return target[property];
 9     } else {
10       throw new ReferenceError("Property \"" + property + "\" does not exist.");
11     }
12   }
13 });
14 
15 proxy.name // "张三"
16 proxy.age // 抛出一个错误
 
 
1 let proto = new Proxy({}, {
2   get(target, propertyKey, receiver) {
3     console.log('GET ' + propertyKey);
4     return target[propertyKey];
5   }
6 });
7 
8 let obj = Object.create(proto);
9 obj.foo // "GET foo"

2.set

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

是不是和get方法大同小异,在读取被代理对象的属性时,该方法会执行;

 1 let validator = {
 2   set: function(obj, prop, value) {
 3     if (prop === 'age') {
 4       if (!Number.isInteger(value)) {
 5         throw new TypeError('The age is not an integer');
 6       }
 7       if (value > 200) {
 8         throw new RangeError('The age seems invalid');
 9       }
10     }
11 
12     // 对于满足条件的 age 属性以及其他属性,直接保存
13     obj[prop] = value;
14   }
15 };
16 
17 let person = new Proxy({}, validator);
18 
19 person.age = 100;
20 
21 person.age // 100
22 person.age = 'young' // 报错
23 person.age = 300 // 报错

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。

我们知道,JavaScript语言中没有私有变量一说,通常的做法是在属性名称前加‘_’表示属性为对象内部私有,但这只是一种通用的约定,在语言的内部并没有限制对“私有属性”的访问。

但是有了Proxy后就可以有效解决。see code

 1 const handler = {
 2   get (target, key) {
 3     invariant(key, 'get');
 4     return target[key];
 5   },
 6   set (target, key, value) {
 7     invariant(key, 'set');
 8     target[key] = value;
 9     return true;
10   }
11 };

// 这一个函数实现了对私有属性访问权限的控制
12 function invariant (key, action) {
13   if (key[0] === '_') {
14     throw new Error(`Invalid attempt to ${action} private "${key}" property`);
15   }
16 }
17 const target = {};
18 const proxy = new Proxy(target, handler);
19 proxy._prop
20 // Error: Invalid attempt to get private "_prop" property
21 proxy._prop = 'c'
22 // Error: Invalid attempt to set private "_prop" property

上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。

有一点需要注意:set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身

 1 const handler = {
 2   set: function(obj, prop, value, receiver) {
 3     obj[prop] = receiver;
 4   }
 5 };
 6 const proxy = new Proxy({}, handler);
 7 const myObj = {};
 8 Object.setPrototypeOf(myObj, proxy);
 9 
10 myObj.foo = 'bar';
11 myObj.foo === myObj // true

上面代码中,设置myObj.foo属性的值时,myObj并没有foo属性,因此引擎会到myObj的原型链去找foo属性。myObj的原型对象proxy是一个 Proxy 实例,设置它的foo属性会触发set方法。这时,第四个参数receiver就指向原始赋值行为所在的对象myObj

还有一点需要注意,如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用

 1 const obj = {};
 2 Object.defineProperty(obj, 'foo', {
 3   value: 'bar',
 4   writable: false,
 5 });
 6 
 7 const handler = {
 8   set: function(obj, prop, value, receiver) {
 9     obj[prop] = 'baz';
10   }
11 };
12 
13 const proxy = new Proxy(obj, handler);
14 proxy.foo = 'baz';
15 proxy.foo // "bar"

猜你喜欢

转载自blog.csdn.net/m0_62336865/article/details/126989063