ES6之Proxy

  • 1. Proxy

    • 1.1. 概述

    • 1.2. Proxy 构造函数

    • 1.3. 处理器方法(Handler Methods)

      • 1.3.1. get(target, propKey, receiver):

      • 1.3.2. set(target, propKey, value, receiver):

      • 1.3.3. has(target, propKey):

      • 1.3.4. deleteProperty(target, propKey):

      • 1.3.5. ownKeys(target):

      • 1.3.6. getOwnPropertyDescriptor(target, propKey):

      • 1.3.7. defineProperty(target, propKey, propDesc):

      • 1.3.8. preventExtensions(target):

      • 1.3.9. getPrototypeOf(target):

      • 1.3.10. isExtensible(target):

      • 1.3.11. setPrototypeOf(target, proto):

      • 1.3.12. apply(target, object, args):

      • 1.3.13. construct(target, args):

  • 2. 应用场景

    • 2.1. 数据校验

    • 2.2. 缓存

    • 2.3. 虚拟化DOM

    • 2.4. 远程对象代理

1. Proxy

ES6之Proxy

1.1. 概述

ES6(ECMAScript 2015)引入了Proxy对象,属于一种“元编程”(meta programming),即对编程语言进行编程。

这是一种可以用来定义基本操作(如获取或设置属性)的自定义行为的对象。

Proxy 可以让你在访问一个对象之前对其进行拦截,从而实现对对象操作的控制,比如验证、计算、日志记录等。

这对于开发框架、库或者进行某些高级的JavaScript编程非常有用。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问 进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});

上面代码对一个空对象架设了一层拦截,重定义了属性的读取( get )和设置( set )行为。

1.2. Proxy 构造函数

  • 目标对象(target):你想要对其操作进行拦截的对象。

  • 处理器对象(handler):定义了一系列拦截行为的对象,比如如何处理属性的获取(get)、设置(set)、枚举(enumerate)等。

创建一个Proxy实例的基本语法如下:

const proxy = new Proxy(target, handler);
  • target:被代理的目标对象。

  • handler:一个对象,其属性是被拦截操作的类型(如get, set等),属性值是对应的处理函数。

Proxy 对象的所有用法,都是上面这种形式,不同的只是 handler 参数的写法。

其中, new Proxy() 表示生成一个 Proxy 实例, target 参数表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为。

下面是另一个拦截读取属性行为的例子。

var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

上面代码中,作为构造函数, Proxy 接受两个参数。

  • 第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有 Proxy 的介入,操作原来要访问的就是这个对象;

  • 第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。

比如,上面代码中,配置对象有一个 get 方法,用来拦截对目标对象属性的访问请求。

get 方法的两个参数分别是目标对象和所要访问的属性。

可以看到,由于拦截函数总是返回 35 ,所以访问任何属性都得到 35 。

注意,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxy 对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

如果 handler 没有设置任何拦截,那就等同于直接通向原对象。

var target = {};
var handler = {};

var proxy = new Proxy(target, handler);

proxy.a = 'b';
target.a // "b"

上面代码中, handler 是一个空对象,没有任何拦截效果,访问 proxy 就等同于访问 target 。

1.3. 处理器方法(Handler Methods)

以下是几个常用的处理器方法:

1.3.1. get(target, propKey, receiver):

拦截对象属性的读取,比如 proxy.foo 和 proxy['foo'] 。

get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为:

  • 目标对象、

  • 属性名

  • proxy 实例本身(即 this 关键字指向的那个对象)

其中最后一个参数可选。

let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});

let obj = Object.create(proto);
obj.foo // "GET foo"

上面代码中,拦截操作定义在 Prototype 对象上面,所以如果读取 obj 对象继承的属性时,拦截会生效。

1.3.2. set(target, propKey, value, receiver):

拦截对象属性的设置,比如 proxy.foo = v 或 proxy['foo'] = v ,返回一个布尔值。

set 方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为:

  • 目标对象、

  • 属性名、

  • 属性值

  • Proxy 实例本身

其中最后一个参数可选。

假定 Person 对象有一个 age 属性,该属性应该是一个不大于200的整数,那么可以使用 Proxy 保证 age 的属性值符合要求。

let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not aninteger');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于age以外的属性,直接保存
obj[prop] = value;
}
};

let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错

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

1.3.3. has(target, propKey):

拦截 propKey in proxy 的操作,返回一个布尔值。

has 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in 运算符。

下面的例子使用 has 方法隐藏某些属性,不被 in 运算符发现。

var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);

'_prop' in proxy // false

上面代码中,如果原对象的属性名的第一个字符是下划线, proxy.has 就会返回 false ,从而不会被 in 运算符发现

1.3.4. deleteProperty(target, propKey):

拦截 delete proxy[propKey] 的操作,返回一个布尔值。

deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,当前属性就无法被 delete 命令删除。

var 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 = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

1.3.5. ownKeys(target):

拦截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) ,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。

