วัตถุ Reflect ทำอะไรใน JavaScript?


91

ฉันเห็นต้นขั้วว่างบน MDN เมื่อสักครู่ที่ผ่านมาสำหรับReflectวัตถุในจาวาสคริปต์ แต่ฉันไม่สามารถหาอะไรใน Google ได้ตลอดชีวิต วันนี้ฉันพบhttp://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-objectและฟังดูคล้ายกับวัตถุ Proxy นอกเหนือจากขอบเขตและฟังก์ชันตัวโหลด

โดยทั่วไปฉันไม่รู้ว่าหน้านี้ฉันพบเพียงอธิบายวิธีใช้ Reflect หรือไม่หรือไม่เข้าใจถ้อยคำของมัน ใครช่วยอธิบายให้ฉันเข้าใจโดยทั่วไปว่ามีวิธีการReflectทำอย่างไร

ตัวอย่างเช่นในหน้าที่ฉันพบบอกว่าการโทรReflect.apply ( target, thisArgument, argumentsList ) จะ "ส่งคืนผลลัพธ์ของการเรียกเมธอดภายใน [[Call]] ของเป้าหมายด้วยอาร์กิวเมนต์ thisArgument และ args" แต่มันต่างจากการโทรtarget.apply(thisArgument, argumentsList)อย่างไร?

อัปเดต:

ขอบคุณ @Blue ฉันพบหน้านี้ในวิกิ http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect ซึ่งสิ่งที่ฉันรู้ดีที่สุดกล่าวว่าวัตถุสะท้อนให้เวอร์ชันวิธีการทั้งหมด การดำเนินการที่พร็อกซีสามารถดักจับได้เพื่อให้การส่งต่อง่ายขึ้น แต่มันดูแปลก ๆ สำหรับฉันเพราะฉันไม่เห็นว่ามันจำเป็นทั้งหมดแค่ไหน แต่ดูเหมือนว่าจะทำได้มากกว่านั้นเล็กน้อยโดยเฉพาะพาร์ที่ระบุdouble-liftingว่าชี้ไปที่ข้อมูลจำเพาะ / พร็อกซีเก่า


1
ข้อมูลจำเพาะระบุว่า "วัตถุสะท้อนแสงเป็นวัตถุธรรมดาชิ้นเดียว" สำหรับความเข้าใจของฉันReflectเป็นเพียงภาชนะสำหรับRealmและLoaderวัตถุ แต่ฉันไม่รู้ว่าสิ่งหลังทำเช่นกัน
simonzack

ขอบคุณ :) ดูเหมือนว่าจากหน้าที่ฉันเชื่อมโยงไป (ไม่รู้ว่ามันถูกต้องแค่ไหน) ว่าแต่ละ Realm เป็น "บริบทของสคริปต์ java" ของตัวเองและตัวโหลดจะโหลด Realms เช่นโมดูลหรืออะไรบางอย่างขึ้นอยู่กับความคล้ายคลึงกันระหว่างการสะท้อน และพร็อกซีและความจริงที่ว่าพร็อกซีประเภท "โอเวอร์โหลด" ในตัวสามารถทำงานได้Reflect.LoaderและReflect.Realmมีบางอย่างที่เกี่ยวข้องกับการทำงานของโมดูลโอเวอร์โหลด?
Jim Jones

1
ดูเหมือนว่ามันเป็น 'ระดับคงที่ (เช่น JSON) ด้วยวิธีการแบบคงที่: isExtensible, ownKeysฯลฯ ใน ES 6 กับการเรียนที่เกิดขึ้นจริงนี้จะเป็นประโยชน์ในการหาข้อมูลเพิ่มเติมเกี่ยวกับชั้นเรียน ( targetใน16.1.2ฉันคิด)
Rudie

คุณเคยเห็นgithub.com/tvcutsem/harmony-reflect/wikiหรือไม่
kangax

คำตอบ:


131

