当前位置:首页 > 技术 > 正文内容

JavaScript代理与反射机制

访客 技术 2026年6月2日 1

目录- 9.1 代理基础

  • 9.1.1 创建空代理
  • 9.1.2 定义捕获器
  • 9.1.3 捕获器参数和反射API
  • 9.1.4 捕获器不变式
  • 9.1.5 可撤销代理
  • 9.1.6 实用 Reflect API
  • 9.1.7 代理另一个代理
  • 9.1.8 代理的问题与不足
  • 9.2 代理捕获器与反射方法
  • 9.2.1 get()
  • 9.2.2 set()
  • 9.2.3 has()
  • 9.2.4 defineProperty()
  • 9.2.5 getOwnPropertyDescriptor()
  • 9.2.6 deleteProperty()
  • 9.2.7 ownKeys()
  • 9.2.8 getPrototypeOf()
  • 9.2.9 setPrototypeOf()
  • 9.2.10 isExtensible()
  • 9.2.11 preventExtensions()
  • 9.2.12 apply()
  • 9.2.13 construct()
  • 9.3 代理模式
  • 9.3.1 跟踪属性访问
  • 9.3.2 隐藏属性
  • 9.3.3 属性验证
  • 9.3.4 函数与构造函数参数验证
  • 9.3.5 数据绑定与可观察对象

ES6引入的代理和反射机制为开发者提供了拦截基础操作并注入自定义行为的能力。通过创建目标对象的代理实例,可以在操作实际作用于目标前进行控制。这种机制的核心在于处理程序对象中定义的捕获器,它们能够拦截并修改对象的基本操作行为。### 9.1 代理基础

代理作为目标对象的中间层,既保持与目标的关联性,又具备独立的控制能力。所有对代理的操作都会被转发到目标对象,但可以通过捕获器实现行为拦截。

9.1.1 创建空代理

通过Proxy构造函数创建代理实例,需要指定目标对象和处理程序对象。两个参数都是必需的,缺少任一都会抛出异常。代理实例的所有操作都会映射到目标对象。

const sourceObject = {
    id:'original'
};

const handlerConfig = {};

const proxyInstance = new Proxy(sourceObject, handlerConfig);

console.log(sourceObject.id); // original
console.log(proxyInstance.id); // original

sourceObject.id = 'modified';
console.log(sourceObject.id); // modified
console.log(proxyInstance.id); // modified

proxyInstance.id = 'changed';
console.log(sourceObject.id); // changed
console.log(proxyInstance.id); // changed

9.1.2 定义捕获器

捕获器是处理程序对象中定义的特殊方法,用于拦截特定操作。当通过代理执行操作时,会优先触发对应的捕获器逻辑。

const source = {
    value: 42
};

const handler = {
    get() {
        return 'intercepted';
    }
};

const proxy = new Proxy(source, handler);

console.log(source.value); // 42
console.log(proxy.value); // intercepted

9.1.3 捕获器参数和反射API

每个捕获器都接收目标对象、属性名和代理实例三个参数。通过Reflect对象可以方便地恢复原始操作行为。

const source = {
    data: 'original'
};

const handler = {
    get(target, prop, receiver) {
        console.log(`Accessing ${prop}`);
        return Reflect.get(target, prop, receiver);
    }
};

const proxy = new Proxy(source, handler);
proxy.data; // Accessing data

9.1.4 捕获器不变式

捕获器行为需遵循规范限制,例如不能修改不可配置属性的访问权限:

const source = Object.freeze({ foo: 'bar' });
const handler = {
    get() {
        return 'modified';
    }
};

const proxy = new Proxy(source, handler);
console.log(proxy.foo); // 抛出类型错误

9.1.5 可撤销代理

通过Proxy.revocable方法创建的代理支持撤销操作,撤销后所有操作都会失败:

const { proxy, revoke } = Proxy.revocable({}, {});
proxy.anyProperty; // 正常工作
revoke();
proxy.anyProperty; // 抛出错误

