JavaScript Proxy 元编程:实现响应式、验证器和 ORM 的底层利器

3484 字
17 分钟
JavaScript Proxy 元编程:实现响应式、验证器和 ORM 的底层利器

前言#

Proxy 是 ES6 引入的元编程(Metaprogramming)原语,它允许你拦截和自定义对象的基本操作——属性读取、赋值、函数调用、in 运算符、delete 操作等等。如果说普通编程是”写操作数据的代码”,那么元编程就是”写操作代码的代码”。

Vue 3 的响应式系统、各种数据验证库、ORM 框架的查询构建器,底层都大量使用了 Proxy。本文将全面解析 Proxy 的 handler traps、Reflect API,并通过三个实战项目(响应式系统、数据验证框架、迷你 ORM)展示 Proxy 的强大威力。

Proxy 基础#

创建 Proxy#

const target = { name: 'Alice', age: 30 };
const handler = {
get(target, property, receiver) {
console.log(`Reading ${String(property)}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`Writing ${String(property)} = ${value}`);
return Reflect.set(target, property, value, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name; // Reading name → "Alice"
proxy.age = 31; // Writing age = 31

Proxy 接收两个参数:

  • target:被代理的原始对象
  • handler:一个对象,定义了各种拦截操作的”陷阱”(traps)

所有 Handler Traps 一览#

Proxy 支持 13 个陷阱,覆盖了对象的所有基本操作:

const fullHandler = {
// 属性读取: proxy.foo, proxy['foo']
get(target, property, receiver) {},
// 属性赋值: proxy.foo = bar
set(target, property, value, receiver) {},
// 属性存在检查: 'foo' in proxy
has(target, property) {},
// 属性删除: delete proxy.foo
deleteProperty(target, property) {},
// Object.keys(), for...in 等
ownKeys(target) {},
// Object.getOwnPropertyDescriptor(proxy, prop)
getOwnPropertyDescriptor(target, property) {},
// Object.defineProperty(proxy, prop, descriptor)
defineProperty(target, property, descriptor) {},
// Object.getPrototypeOf(proxy)
getPrototypeOf(target) {},
// Object.setPrototypeOf(proxy, proto)
setPrototypeOf(target, prototype) {},
// Object.isExtensible(proxy)
isExtensible(target) {},
// Object.preventExtensions(proxy)
preventExtensions(target) {},
// proxy(...args) — 仅当 target 是函数时
apply(target, thisArg, argumentsList) {},
// new proxy(...args) — 仅当 target 是构造函数时
construct(target, argumentsList, newTarget) {},
};

Reflect API:Proxy 的最佳拍档#

Reflect 对象为每个 Proxy trap 提供了对应的默认操作方法。使用 Reflect 而不是直接操作 target 的原因:

  1. 保持正确的 receiver:确保原型链上的 getter/setter 正确工作
  2. 返回值语义一致Reflect.set() 返回 boolean,与 set trap 的期望返回值一致
  3. 避免异常:某些操作如 Object.defineProperty 失败会抛异常,Reflect.defineProperty 返回 false
// 错误示范:直接操作 target
const badHandler = {
get(target, prop) {
return target[prop]; // 不传 receiver,原型链上的 getter 可能出错
},
set(target, prop, value) {
target[prop] = value; // 不返回 boolean
return true;
}
};
// 正确示范:使用 Reflect
const goodHandler = {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
return Reflect.set(target, prop, value, receiver);
}
};

Receiver 的重要性#

const parent = {
get greeting() {
return `Hello, I'm ${this.name}`;
}
};
const child = Object.create(
new Proxy(parent, {
get(target, prop, receiver) {
console.log('receiver is child?', receiver === child);
return Reflect.get(target, prop, receiver); // receiver 确保 this 指向 child
}
})
);
child.name = 'Bob';
console.log(child.greeting);
// receiver is child? true
// "Hello, I'm Bob"

如果不传 receiverthis.name 将在 parent 上查找,得到 undefined

实战一:Vue 3 风格响应式系统#

Vue 3 的响应式核心就是基于 Proxy 实现的。下面我们从零实现一个简化版:

依赖收集与触发#

// 当前正在执行的副作用函数
let activeEffect = null;
const effectStack = [];
// 存储依赖关系: target -> property -> Set<effect>
const targetMap = new WeakMap();
function track(target, property) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let deps = depsMap.get(property);
if (!deps) {
deps = new Set();
depsMap.set(property, deps);
}
deps.add(activeEffect);
}
function trigger(target, property) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const deps = depsMap.get(property);
if (!deps) return;
// 创建副本避免无限循环
const effectsToRun = new Set(deps);
effectsToRun.forEach(effect => {
// 避免递归触发自身
if (effect !== activeEffect) {
effect();
}
});
}