UPDATE 2015 ในฐานะที่เป็นแหลมออกโดย7 's คำตอบตอนนี้ที่ ES6 (ECMAScript 2015) ได้รับการสรุปเอกสารที่เหมาะสมมากขึ้นอยู่ในขณะนี้:


คำตอบเดิม (สำหรับความเข้าใจ (ประวัติศาสตร์) และตัวอย่างเพิ่มเติม) :

Reflection proposalดูเหมือนว่าจะมีความคืบหน้าไปยังร่าง ECMAScript 6 จำเพาะ ขณะนี้เอกสารนี้สรุปReflectวิธีการของ -object และระบุเฉพาะสิ่งต่อไปนี้เกี่ยวกับReflect-object เท่านั้น:

วัตถุสะท้อนแสงเป็นวัตถุธรรมดาชิ้นเดียว

ค่าของสล็อตภายใน [[Prototype]] ของวัตถุ Reflect คืออ็อบเจกต์ต้นแบบในตัวมาตรฐาน (19.1.3)

วัตถุสะท้อนไม่ใช่วัตถุฟังก์ชัน ไม่มี [[สร้าง]] วิธีการภายใน; ไม่สามารถใช้วัตถุ Reflect เป็นตัวสร้างร่วมกับตัวดำเนินการใหม่ได้ นอกจากนี้วัตถุ Reflect ยังไม่มีวิธีการภายใน [[Call]] ไม่สามารถเรียกใช้วัตถุสะท้อนเป็นฟังก์ชันได้

อย่างไรก็ตามมีคำอธิบายสั้น ๆ เกี่ยวกับวัตถุประสงค์ในES Harmony :

โมดูล“ @reflect” รองรับวัตถุประสงค์หลายประการ:
  • ตอนนี้เรามีโมดูลแล้วโมดูล“ @reflect” เป็นสถานที่ที่เป็นธรรมชาติมากขึ้นสำหรับวิธีการสะท้อนหลายอย่างที่กำหนดไว้ก่อนหน้านี้ใน Object เพื่อวัตถุประสงค์ในการทำงานร่วมกันได้แบบย้อนกลับไม่น่าเป็นไปได้ที่วิธีการคงที่บน Object จะหายไป อย่างไรก็ตามควรเพิ่มเมธอดใหม่ในโมดูล“ @reflect” มากกว่าในตัวสร้างวัตถุ
  • บ้านที่เป็นธรรมชาติสำหรับผู้รับมอบฉันทะโดยหลีกเลี่ยงความจำเป็นในการผูกพร็อกซีทั่วโลก
  • วิธีการส่วนใหญ่ในโมดูลนี้จะแมปแบบหนึ่งต่อหนึ่งบนกับดักพร็อกซี ตัวจัดการพร็อกซีจำเป็นต้องใช้วิธีการเหล่านี้เพื่อส่งต่อการดำเนินการดังที่แสดงด้านล่าง



ดังนั้นReflectอ็อบเจ็กต์จึงจัดเตรียมฟังก์ชันยูทิลิตี้จำนวนหนึ่งซึ่งหลายฟังก์ชันดูเหมือนจะทับซ้อนกับเมธอด ES5 ที่กำหนดไว้บนโกลบอลอ็อบเจ็กต์

อย่างไรก็ตามนั่นไม่ได้อธิบายว่าปัญหาที่มีอยู่ซึ่งตั้งใจจะแก้ไขหรือเพิ่มฟังก์ชันอะไร ฉันสงสัยว่าสิ่งนี้อาจถูก shimmed และแท้จริงแล้วลิงก์ข้อกำหนดความสามัคคีข้างต้นไปยังไฟล์ 'ไม่ใช่กฎเกณฑ์การดำเนินงานโดยประมาณของวิธีการเหล่านี้'