9.1.6 实用 Reflect API

Reflect对象提供了一系列静态方法,用于执行默认操作并返回布尔状态标识:

const obj = {};
const success = Reflect.defineProperty(obj, 'key', {
    value: 'value',
    enumerable: true
});
console.log(success); // true

9.1.7 代理另一个代理

代理可以嵌套使用,每个代理都会依次触发自己的捕获器:

const base = { prop: 'value' };
const firstProxy = new Proxy(base, {
    get(target, prop) {
        console.log('First level');
        return Reflect.get(target, prop);
    }
});
const secondProxy = new Proxy(firstProxy, {
    get(target, prop) {
        console.log('Second level');
        return Reflect.get(target, prop);
    }
});
secondProxy.prop; // 输出两行日志

9.1.8 代理的问题与不足

代理可能影响某些依赖对象身份判断的场景,例如WeakMap的键引用:

const wm = new WeakMap();
class Data {
    constructor(id) {
        wm.set(this, id);
    }
}
const proxy = new Proxy(new Data(1), {});
console.log(wm.get(proxy)); // undefined

9.2 代理捕获器与反射方法

9.2.1 get()

9.2.2 set()

9.2.3 has()

9.2.4 defineProperty()

9.2.5 getOwnPropertyDescriptor()

9.2.6 deleteProperty()

9.2.7 ownKeys()

9.2.8 getPrototypeOf()

9.2.9 setPrototypeOf()

9.2.10 isExtensible()

9.2.11 preventExtensions()

9.2.12 apply()

9.2.13 construct()

9.3 代理模式

9.3.1 跟踪属性访问

通过捕获器可以记录所有属性访问和修改操作:

const target = { name: 'John' };
const proxy = new Proxy(target, {
    get(target, prop) {
        console.log(`Reading ${prop}`);
        return Reflect.get(target, prop);
    },
    set(target, prop, value) {
        console.log(`Updating ${prop} to ${value}`);
        return Reflect.set(target, prop, value);
    }
});
proxy.name; // 读取操作日志
proxy.name = 'Doe'; // 更新操作日志

9.3.2 隐藏属性

可以实现属性过滤,使特定属性不可见:

const hidden = ['secret'];
const target = { public: 'visible', secret: 'hidden' };
const proxy = new Proxy(target, {
    get(target, prop) {
        return hidden.includes(prop) ? undefined : Reflect.get(target, prop);
    },
    has(target, prop) {
        return !hidden.includes(prop) && Reflect.has(target, prop);
    }
});
console.log(proxy.secret); // undefined
console.log('secret' in proxy); // false

9.3.3 属性验证

可以限制属性值类型:

const target = { number: 0 };
const proxy = new Proxy(target, {
    set(target, prop, value) {
        if (typeof value === 'number') {
            return Reflect.set(target, prop, value);
        }
        return false;
    }
});
proxy.number = 42; // 有效
proxy.number = 'text'; // 无效

9.3.4 函数与构造函数参数验证

可以对函数参数进行类型检查:

function sum(...args) {
    return args.reduce((a, b) => a + b, 0);
}
const proxy = new Proxy(sum, {
    apply(target, thisArg, args) {
        if (args.some(arg => typeof arg !== 'number')) {
            throw new TypeError('All arguments must be numbers');
        }
        return Reflect.apply(target, thisArg, args);
    }
});
proxy(1, 2, 3); // 有效
proxy(1, '2', 3); // 抛出错误

9.3.5 数据绑定与可观察对象

可以实现自动更新的集合管理:

const list = [];
const proxy = new Proxy(list, {
    set(target, prop, value) {
        const result = Reflect.set(target, prop, value);
        if (result) {
            console.log(`New item: ${value}`);
        }
        return result;
    }
});
proxy.push('Alice'); // 触发日志
proxy.push('Bob'); // 再次触发日志
标签: JavaScript

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。