reactive() 实现#

const reactiveMap = new WeakMap();
function reactive(target) {
// 避免重复代理
if (reactiveMap.has(target)) {
return reactiveMap.get(target);
}
const proxy = new Proxy(target, {
get(target, property, receiver) {
// 特殊标记,用于判断是否是 reactive 对象
if (property === '__isReactive') return true;
if (property === '__raw') return target;
track(target, property);
const result = Reflect.get(target, property, receiver);
// 深层代理:如果值是对象,递归创建 reactive
if (result !== null && typeof result === 'object') {
return reactive(result);
}
return result;
},
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver);
// 只在值真正变化时触发
if (oldValue !== value && (oldValue === oldValue || value === value)) {
trigger(target, property);
}
return result;
},
has(target, property) {
track(target, property);
return Reflect.has(target, property);
},
deleteProperty(target, property) {
const hadKey = Object.prototype.hasOwnProperty.call(target, property);
const result = Reflect.deleteProperty(target, property);
if (hadKey && result) {
trigger(target, property);
}
return result;
},
ownKeys(target) {
// 当 for...in 或 Object.keys() 时追踪
track(target, Symbol('iterate'));
return Reflect.ownKeys(target);
}
});
reactiveMap.set(target, proxy);
return proxy;
}

effect() 实现#

function effect(fn) {
const effectFn = () => {
// 清除旧的依赖关系
cleanup(effectFn);
activeEffect = effectFn;
effectStack.push(effectFn);
const result = fn();
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
return result;
};
effectFn.deps = [];
effectFn();
return effectFn;
}
function cleanup(effectFn) {
for (const deps of effectFn.deps) {
deps.delete(effectFn);
}
effectFn.deps.length = 0;
}

computed() 实现#

function computed(getter) {
let value;
let dirty = true;
const effectFn = effect(() => {
value = getter();
dirty = false;
});
// 重写 effect 的自动执行行为
// 这里简化实现,实际 Vue 使用 scheduler
return {
get value() {
if (dirty) {
value = getter();
dirty = false;
}
return value;
}
};
}

完整使用示例#

const state = reactive({
firstName: 'John',
lastName: 'Doe',
todos: [
{ text: 'Learn Proxy', done: false },
{ text: 'Build reactive system', done: false }
]
});
// 自动追踪依赖
effect(() => {
console.log(`Full name: ${state.firstName} ${state.lastName}`);
});
// 立即输出: "Full name: John Doe"
state.firstName = 'Jane';
// 自动输出: "Full name: Jane Doe"
state.lastName = 'Smith';
// 自动输出: "Full name: Jane Smith"
// 深层响应式
effect(() => {
const pending = state.todos.filter(t => !t.done).length;
console.log(`Pending todos: ${pending}`);
});
// 立即输出: "Pending todos: 2"
state.todos[0].done = true;
// 自动输出: "Pending todos: 1"

实战二:数据验证框架#

利用 Proxy 的 set trap,可以实现强大的运行时数据验证:

Schema 定义#

const Types = {
string: {
validate: v => typeof v === 'string',
message: 'must be a string'
},
number: {
validate: v => typeof v === 'number' && !isNaN(v),
message: 'must be a number'
},
boolean: {
validate: v => typeof v === 'boolean',
message: 'must be a boolean'
},
email: {
validate: v => typeof v === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
message: 'must be a valid email'
},
url: {
validate: v => {
try { new URL(v); return true; } catch { return false; }
},
message: 'must be a valid URL'
},
array: (itemValidator) => ({
validate: v => Array.isArray(v) && v.every(item => itemValidator.validate(item)),
message: `must be an array of valid items`
}),
oneOf: (...values) => ({
validate: v => values.includes(v),
message: `must be one of: ${values.join(', ')}`
}),
range: (min, max) => ({
validate: v => typeof v === 'number' && v >= min && v <= max,
message: `must be between ${min} and ${max}`
})
};

验证 Proxy 工厂#