การตรวจสอบรหัสนั้นสามารถให้แนวคิด (เพิ่มเติม) เกี่ยวกับการใช้งานได้ แต่โชคดีที่ยังมีวิกิที่อธิบายเหตุผลหลายประการว่าทำไมวัตถุ Reflect จึงมีประโยชน์ :
(ฉันได้คัดลอก (และจัดรูปแบบ) ข้อความต่อไปนี้เพื่อใช้อ้างอิงในอนาคตจากนั้น แหล่งที่มาเนื่องจากเป็นตัวอย่างเดียวที่ฉันสามารถหาได้นอกจากนั้นยังมีเหตุผลมีคำอธิบายที่ดีอยู่แล้วและสัมผัสกับapplyตัวอย่างของคำถาม)


ค่าส่งคืนที่มีประโยชน์มากขึ้น

การดำเนินงานจำนวนมากในการReflectมีความคล้ายคลึงกับการดำเนินงานที่กำหนดไว้ใน ES5 Objectเช่นและReflect.getOwnPropertyDescriptor Reflect.definePropertyอย่างไรก็ตามในขณะที่Object.defineProperty(obj, name, desc)จะส่งคืนobjเมื่อคุณสมบัติถูกกำหนดสำเร็จหรือโยนเป็นTypeErrorอย่างอื่นระบุReflect.defineProperty(obj, name, desc)เพียงแค่ส่งคืนบูลีนที่ระบุว่าคุณสมบัติถูกกำหนดสำเร็จหรือไม่ สิ่งนี้ช่วยให้คุณสามารถ refactor รหัสนี้:

try {
  Object.defineProperty(obj, name, desc);
  // property defined successfully
} catch (e) {
  // possible failure (and might accidentally catch the wrong exception)
}

สำหรับสิ่งนี้:

if (Reflect.defineProperty(obj, name, desc)) {
  // success
} else {
  // failure
}

วิธีอื่นที่ส่งคืนสถานะความสำเร็จของบูลีน ได้แก่Reflect.set(เพื่ออัพเดตคุณสมบัติ) Reflect.deleteProperty(เพื่อลบคุณสมบัติ) Reflect.preventExtensions(เพื่อทำให้อ็อบเจ็กต์ไม่สามารถขยายได้) และReflect.setPrototypeOf(เพื่ออัพเดตลิงก์ต้นแบบของอ็อบเจ็กต์)


การดำเนินงานชั้นหนึ่ง

ใน ES5 วิธีตรวจสอบว่าวัตถุobjกำหนดหรือสืบทอดชื่อคุณสมบัติบางอย่างคือการเขียน(name in obj)กำหนดหรือสืบทอดชื่อคุณสมบัติบางอย่างคือการเขียนdelete obj[name]ในทำนองเดียวกันการลบคุณสมบัติอย่างใดอย่างหนึ่งใช้ แม้ว่าไวยากรณ์เฉพาะจะดีและสั้น แต่ก็หมายความว่าคุณต้องรวมการดำเนินการเหล่านี้ไว้ในฟังก์ชันอย่างชัดเจนเมื่อคุณต้องการส่งผ่านการดำเนินการเป็นค่าชั้นหนึ่ง

ด้วยReflectการดำเนินการเหล่านี้จึงถูกกำหนดให้เป็นฟังก์ชันระดับเฟิร์สคลาส:
Reflect.has(obj, name)เทียบเท่ากับฟังก์ชัน(name in obj)และReflect.deleteProperty(obj, name)เป็นฟังก์ชันที่ทำเช่นเดียวกับdelete obj[name].


แอปพลิเคชั่นฟังก์ชั่นที่เชื่อถือได้มากขึ้น

ใน ES5 เมื่อต้องการเรียกใช้ฟังก์ชันที่fมีอาร์กิวเมนต์จำนวนตัวแปรที่บรรจุเป็นอาร์เรย์argsและผูกthisค่าเข้ากับobjเราสามารถเขียน:

f.apply(obj, args)

