14JavaScript高级程序设计(第4版)

元编程

高级特性

ProxyReflectSymbol

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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);   // false

14.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);  // 3

14.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客户端。

实现一个完整的响应式数据系统。


14/14元编程
0%