第14章:元编程(Metaprogramming)
一、导读
1.1 本章学习目标
第14章介绍JavaScript的元编程特性。本章核心目标:
- 理解Symbol:唯一的标识符
- 掌握Proxy:创建代理对象
- 学会使用Reflect:默认操作
- 了解WeakRef和FinalizationRegistry
- 理解元编程的应用场景
1.2 为什么本章如此重要
元编程允许你拦截和修改语言的基本操作。Proxy是Vue 3响应式系统的核心,Symbol用于定义特殊的对象行为。这些高级特性让你能够创建更强大的抽象。
1.3 核心问题引导
Symbol解决了什么问题?
Proxy可以拦截哪些操作?
Reflect和Proxy有什么关系?
元编程的性能影响如何?
二、核心概念详解
14.1 Symbol
javascript
// 创建Symbol
let sym1 = Symbol("description");
let sym2 = Symbol("description");
console.log(sym1 === sym2); // false(每个Symbol都是唯一的)
// Symbol作为属性键
let obj = {
[Symbol("id")]: 123,
name: "Alice"
};
console.log(Object.keys(obj)); // ["name"]
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]
// 内置Symbol
class Collection {
constructor(...items) {
this.items = items;
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.items.length) {
return {value: this.items[index++], done: false};
}
return {done: true};
}
};
}
}
let coll = new Collection(1, 2, 3);
for (let item of coll) {
console.log(item);
}
// Symbol.toPrimitive
let obj2 = {
[Symbol.toPrimitive](hint) {
if (hint === "number") return 42;
if (hint === "string") return "forty-two";
return true;
}
};
console.log(+obj2); // 42
console.log(String(obj2)); // "forty-two"
console.log(obj2 + ""); // "true"
// Symbol.hasInstance
class EvenNumber {
static [Symbol.hasInstance](value) {
return typeof value === "number" && value % 2 === 0;
}
}
console.log(4 instanceof EvenNumber); // true
console.log(5 instanceof EvenNumber); // false14.2 Proxy
javascript
// 创建Proxy
let handler = {
get(target, prop) {
console.log(`Getting ${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting ${prop} = ${value}`);
if (prop === "age" && (value < 0 || value > 150)) {
throw new Error("Invalid age");
}
target[prop] = value;
return true;
},
has(target, prop) {
console.log(`Checking if ${prop} exists`);
return prop in target;
},
deleteProperty(target, prop) {
console.log(`Deleting ${prop}`);
delete target[prop];
return true;
}
};
let person = new Proxy({}, handler);
person.name = "Alice"; // "Setting name = Alice"
person.age = 25; // "Setting age = 25"
console.log(person.name); // "Getting name", "Alice"
console.log("age" in person); // "Checking if age exists", true
// 数据验证
let validator = {
set(target, prop, value) {
const schema = {
name: {type: "string", required: true},
age: {type: "number", min: 0, max: 150},
email: {type: "string", pattern: /^[^\s@]+@[^\s@]+$/}
};
const rule = schema[prop];
if (rule) {
if (typeof value !== rule.type) {
throw new TypeError(`${prop} must be ${rule.type}`);
}
if (rule.min !== undefined && value < rule.min) {
throw new Error(`${prop} must be >= ${rule.min}`);
}
if (rule.pattern && !rule.pattern.test(value)) {
throw new Error(`Invalid ${prop}`);
}
}
target[prop] = value;
return true;
}
};
let user = new Proxy({}, validator);
user.name = "Alice"; // OK
user.age = 25; // OK
// user.age = -1; // Error
// user.email = "invalid"; // Error
// 日志记录
function createLogger(target) {
return new Proxy(target, {
get(target, prop) {
const value = target[prop];
if (typeof value === 'function') {
return function(...args) {
console.log(`Calling ${prop}(${args.join(', ')})`);
const result = value.apply(target, args);
console.log(` => ${result}`);
return result;
};
}
return value;
}
});
}14.3 Reflect
javascript
// Reflect提供默认操作
let target = {x: 1, y: 2};
console.log(Reflect.get(target, "x")); // 1
Reflect.set(target, "z", 3);
console.log(target.z); // 3
console.log(Reflect.has(target, "x")); // true
console.log(Reflect.ownKeys(target)); // ["x", "y", "z"]
// Reflect与Proxy配合
let loggedObj = new Proxy({}, {
get(target, prop, receiver) {
console.log(`get ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`set ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
});
// Reflect.construct
function Person(name) {
this.name = name;
}
let args = ["Alice"];
let person = Reflect.construct(Person, args);
console.log(person.name); // "Alice"
// Reflect.apply
let max = Reflect.apply(Math.max, null, [1, 2, 3]);
console.log(max); // 314.4 WeakRef和FinalizationRegistry
javascript
// WeakRef:弱引用
let obj = {data: "large object"};
let weakRef = new WeakRef(obj);
console.log(weakRef.deref()?.data); // "large object"
// obj被垃圾回收后,weakRef.deref()返回undefined
// FinalizationRegistry:注册清理回调
let registry = new FinalizationRegistry((key) => {
console.log(`Object with key ${key} was garbage collected`);
});
function createResource(id) {
let resource = {id, data: "resource data"};
registry.register(resource, id);
return resource;
}
let resource = createResource(1);
// 当resource被垃圾回收时,回调会被调用三、实战案例
3.1 响应式数据系统
javascript
// 简单的响应式系统(类似Vue 3)
function reactive(target) {
const subscribers = new Map();
return new Proxy(target, {
get(target, prop, receiver) {
// 追踪依赖
if (currentEffect) {
if (!subscribers.has(prop)) {
subscribers.set(prop, new Set());
}
subscribers.get(prop).add(currentEffect);
}
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'object' && value !== null) {
return reactive(value);
}
return value;
},
set(target, prop, value, receiver) {
const oldValue = target[prop];
const result = Reflect.set(target, prop, value, receiver);
if (oldValue !== value) {
trigger(prop);
}
return result;
}
});
function trigger(prop) {
const effects = subscribers.get(prop);
if (effects) {
effects.forEach(effect => effect());
}
}
}
let currentEffect = null;
function effect(fn) {
currentEffect = fn;
fn();
currentEffect = null;
}
// 使用
let state = reactive({count: 0, name: "Alice"});
effect(() => {
console.log(`Count changed: ${state.count}`);
});
state.count = 1; // "Count changed: 1"
state.count = 2; // "Count changed: 2"
state.name = "Bob"; // 不触发(没有依赖name的effect)四、本章小结
第14章介绍了JavaScript的元编程:Symbol(唯一标识符)、Proxy(代理对象)、Reflect(默认操作)、WeakRef(弱引用)。这些高级特性让你能够拦截和修改语言的基本行为。
五、思考与练习
思考题
Proxy的性能影响如何?在什么场景下应该避免使用?
Symbol.iterator和生成器有什么关系?
Reflect为什么存在?它和直接操作有什么区别?
WeakRef的垃圾回收时机是不确定的,这带来什么问题?
编程练习
使用Proxy实现一个不可变对象。
使用Proxy实现一个带自动重试的API客户端。
实现一个完整的响应式数据系统。