อย่างไรก็ตามfอาจเป็นวัตถุที่กำหนดapplyวิธีการของตัวเองโดยเจตนาหรือไม่ตั้งใจ เมื่อคุณต้องการให้แน่ใจว่าapplyมีการเรียกใช้ฟังก์ชันในตัวโดยทั่วไปจะเขียนว่า:

Function.prototype.apply.call(f, obj, args)

ไม่เพียง แต่เป็นรายละเอียดเท่านั้น แต่ยังยากที่จะเข้าใจอย่างรวดเร็ว ด้วยReflectตอนนี้คุณสามารถเรียกใช้ฟังก์ชันที่เชื่อถือได้ด้วยวิธีที่สั้นและเข้าใจง่ายขึ้น:

Reflect.apply(f, obj, args)


ตัวสร้างตัวแปร - อาร์กิวเมนต์

สมมติว่าคุณต้องการเรียกใช้ฟังก์ชันตัวสร้างด้วยอาร์กิวเมนต์ที่เป็นตัวแปร ใน ES6 ต้องขอบคุณไวยากรณ์การแพร่กระจายใหม่ทำให้สามารถเขียนโค้ดเช่น:

var obj = new F(...args)

ใน ES5 สิ่งนี้ยากกว่าที่จะเขียนเนื่องจากสามารถใช้F.applyหรือF.callเพื่อเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์จำนวนตัวแปรเท่านั้น แต่ไม่มีF.constructฟังก์ชันสำหรับnewฟังก์ชันที่มีจำนวนอาร์กิวเมนต์ตัวแปร ด้วยReflectตอนนี้เราสามารถเขียนใน ES5:

var obj = Reflect.construct(F, args)


พฤติกรรมการส่งต่อเริ่มต้นสำหรับกับดักพร็อกซี

เมื่อใช้Proxyอ็อบเจกต์เพื่อห่ออ็อบเจ็กต์ที่มีอยู่เป็นเรื่องปกติมากที่จะขัดขวางการดำเนินการทำอะไรบางอย่างแล้ว "ทำสิ่งที่เป็นค่าเริ่มต้น" ซึ่งโดยทั่วไปแล้วจะใช้การดำเนินการที่ถูกดักจับกับวัตถุที่ถูกห่อ ตัวอย่างเช่นสมมติว่าฉันต้องการเพียงแค่บันทึกคุณสมบัติทั้งหมดที่เข้าถึงวัตถุobj:

var loggedObj = new Proxy(obj, {
  get: function(target, name) {
    console.log("get", target, name);
    // now do the default thing
  }
});

ReflectและProxyAPIs ได้รับการออกแบบควบคู่เช่นว่าสำหรับแต่ละProxyดักมีอยู่วิธีการที่สอดคล้องกันในการReflectที่ว่า "จะเป็นสิ่งเริ่มต้น" ดังนั้นเมื่อใดก็ตามที่คุณพบว่าตัวเองต้องการ "ทำค่าเริ่มต้น" ภายในตัวจัดการพร็อกซีสิ่งที่ต้องทำคือการเรียกใช้เมธอดที่เกี่ยวข้องในReflectวัตถุเสมอ:

var loggedObj = new Proxy(obj, {
  get: function(target, name) {
    console.log("get", target, name);
    return Reflect.get(target, name);
  }
});

Reflectรับประกันประเภทการส่งคืนของวิธีการที่เข้ากันได้กับประเภทการส่งคืนของProxyกับดัก


ควบคุมการเชื่อมโยงนี้ของ accessors

ใน ES5 การเข้าถึงคุณสมบัติทั่วไปหรือการอัปเดตคุณสมบัตินั้นค่อนข้างง่าย ตัวอย่างเช่น:

var name = ... // get property name as a string
obj[name] // generic property lookup
obj[name] = value // generic property update