1.3.6. getOwnPropertyDescriptor(target, propKey):

拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。

1.3.7. defineProperty(target, propKey, propDesc):

拦截 Object.defineProperty(proxy, propKey,propDesc) 、Object.defineProperties(proxy,propDescs) ,返回一个布尔值。

1.3.8. preventExtensions(target):

拦截 Object.preventExtensions(proxy) ,返回一个布尔值。

1.3.9. getPrototypeOf(target):

拦截 Object.getPrototypeOf(proxy) ,返回一个对象。

1.3.10. isExtensible(target):

拦截 Object.isExtensible(proxy) ,返回一个布尔值。

1.3.11. setPrototypeOf(target, proto):

拦截 Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

1.3.12. apply(target, object, args):

拦截 Proxy 实例作为函数调用的操作,比如 proxy(…args) 、 proxy.call(object,…args) 、 proxy.apply(…) 。

1.3.13. construct(target, args):

拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(…args) 。

更多详细内容,请微信搜索“前端爱好者戳我 查看

2. 应用场景

  • 数据绑定和响应式编程:例如Vue 3的Reactivity系统就利用Proxy来监测数据变化,实现自动更新视图。

  • 权限控制和验证:在访问特定属性或方法前进行权限检查。

  • 日志记录和调试:记录对象的访问和修改历史。

  • 虚拟化对象结构:为现有对象提供一个虚拟的、可能具有不同行为的视图。

Proxy是一个强大的工具,但需要注意的是,它可能会使代码变得难以理解和维护,因此应当谨慎使用。

ES6的Proxy提供了强大的元编程能力,能够让我们在访问或修改对象时插入自定义逻辑。以下是一些典型的使用场景及示例:

2.1. 数据校验

场景说明:在复杂应用中,尤其是在表单处理和状态管理时,经常需要对输入数据进行校验。使用Proxy可以在数据赋值时进行验证。

示例

const validator = {
set(target, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new Error('Age must be an integer');
}
if (value < 0 || value > 120) {
throw new Error('Age must be between 0 and 120');
}
}
target[prop] = value;
return true; // 表示设置成功
}
};

const person = new Proxy({}, validator);
person.age = 25; // 正常赋值
console.log(person.age); // 输出: 25

try {
person.age = 'young'; // 尝试赋予非法值
} catch (e) {
console.error(e.message); // 输出错误信息
}

2.2. 缓存

场景说明:对于一些计算密集型或频繁访问的数据,可以通过Proxy实现只计算一次并缓存结果的机制。

示例

const cacheHandler = {
get(target, prop, receiver) {
const value = Reflect.get(...arguments);
if (typeof value === 'function') {
return (...args) => {
if (!cacheHandler.cache.has(prop)) {
cacheHandler.cache.set(prop, value(...args));
}
return cacheHandler.cache.get(prop);
};
}
return value;
},
cache: new Map()
};

function expensiveCalculation(a, b) {
console.log('Calculating...');
return a * a + b * b;
}

const cachedCalculation = new Proxy(expensiveCalculation, cacheHandler);

console.log(cachedCalculation(2, 3)); // 输出: Calculating... 然后输出: 13
console.log(cachedCalculation(2, 3)); // 输出: 13,不再重新计算

2.3. 虚拟化DOM

场景说明:虽然这不是Proxy最直接的应用,但在某些库或框架中,它被用来虚拟化DOM操作,使得框架能更高效地管理UI状态。

示例概念

想象一个简化版的场景,我们不直接操作DOM,而是通过Proxy拦截对DOM元素属性的访问和修改,以便于跟踪变化并优化渲染。

// 简化的虚拟DOM概念展示,实际应用中会更复杂
const domProxy = (element) => {
return new Proxy(element, {
set(target, prop, value) {
Reflect.set(target, prop, value);
// 这里可以添加逻辑来决定是否重新渲染或更新DOM
console.log(`Updating ${prop} to ${value}`);
return true;
}
});
};

const div = document.createElement('div');
const proxiedDiv = domProxy(div);
proxiedDiv.textContent = 'Hello, World!'; // 触发更新逻辑

2.4. 远程对象代理

场景说明:在分布式系统中,可以使用Proxy来模拟远程对象的行为,使得客户端可以像操作本地对象一样操作远程服务。

示例概念

class RemoteObjectProxy {
constructor(url) {
this.url = url;
}

get(target, prop) {
return fetch(`${this.url}/${prop}`).then(res => res.json());
}

// ...其他必要的代理逻辑,如set等
}

const proxy = new RemoteObjectProxy('http://api.example.com/data');
proxy.someProperty.then(data => console.log(data)); // 异步获取数据

这些示例展示了Proxy在不同场景下的灵活性和强大功能,从数据验证到性能优化,再到复杂的架构设计,Proxy都是一个值得掌握的现代JavaScript特性。


原文始发于微信公众号(前端爱好者):ES6之Proxy

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/290473.html

(0)
李, 若俞的头像李, 若俞

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!