class ValidationError extends Error {
constructor(property, value, message) {
super(`Validation failed for "${property}": ${message} (got ${JSON.stringify(value)})`);
this.property = property;
this.value = value;
}
}
function createValidatedModel(schema, options = {}) {
const { strict = true, onError = 'throw', coerce = false } = options;
const errors = [];
const handler = {
set(target, property, value, receiver) {
const rule = schema[property];
// 严格模式:不允许设置 schema 中未定义的属性
if (strict && !rule && typeof property === 'string') {
const err = new ValidationError(property, value, 'property not defined in schema');
if (onError === 'throw') throw err;
errors.push(err);
return false;
}
if (rule) {
// 类型强制转换
if (coerce && rule.coerce) {
value = rule.coerce(value);
}
// 执行所有验证器
const validators = Array.isArray(rule) ? rule : [rule];
for (const validator of validators) {
if (!validator.validate(value)) {
const err = new ValidationError(property, value, validator.message);
if (onError === 'throw') throw err;
errors.push(err);
return false;
}
}
}
return Reflect.set(target, property, value, receiver);
},
get(target, property, receiver) {
if (property === '__errors') return [...errors];
if (property === '__schema') return schema;
if (property === '__isValid') return errors.length === 0;
return Reflect.get(target, property, receiver);
},
deleteProperty(target, property) {
if (strict && schema[property]?.required) {
throw new ValidationError(property, undefined, 'required property cannot be deleted');
}
return Reflect.deleteProperty(target, property);
}
};
// 创建初始对象并设置默认值
const initial = {};
for (const [key, rule] of Object.entries(schema)) {
if (rule.default !== undefined) {
initial[key] = rule.default;
}
}
return new Proxy(initial, handler);
}

使用示例#

const user = createValidatedModel({
name: {
validate: v => typeof v === 'string' && v.length >= 2,
message: 'must be a string with at least 2 characters',
required: true
},
age: Types.range(0, 150),
email: Types.email,
role: Types.oneOf('admin', 'user', 'moderator'),
tags: Types.array(Types.string)
});
// 正常赋值
user.name = 'Alice';
user.age = 30;
user.email = 'alice@example.com';
user.role = 'admin';
user.tags = ['developer', 'blogger'];
console.log(user.name); // "Alice"
console.log(user.age); // 30
// 验证失败
try {
user.age = 200; // ValidationError: must be between 0 and 150
} catch (e) {
console.error(e.message);
}
try {
user.email = 'not-an-email'; // ValidationError: must be a valid email
} catch (e) {
console.error(e.message);
}
try {
user.role = 'superadmin'; // ValidationError: must be one of: admin, user, moderator
} catch (e) {
console.error(e.message);
}

嵌套验证#

function nestedModel(schema) {
return {
validate: v => typeof v === 'object' && v !== null,
message: 'must be an object',
transform: v => createValidatedModel(schema).assign(v)
};
}
// 深度代理版本
function deepValidated(schema) {
const handler = {
set(target, property, value, receiver) {
const rule = schema[property];
if (rule && typeof value === 'object' && value !== null && rule.schema) {
// 嵌套对象也进行验证代理
value = deepValidated(rule.schema);
Object.assign(value, value);
}
if (rule && !rule.validate(value)) {
throw new ValidationError(property, value, rule.message);
}
return Reflect.set(target, property, value, receiver);
},
get(target, property, receiver) {
return Reflect.get(target, property, receiver);
}
};
return new Proxy({}, handler);
}

实战三:迷你 ORM 查询构建器#

利用 Proxy 的 get trap 可以实现优雅的链式 API:

class QueryBuilder {
#table;
#conditions = [];
#selections = ['*'];
#ordering = [];
#limitVal = null;
#offsetVal = null;
#joins = [];
constructor(table) {
this.#table = table;
}
select(...fields) {
this.#selections = fields;
return this;
}
where(condition, ...params) {
this.#conditions.push({ clause: condition, params });
return this;
}
orderBy(field, direction = 'ASC') {
this.#ordering.push(`${field} ${direction}`);
return this;
}
limit(n) {
this.#limitVal = n;
return this;
}
offset(n) {
this.#offsetVal = n;
return this;
}
join(table, on) {
this.#joins.push(`JOIN ${table} ON ${on}`);
return this;
}
leftJoin(table, on) {
this.#joins.push(`LEFT JOIN ${table} ON ${on}`);
return this;
}
toSQL() {
let sql = `SELECT ${this.#selections.join(', ')} FROM ${this.#table}`;
if (this.#joins.length) {
sql += ' ' + this.#joins.join(' ');
}
if (this.#conditions.length) {
const where = this.#conditions.map(c => c.clause).join(' AND ');
sql += ` WHERE ${where}`;
}
if (this.#ordering.length) {
sql += ` ORDER BY ${this.#ordering.join(', ')}`;
}
if (this.#limitVal !== null) {
sql += ` LIMIT ${this.#limitVal}`;
}
if (this.#offsetVal !== null) {
sql += ` OFFSET ${this.#offsetVal}`;
}
return sql;
}
getParams() {
return this.#conditions.flatMap(c => c.params);
}
}

