结合 Proxy 和 Reflect,我们可以实现很多强大的功能。本文通过几个完整的实战案例来展示元编程的威力。
案例1:深度响应式系统#
实现一个类似 Vue 3 的响应式系统:
// 存储依赖关系const targetMap = new WeakMap()// 当前正在执行的 effectlet activeEffect = null// effect 栈,处理嵌套const effectStack = []
// 创建响应式对象function reactive(target) { if (typeof target !== 'object' || target === null) { return target }
const proxy = new Proxy(target, { get(target, key, receiver) { // 收集依赖 track(target, key)
const result = Reflect.get(target, key, receiver)
// 深度响应式 if (typeof result === 'object' && result !== null) { return reactive(result) }
return result },
set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver)
// 值变化时触发更新 if (oldValue !== value) { trigger(target, key) }
return result },
deleteProperty(target, key) { const hasKey = key in target const result = Reflect.deleteProperty(target, key)
if (hasKey && result) { trigger(target, key) }
return result }, })
return proxy}
// 收集依赖function track(target, key) { if (!activeEffect) return
let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) }
let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) }
dep.add(activeEffect)}
// 触发更新function trigger(target, key) { const depsMap = targetMap.get(target) if (!depsMap) return
const dep = depsMap.get(key) if (dep) { dep.forEach((effect) => { // 避免无限循环 if (effect !== activeEffect) { effect() } }) }}
// 创建副作用function effect(fn) { const effectFn = () => { try { activeEffect = effectFn effectStack.push(effectFn) return fn() } finally { effectStack.pop() activeEffect = effectStack[effectStack.length - 1] } }
effectFn() return effectFn}
// 计算属性function computed(getter) { let cached let dirty = true
const effectFn = effect(() => { cached = getter() dirty = false })
return { get value() { if (dirty) { effectFn() } return cached }, }}
// 使用示例const state = reactive({ user: { name: '张三' }, count: 0,})
effect(() => { console.log(`Count: ${state.count}`)})
effect(() => { console.log(`User: ${state.user.name}`)})
state.count++ // 自动打印 'Count: 1'state.user.name = '李四' // 自动打印 'User: 李四'案例2:Schema 验证代理#
实现一个类型安全的对象:
// 验证器定义const Validators = { string: (value) => typeof value === 'string', number: (value) => typeof value === 'number' && !isNaN(value), boolean: (value) => typeof value === 'boolean', array: (value) => Array.isArray(value), email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), phone: (value) => /^1[3-9]\d{9}$/.test(value), range: (min, max) => (value) => value >= min && value <= max, length: (min, max) => (value) => value.length >= min && value.length <= max, pattern: (regex) => (value) => regex.test(value), oneOf: (...values) => (value) => values.includes(value),}
// 创建 Schemafunction createSchema(definition) { const compiled = {}
for (const [key, rules] of Object.entries(definition)) { compiled[key] = Array.isArray(rules) ? rules : [rules] }
return compiled}
// 创建验证代理function createValidatedObject(schema, initialData = {}) { const compiledSchema = createSchema(schema)
function validate(key, value) { const rules = compiledSchema[key] if (!rules) return { valid: true }
for (const rule of rules) { let validator, message
if (typeof rule === 'string') { validator = Validators[rule] message = `${key} 必须是 ${rule} 类型` } else if (typeof rule === 'function') { validator = rule message = `${key} 验证失败` } else if (typeof rule === 'object') { validator = rule.validator message = rule.message }
if (validator && !validator(value)) { return { valid: false, message } } }
return { valid: true } }
return new Proxy(initialData, { set(target, key, value, receiver) { const { valid, message } = validate(key, value)
if (!valid) { throw new TypeError(message) }
return Reflect.set(target, key, value, receiver) },
get(target, key, receiver) { return Reflect.get(target, key, receiver) }, })}
// 使用示例const user = createValidatedObject({ name: [ 'string', { validator: Validators.length(2, 20), message: '姓名长度 2-20' }, ], age: [ 'number', { validator: Validators.range(0, 150), message: '年龄 0-150' }, ], email: 'email', role: { validator: Validators.oneOf('admin', 'user'), message: '角色无效' },})
user.name = '张三' // OKuser.age = 25 // OKuser.email = 'test@example.com' // OKuser.role = 'admin' // OK
// user.age = -1; // TypeError: 年龄 0-150// user.email = 'invalid'; // TypeError: email 必须是 email 类型案例3:API 客户端代理#
创建一个自动发起请求的 API 客户端:
function createAPIClient(baseURL, options = {}) { const { headers = {}, timeout = 5000 } = options
async function request(method, path, data) { const url = `${baseURL}${path}` const config = { method, headers: { 'Content-Type': 'application/json', ...headers, }, }
if (data && ['POST', 'PUT', 'PATCH'].includes(method)) { config.body = JSON.stringify(data) }
const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) config.signal = controller.signal
try { const response = await fetch(url, config) clearTimeout(timeoutId)
if (!response.ok) { throw new Error(`HTTP ${response.status}`) }
return await response.json() } catch (error) { clearTimeout(timeoutId) throw error } }
// 创建链式代理 function createProxy(path = '') { return new Proxy(() => {}, { get(target, property) { if (property === 'get') { return (params) => { const query = params ? '?' + new URLSearchParams(params).toString() : '' return request('GET', path + query) } } if (property === 'post') { return (data) => request('POST', path, data) } if (property === 'put') { return (data) => request('PUT', path, data) } if (property === 'delete') { return () => request('DELETE', path) }
// 继续链式 return createProxy(`${path}/${property}`) },
apply(target, thisArg, args) { // 处理动态路径参数,如 api.users(123) return createProxy(`${path}/${args[0]}`) }, }) }
return createProxy()}
// 使用示例const api = createAPIClient('https://api.example.com')
// GET /usersapi.users.get()
// GET /users?page=1&limit=10api.users.get({ page: 1, limit: 10 })
// GET /users/123api.users(123).get()
// POST /usersapi.users.post({ name: '张三', age: 25 })
// PUT /users/123api.users(123).put({ name: '李四' })
// DELETE /users/123api.users(123).delete()
// 深层路径 GET /users/123/posts/456/commentsapi.users(123).posts(456).comments.get()案例4:属性访问记录器#
记录对象的所有访问操作:
function createAccessLogger(target, options = {}) { const { name = 'Object', logger = console } = options const accessLog = []
const proxy = new Proxy(target, { get(target, property, receiver) { const entry = { type: 'get', property: String(property), timestamp: Date.now(), } accessLog.push(entry) logger.log(`[GET] ${name}.${String(property)}`)
const value = Reflect.get(target, property, receiver)
// 递归代理嵌套对象 if (typeof value === 'object' && value !== null) { return createAccessLogger(value, { name: `${name}.${String(property)}`, logger, }) }
return value },
set(target, property, value, receiver) { const oldValue = target[property] const entry = { type: 'set', property: String(property), oldValue, newValue: value, timestamp: Date.now(), } accessLog.push(entry) logger.log(`[SET] ${name}.${String(property)} = ${JSON.stringify(value)}`)
return Reflect.set(target, property, value, receiver) },
deleteProperty(target, property) { const entry = { type: 'delete', property: String(property), timestamp: Date.now(), } accessLog.push(entry) logger.log(`[DELETE] ${name}.${String(property)}`)
return Reflect.deleteProperty(target, property) }, })
// 添加获取日志的方法 proxy.__getAccessLog = () => [...accessLog]
return proxy}
// 使用示例const data = createAccessLogger( { user: { name: '张三', age: 25 }, count: 0, }, { name: 'data' })
data.count // [GET] data.countdata.user.name // [GET] data.user, [GET] data.user.namedata.count = 1 // [SET] data.count = 1delete data.count // [DELETE] data.count
console.log(data.__getAccessLog())案例5:不可变数据#
创建真正不可变的数据结构:
function deepFreeze(obj) { const handler = { 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 set prototype of immutable object') },
get(target, property, receiver) { const value = Reflect.get(target, property, receiver)
if (typeof value === 'object' && value !== null) { return deepFreeze(value) }
return value }, }
return new Proxy(obj, handler)}
// 使用示例const config = deepFreeze({ api: { baseUrl: 'https://api.example.com', timeout: 5000, }, features: ['a', 'b', 'c'],})
// config.api.baseUrl = 'xxx'; // TypeError// config.features.push('d'); // TypeError// delete config.api; // TypeError小结#
元编程的核心应用场景:
| 场景 | 技术要点 |
|---|---|
| 响应式系统 | get/set 拦截 + 依赖收集 |
| 数据验证 | set 拦截 + 验证规则 |
| API 客户端 | get 拦截 + 链式代理 |
| 访问日志 | get/set/delete 拦截 |
| 不可变数据 | 拦截所有修改操作 |
Proxy 和 Reflect 是 JavaScript 元编程的基石,掌握它们可以实现很多”魔法”般的功能。