Reflect.getและReflect.setวิธีการช่วยให้คุณทำในสิ่งเดียวกัน แต่ยังยอมรับเป็นอาร์กิวเมนต์ตัวเลือกสุดท้ายreceiverพารามิเตอร์ที่ช่วยให้คุณสามารถตั้งค่าอย่างชัดเจนthis-binding เมื่อทรัพย์สินที่คุณได้รับ / ชุดเป็นเข้าถึง:

var name = ... // get property name as a string
Reflect.get(obj, name, wrapper) // if obj[name] is an accessor, it gets run with `this === wrapper`
Reflect.set(obj, name, value, wrapper)

สิ่งนี้มีประโยชน์ในบางครั้งเมื่อคุณกำลังตัดobjและคุณต้องการให้การส่งด้วยตนเองใด ๆ ภายใน accessor ได้รับการกำหนดเส้นทางใหม่ไปยัง Wrapper ของคุณเช่นหากobjกำหนดเป็น:

var obj = {
  get foo() { return this.bar(); },
  bar: function() { ... }
}

Calling Reflect.get(obj, "foo", wrapper)จะทำให้เกิดการเรียกร้องให้ได้รับการเปลี่ยนเส้นทางไปยังthis.bar()wrapper


หลีกเลี่ยงมรดก __proto__

ในบางเบราว์เซอร์__proto__ถูกกำหนดให้เป็นคุณสมบัติพิเศษที่ให้การเข้าถึงต้นแบบของวัตถุ ES5 กำหนดวิธีการใหม่ในการObject.getPrototypeOf(obj)สืบค้นต้นแบบเป็นมาตรฐาน Reflect.getPrototypeOf(obj)ทำเหมือนกันทุกประการยกเว้นว่าจะReflectกำหนดสิ่งที่เกี่ยวข้องReflect.setPrototypeOf(obj, newProto)กับการตั้งค่าต้นแบบของวัตถุด้วย นี่เป็นวิธีใหม่ที่สอดคล้องกับ ES6 ในการอัปเดตต้นแบบของวัตถุ
โปรดทราบว่า: setPrototypeOf ยังมีอยู่Object (ตามที่ระบุไว้อย่างถูกต้องตามความคิดเห็นของKnu )!


แก้ไข:
หมายเหตุด้านข้าง (แสดงความคิดเห็นต่อ Q): มีคำตอบสั้น ๆ และเรียบง่ายเกี่ยวกับ 'Q: โมดูล ES6 เทียบกับการนำเข้า HTML'ที่อธิบายRealmsและLoaderวัตถุ

คำอธิบายอื่นมีให้โดยลิงค์นี้ :

วัตถุขอบเขตเป็นนามธรรมแนวคิดของสภาพแวดล้อมโลกที่แตกต่างกันโดยมีอ็อบเจ็กต์ส่วนกลางของตัวเองสำเนาของไลบรารีมาตรฐานและ "intrinsics" (อ็อบเจ็กต์มาตรฐานที่ไม่ถูกผูกไว้กับตัวแปรส่วนกลางเช่นค่าเริ่มต้นของ Object.prototype)

เว็บที่ขยายได้ : เทียบเท่าแบบไดนามิกของแหล่งกำเนิดเดียวกันที่ <iframe>ไม่มี DOM

ที่ควรค่าแก่การกล่าวขวัญ: ทั้งหมดนี้ยังอยู่ในร่างนี่ไม่ใช่คุณสมบัติที่สลักด้วยหิน มันคือ ES6 ดังนั้นโปรดคำนึงถึงความเข้ากันได้ของเบราว์เซอร์!

หวังว่านี่จะช่วยได้!


@Spencer Killen: อืม .. คำตอบนี้ชี้ความคิดของคุณไปในทิศทางที่ถูกต้องหรือไม่และอธิบายว่าสิ่งนี้เกี่ยวข้องกับความแตกต่างระหว่างReflect.applyและtarget.applyอย่างไร? หรือฉันควรเพิ่มอะไรก่อนที่ค่าหัวจะสิ้นสุดลง?
GitaarLAB