用 Proxy 实现魔术方法#

function createModel(tableName, columns) {
const model = {
tableName,
columns,
query() {
return new QueryBuilder(tableName);
},
find(id) {
return new QueryBuilder(tableName)
.where('id = ?', id)
.limit(1);
},
all() {
return new QueryBuilder(tableName);
}
};
// 使用 Proxy 实现动态 findByXxx 方法
return new Proxy(model, {
get(target, property, receiver) {
if (property in target) {
return Reflect.get(target, property, receiver);
}
const propStr = String(property);
// findByXxx 动态方法
if (propStr.startsWith('findBy')) {
const field = propStr.slice(6) // 去掉 'findBy'
.replace(/([A-Z])/g, '_$1') // camelCase → snake_case
.toLowerCase()
.replace(/^_/, '');
if (columns.includes(field)) {
return function (value) {
return new QueryBuilder(tableName)
.where(`${field} = ?`, value);
};
}
}
// whereXxx 动态条件
if (propStr.startsWith('where')) {
const field = propStr.slice(5)
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/^_/, '');
return function (operator, value) {
if (value === undefined) {
value = operator;
operator = '=';
}
return new QueryBuilder(tableName)
.where(`${field} ${operator} ?`, value);
};
}
return undefined;
}
});
}
// 使用
const User = createModel('users', ['id', 'name', 'email', 'age', 'created_at']);
console.log(User.find(1).toSQL());
// SELECT * FROM users WHERE id = ? LIMIT 1
console.log(User.findByEmail('alice@example.com').toSQL());
// SELECT * FROM users WHERE email = ?
console.log(User.findByName('Alice').orderBy('created_at', 'DESC').toSQL());
// SELECT * FROM users WHERE name = ? ORDER BY created_at DESC
console.log(
User.query()
.select('name', 'email')
.where('age > ?', 18)
.where('name LIKE ?', '%alice%')
.orderBy('name')
.limit(10)
.offset(20)
.toSQL()
);
// SELECT name, email FROM users WHERE age > ? AND name LIKE ? ORDER BY name LIMIT 10 OFFSET 20

用 Proxy 实现属性变更追踪(Dirty Checking)#

function trackable(obj) {
const original = { ...obj };
const changes = new Map();
const proxy = new Proxy(obj, {
set(target, property, value, receiver) {
const oldValue = target[property];
if (oldValue !== value) {
if (original[property] === value) {
changes.delete(property);
} else {
changes.set(property, { old: oldValue, new: value });
}
}
return Reflect.set(target, property, value, receiver);
},
get(target, property, receiver) {
if (property === '$isDirty') return changes.size > 0;
if (property === '$changes') return Object.fromEntries(changes);
if (property === '$reset') {
return () => {
for (const [key, val] of Object.entries(original)) {
target[key] = val;
}
changes.clear();
};
}
if (property === '$commit') {
return () => {
Object.assign(original, target);
changes.clear();
};
}
return Reflect.get(target, property, receiver);
}
});
return proxy;
}
// 使用
const record = trackable({ name: 'Alice', age: 30, email: 'alice@test.com' });
record.name = 'Bob';
record.age = 25;
console.log(record.$isDirty); // true
console.log(record.$changes);
// { name: { old: 'Alice', new: 'Bob' }, age: { old: 30, new: 25 } }
record.name = 'Alice'; // 恢复原值
console.log(record.$changes);
// { age: { old: 30, new: 25 } } — name 的变更自动清除
record.$reset();
console.log(record.name); // "Alice"
console.log(record.age); // 30
console.log(record.$isDirty); // false

高级技巧#

可撤销代理(Revocable Proxy)#

