Skip to content

元编程实战案例

结合 Proxy 和 Reflect,我们可以实现很多强大的功能。本文通过几个完整的实战案例来展示元编程的威力。

案例1:深度响应式系统#

实现一个类似 Vue 3 的响应式系统:

// 存储依赖关系
const targetMap = new WeakMap()
// 当前正在执行的 effect
let 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),
}
// 创建 Schema
function 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 = '张三' // OK
user.age = 25 // OK
user.email = 'test@example.com' // OK
user.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 /users
api.users.get()
// GET /users?page=1&limit=10
api.users.get({ page: 1, limit: 10 })
// GET /users/123
api.users(123).get()
// POST /users
api.users.post({ name: '张三', age: 25 })
// PUT /users/123
api.users(123).put({ name: '李四' })
// DELETE /users/123
api.users(123).delete()
// 深层路径 GET /users/123/posts/456/comments
api.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.count
data.user.name // [GET] data.user, [GET] data.user.name
data.count = 1 // [SET] data.count = 1
delete 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 元编程的基石,掌握它们可以实现很多”魔法”般的功能。