2
setPrototypeOfยังมีอยู่บนObject.
Knu

1
โปรดทราบว่าการใช้Reflect.getเป็นการนำไปใช้งานเริ่มต้นสำหรับ proxy get จะไม่ได้ผลดีหากคุณกำลังพร็อกซีอ็อบเจกต์ที่มีคุณสมบัติต้นแบบ มันแค่บ่นว่าใช้ไม่ได้ อย่างไรก็ตามหากคุณใช้แทนReflect.get(target, property)โดยไม่ผ่านreceiverมันก็ใช้งานได้
CMCDragonkai

การทดสอบของฉันที่คุณเข้าถึงคุณสมบัติผ่านพร็อกซีเสมอส่งผลให้เกิดสถานการณ์ที่targetเป้าหมายเดิมที่พร็อกซีห่อหุ้มในขณะที่receiverเป็นพร็อกซีเอง แต่อีกครั้งสิ่งนี้อาจแตกต่างออกไปหากคุณจัดการเพื่อเข้าถึงคุณสมบัติต่างออกไป
CMCDragonkai

นี่คือคำตอบที่ยอดเยี่ยม! ขอบคุณ
rpivovar

5

ไปตามเอกสารฉบับร่างที่พบในวิกิ

http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts

เราได้บรรทัดเกี่ยวกับ "วัตถุธรรมดาชิ้นเดียว" ซึ่งอธิบายไว้ในร่าง นอกจากนี้ยังมีคำจำกัดความของฟังก์ชัน

wiki ควรมีความน่าเชื่อถือเนื่องจากคุณสามารถหาลิงค์ไปได้จากเว็บไซต์ emcascript

http://www.ecmascript.org/dev.php

ฉันพบลิงค์แรกโดย google และไม่มีโชคในการค้นหาโดยค้นหา wiki โดยตรง


ขอบคุณขั้นตอนที่ดำเนินการเมื่อแต่ละวิธีถูกเรียกในข้อมูลจำเพาะอย่างเป็นทางการดูเหมือนจะค่อนข้างเหมือนกับในลิงค์ของฉัน แต่ฉันยังไม่สามารถเข้าใจได้ว่าแต่ละวิธีทำอย่างไรเมื่อถูกเรียกตัวอย่างเช่นภายใต้ Reflect ใช้ขั้นตอนที่แสดงในรายการ 4 ดำเนินการการดำเนินการบทคัดย่อของ PrepForTailCall 5. ส่งคืนผลลัพธ์ของการเรียกเมธอดภายในของเป้าหมาย [[Call]] ด้วยอาร์กิวเมนต์ thisArgument และ args ------- นั่นหมายความว่าอย่างไร?
Jim Jones

การคาดเดาที่ดีที่สุดของฉันจากการดูคือการอ้างอิงการเรียกซ้ำของหางในขั้นตอนที่ 4 และขั้นตอนที่ 5 เป็นการอ้างอิงถึงฟังก์ชันต้นแบบ ดูเหมือนว่าแนวคิดทั่วไปคือการตรวจสอบว่าวิธีการใช้สามารถรันกับสิ่งที่นำไปใช้กับ (ขั้นตอนที่ 1-2) จัดการข้อผิดพลาด (ขั้นตอนที่ 3) จากนั้นเรียกใช้ฟังก์ชันใช้กำลังทำงานอยู่ (ขั้นตอนที่ 4-5) การคาดเดาที่ดีที่สุดของฉันจากการสแกนผ่านเอกสารคือประเด็นของโมดูลสะท้อนแสงคือเมื่อคุณทำฟังก์ชันที่ต้องใช้การไตร่ตรองวัตถุบางรูปแบบ การใช้การโทรอาจเป็นสาเหตุที่ "ไม่มี [[โทร]] วิธีการภายใน"
สีน้ำเงิน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.