function createSecureSession(data, ttl = 5000) {
const { proxy, revoke } = Proxy.revocable(data, {
get(target, property, receiver) {
console.log(`[SESSION] Reading ${String(property)}`);
return Reflect.get(target, property, receiver);
}
});
// TTL 过期后自动撤销
setTimeout(() => {
revoke();
console.log('[SESSION] Expired and revoked');
}, ttl);
return { session: proxy, revoke };
}
const { session } = createSecureSession({ token: 'abc123', user: 'admin' }, 3000);
console.log(session.token); // [SESSION] Reading token → "abc123"
// 3 秒后...
// [SESSION] Expired and revoked
// session.token → TypeError: Cannot perform 'get' on a proxy that has been revoked

负索引数组#

function negativeArray(arr) {
return new Proxy(arr, {
get(target, property, receiver) {
const index = Number(property);
if (Number.isInteger(index) && index < 0) {
property = String(target.length + index);
}
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
const index = Number(property);
if (Number.isInteger(index) && index < 0) {
property = String(target.length + index);
}
return Reflect.set(target, property, value, receiver);
}
});
}
const arr = negativeArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5
console.log(arr[-2]); // 4
arr[-1] = 99;
console.log(arr); // [1, 2, 3, 4, 99]

不可变对象(Immutable Object)#

function immutable(obj) {
return new Proxy(obj, {
set() {
throw new TypeError('Cannot modify immutable object');
},
deleteProperty() {
throw new TypeError('Cannot delete from immutable object');
},
defineProperty() {
throw new TypeError('Cannot define property on immutable object');
},
setPrototypeOf() {
throw new TypeError('Cannot change prototype of immutable object');
},
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
// 深层不可变
if (value !== null && typeof value === 'object') {
return immutable(value);
}
return value;
}
});
}
const config = immutable({
db: { host: 'localhost', port: 5432 },
cache: { ttl: 3600 }
});
console.log(config.db.host); // "localhost"
try {
config.db.host = 'remote'; // TypeError: Cannot modify immutable object
} catch (e) {
console.error(e.message);
}

性能考量#

Proxy 虽然强大,但有性能成本:

  1. 每次属性访问都经过 trap:在热路径上,Proxy 比直接属性访问慢 5-10 倍
  2. 无法被 V8 内联优化:引擎无法对 Proxy 对象进行常规的 hidden class 优化
  3. 深层代理的递归开销:每次读取嵌套对象都会创建新的 Proxy(Vue 3 通过缓存优化了这个问题)

优化建议

// ❌ 不要在性能关键路径上使用 Proxy
for (let i = 0; i < 1000000; i++) {
sum += proxyObj.value; // 每次都经过 get trap
}
// ✅ 先解引用,再循环
const value = proxyObj.value;
for (let i = 0; i < 1000000; i++) {
sum += value;
}
// ✅ 缓存深层代理
const reactiveCache = new WeakMap();
function reactive(obj) {
if (reactiveCache.has(obj)) return reactiveCache.get(obj);
const proxy = new Proxy(obj, handler);
reactiveCache.set(obj, proxy);
return proxy;
}

Proxy 的不变量(Invariants)#

JavaScript 引擎会强制执行某些不变量,防止 Proxy 违反语言基本约定:

const obj = {};
Object.defineProperty(obj, 'x', {
value: 42,
writable: false,
configurable: false
});
const proxy = new Proxy(obj, {
get(target, property) {
return 100; // 试图返回与实际不同的值
}
});
// TypeError: 'get' on proxy: property 'x' is a read-only and non-configurable
// data property on the proxy target but the proxy did not return its actual value
console.log(proxy.x);

这些不变量确保了即使使用 Proxy,JavaScript 的核心语义也不会被破坏。

总结#

Proxy 是 JavaScript 元编程的终极工具:

  • 响应式系统:拦截读写操作,实现自动依赖收集和更新触发
  • 数据验证:在赋值时自动校验,保证数据完整性
  • ORM/查询构建:通过 get trap 实现动态方法和魔术属性
  • 变更追踪:自动记录对象修改历史
  • 访问控制:实现只读、可撤销、有时效的对象访问

配合 Reflect API,你可以在保持语言语义正确的前提下,对对象的行为进行任意定制。理解 Proxy 的 13 个 trap 和它们的不变量约束,是成为 JavaScript 高级开发者的必备技能。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

JavaScript Proxy 元编程:实现响应式、验证器和 ORM 的底层利器
https://boke.hackerdream.xyz/posts/javascript-proxy-metaprogramming/
作者
晴天
发布于
2026-02-09
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
125
分类
17
标签
287
总字数
257,955
运行时长
0
最后活动
0 天前

目录