Kodokon kodokon.com

Proxy, Reflect และเมตาโปรแกรมมิง

ดักจับการดำเนินการพื้นฐานของภาษาด้วย trap ของ Proxy และมอบหมายพฤติกรรมเริ่มต้นอย่างสะอาดด้วย Reflect

10 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

Proxy ห่ออ็อบเจกต์เป้าหมายและดักจับ การดำเนินการภายใน ของภาษาผ่าน trap สิบสามตัว: get, set, has (โอเปอเรเตอร์ in), deleteProperty, ownKeys, apply, construct และอื่น ๆ อ็อบเจกต์ Reflect เปิดเผยการดำเนินการเดียวกันในรูปฟังก์ชัน โดยมี signature เหมือนกับ trap มันคือรากฐานของ reactivity ใน Vue 3 ของ mock แบบไดนามิก และของ API แบบ ORM

JAVASCRIPT
const target = { name: "Ada" };

const traced = new Proxy(target, {
  get(obj, prop, receiver) {
    console.log("get " + String(prop));
    return Reflect.get(obj, prop, receiver);
  }
});

traced.name; // get name
get trap ขั้นต่ำที่มอบหมายให้ Reflect

ทำไมต้องใช้ Reflect.get(obj, prop, receiver) แทน obj[prop]? เพราะ receiver ถ้าพร็อพเพอร์ตี้เป็น accessor (getter) Reflect.get จะเรียกมันโดยให้ this ชี้ไปที่ receiver - proxy หรืออ็อบเจกต์ที่สืบทอดจากมัน - ในขณะที่ obj[prop] จะตั้ง this เป็นเป้าหมายดิบ ถ้าไม่มี receiver getter ที่อ่าน this จะพังทันทีที่อ็อบเจกต์หนึ่งสืบทอดจาก proxy

JAVASCRIPT
const base = {
  get label() {
    return this.name;
  }
};

const proxy = new Proxy(base, {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver);
  }
});

const child = Object.create(proxy);
child.name = "Ada";
console.log(child.label); // "Ada" thanks to the receiver
receiver รักษา this ไว้ใน getter ที่สืบทอดมา

กรณีใช้งานจริงจัง: การตรวจสอบ ตอนเขียนค่า พร็อพเพอร์ตี้เสมือนที่คำนวณสด ๆ, logging, การควบคุมการเข้าถึง (ซ่อนคีย์ที่ขึ้นต้นด้วย _ ผ่าน has และ ownKeys) หรืออ็อบเจกต์ที่มีพฤติกรรมเริ่มต้น สังเกตว่า Reflect.set คืนค่าบูลีนแทนที่จะโยน error ในโหมด strict trap ของคุณจึงต้อง ส่งต่อค่าที่คืนนั้น

JAVASCRIPT
function validated(target) {
  return new Proxy(target, {
    set(obj, prop, value, receiver) {
      if (prop === "age" && !Number.isInteger(value)) {
        throw new TypeError("age must be an integer");
      }
      return Reflect.set(obj, prop, value, receiver);
    }
  });
}

const user = validated({ age: 30 });
user.age = 31;       // ok
// user.age = "old"; // TypeError
การตรวจสอบตอนเขียนค่าอย่างโปร่งใส

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. ใน trap get ทำไมจึงควรใช้ Reflect.get(target, prop, receiver) แทน target[prop]?
    • มันเร็วกว่าเพราะ Reflect ข้าม prototype
    • เพื่อให้ getter รันด้วย this ที่ถูกต้อง (receiver) แทนที่จะเป็นเป้าหมายดิบ
    • เพราะ target[prop] จะทริกเกอร์ trap ซ้ำและทำให้เกิดการเรียกซ้ำไม่รู้จบ
    • ทั้งสองรูปแบบเทียบเท่ากันอย่างเคร่งครัด
  2. trap ตัวใดดักจับนิพจน์ "id" in obj?
    • get
    • has
    • ownKeys
    • getOwnPropertyDescriptor
  3. trap get คืนค่า 42 สำหรับพร็อพเพอร์ตี้ของเป้าหมายที่มีค่าเป็น 1 ซึ่งประกาศเป็น non-configurable และ non-writable จะเกิดอะไรขึ้น?
    • proxy คืนค่า 42 เพราะ trap มีลำดับความสำคัญเสมอ
    • เอนจินคืนค่า 1 อย่างเงียบ ๆ
    • เอนจินโยน TypeError เพราะละเมิด invariant
    • พฤติกรรมขึ้นอยู่กับเอนจิน JavaScript