Apa yang dilakukan objek Reflect di JavaScript?

91

Saya melihat sebuah rintisan kosong di MDN beberapa waktu yang lalu untuk Reflectobjek dalam javascript tetapi saya tidak bisa seumur hidup saya menemukan apa pun di Google. Hari ini saya menemukan http://people.mozilla.org/~jorendorff/es6-draft.html#sec-reflect-object ini dan kedengarannya mirip dengan objek Proxy selain dari fungsi realm dan loader.

Pada dasarnya, saya tidak tahu apakah halaman yang saya temukan ini hanya menjelaskan cara menerapkan Reflect atau jika saya tidak dapat memahami kata-katanya. Bisakah seseorang menjelaskan kepada saya secara umum apa metode Reflectlakukan?

Misalnya, pada halaman yang saya temukan mengatakan bahwa pemanggilan Reflect.apply ( target, thisArgument, argumentsList ) akan "Mengembalikan hasil pemanggilan metode internal [[Call]] dari target dengan argumen thisArgument dan args." tetapi apa bedanya dengan hanya menelepon target.apply(thisArgument, argumentsList)?

Memperbarui:

Terima kasih kepada @Blue, saya menemukan halaman ini di wiki http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api&s=reflect yang menurut pengetahuan saya terbaik mengatakan bahwa objek refleksi menyediakan versi metode dari semua tindakan yang dapat dijebak oleh proxy untuk mempermudah penerusan. Tapi itu nampaknya agak aneh bagi saya karena saya tidak melihat bagaimana itu sepenuhnya diperlukan. Tapi sepertinya melakukan lebih dari itu, terutama par yang mengatakan double-liftingtapi itu menunjuk ke spesifikasi proxy lama /

Jim Jones
sumber
1
Spesifikasi mengatakan "Objek Refleksi adalah objek biasa tunggal.", Menurut pemahaman saya Reflecthanyalah wadah untuk Realmdan Loaderobjek, tetapi saya juga tidak tahu apa yang dilakukan oleh objek.
simonzack
Terima kasih :), sepertinya dari halaman yang saya tautkan (tidak tahu seberapa sahnya) bahwa setiap Realm memiliki "konteks skrip java" -nya sendiri dan pemuat memuat Realms seperti modul atau sesuatu, berdasarkan kesamaan antara refleksi dan proxy dan fakta bahwa proxy semacam "overloads" built in fungsionalitas bisa Reflect.Loaderdan Reflect.Realmada hubungannya dengan fungsi modul overloading?
Jim Jones
1
Tampak seperti itu adalah 'kelas statis' (seperti JSON) dengan metode statis: isExtensible, ownKeysdll ES 6, dengan kelas yang sebenarnya, ini berguna untuk mengetahui lebih lanjut tentang kelas ( targetdi 16.1.2 saya pikir).
Rudie
Pernahkah Anda melihat github.com/tvcutsem/harmony-reflect/wiki ?
kangax

Jawaban:

131

UPDATE 2015: Seperti yang ditunjukkan oleh jawaban ke - 7 , sekarang ES6 (ECMAScript 2015) telah diselesaikan, dokumentasi yang lebih sesuai sekarang tersedia:


Jawaban asli (untuk pemahaman (historis) dan contoh tambahan) :

The Reflection proposaltampaknya telah berkembang ke Draft ECMAScript 6 Keterangan . Dokumen ini saat ini menguraikan metode Reflect-object dan hanya menyatakan hal berikut tentangReflect -object itu sendiri:

Objek Reflect adalah satu objek biasa.

Nilai slot internal [[Prototipe]] dari objek Reflect adalah objek prototipe Objek bawaan standar (19.1.3).

Objek Reflect bukanlah objek fungsi. Itu tidak memiliki metode internal [[Membangun]]; tidak mungkin menggunakan objek Reflect sebagai konstruktor dengan yang baru operator . Objek Reflect juga tidak memiliki metode internal [[Call]]; tidak mungkin untuk memanggil objek Reflect sebagai sebuah fungsi.

Namun demikian, ada penjelasan singkat tentang kegunaannya dalam ES Harmony :

Modul "@reflect" memiliki beberapa tujuan:
  • Sekarang kita memiliki modul, modul "@reflect" adalah tempat yang lebih alami untuk banyak metode refleksi yang sebelumnya didefinisikan pada Object. Untuk tujuan kompatibilitas mundur, kecil kemungkinan metode statis pada Object akan menghilang. Namun, metode baru mungkin harus ditambahkan ke modul "@reflect" daripada ke konstruktor Object.
  • Tempat alami untuk proxy, menghindari kebutuhan pengikatan Proxy global.
  • Sebagian besar metode dalam modul ini memetakan satu-ke-satu ke dalam perangkap Proxy. Penangan proxy memerlukan metode ini untuk meneruskan operasi dengan mudah, seperti yang ditunjukkan di bawah ini.



Sehingga Reflect objek tersebut menyediakan sejumlah fungsi utilitas, banyak di antaranya tampak tumpang tindih dengan metode ES5 yang ditentukan pada Objek global.

Namun, itu tidak benar-benar menjelaskan masalah apa yang ingin diselesaikan atau fungsionalitas apa yang ditambahkan. Saya curiga ini bisa digoyahkan dan memang, spesifikasi harmoni di atas terkait dengan 'non-normatif, perkiraan penerapan metode ini' .

Memeriksa kode itu dapat memberikan gagasan (lebih lanjut) tentang penggunaannya, tetapi untungnya ada juga wiki yang menguraikan sejumlah alasan mengapa objek Reflect berguna :
(Saya telah menyalin (dan memformat) teks berikut untuk referensi di masa mendatang dari itu sumber karena mereka adalah satu - satunya contoh yang dapat saya temukan. Selain itu, mereka masuk akal, sudah memiliki penjelasan yang baik dan menyentuh contoh pertanyaan apply.)


Nilai kembali yang lebih berguna

Banyak operasi dalam Reflectmirip dengan operasi ES5 yang ditentukan pada Object, seperti Reflect.getOwnPropertyDescriptordan Reflect.defineProperty. Namun, sedangkan Object.defineProperty(obj, name, desc)akan kembali objketika properti berhasil didefinisikan, atau melempar TypeErrorsebaliknya, Reflect.defineProperty(obj, name, desc)ditentukan untuk mengembalikan boolean yang menunjukkan apakah properti berhasil didefinisikan atau tidak. Ini memungkinkan Anda untuk memfaktorkan ulang kode ini:

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

Untuk ini:

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

Metode lain yang mengembalikan status sukses boolean adalah Reflect.set(untuk memperbarui properti), Reflect.deleteProperty(untuk menghapus properti), Reflect.preventExtensions(untuk membuat objek tidak dapat diperluas) dan Reflect.setPrototypeOf(untuk memperbarui tautan prototipe objek).


Operasi kelas satu

Di ES5, cara untuk mendeteksi apakah suatu objek objmendefinisikan atau mewarisi nama properti tertentu adalah dengan menulis (name in obj). Demikian pula untuk menghapus properti, seseorang menggunakan delete obj[name]. Meskipun sintaks khusus bagus dan pendek, itu juga berarti Anda harus secara eksplisit menggabungkan operasi ini dalam fungsi ketika Anda ingin meneruskan operasi sebagai nilai kelas satu.

Dengan Reflect, operasi ini dengan mudah didefinisikan sebagai fungsi kelas satu:
Reflect.has(obj, name)adalah setara fungsional (name in obj)dan Reflect.deleteProperty(obj, name)merupakan fungsi yang melakukan hal yang sama sepertidelete obj[name].


Aplikasi fungsi yang lebih andal

Di ES5, ketika seseorang ingin memanggil fungsi fdengan sejumlah variabel argumen yang dikemas sebagai array argsdan mengikat thisnilainya obj, seseorang dapat menulis:

f.apply(obj, args)

Namun, fbisa jadi sebuah objek yang dengan sengaja atau tidak sengaja mendefinisikan applymetodenya sendiri . Saat Anda benar-benar ingin memastikan bahwa fungsi built-in applydipanggil, seseorang biasanya menulis:

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

Tidak hanya bertele-tele ini, dengan cepat menjadi sulit untuk dipahami. Dengan Reflect, sekarang Anda dapat melakukan panggilan fungsi yang andal dengan cara yang lebih singkat dan lebih mudah dipahami:

Reflect.apply(f, obj, args)


Konstruktor argumen-variabel

Bayangkan Anda ingin memanggil fungsi konstruktor dengan sejumlah variabel argumen. Di ES6, berkat sintaks penyebaran baru, dimungkinkan untuk menulis kode seperti:

var obj = new F(...args)

Di ES5, ini lebih sulit untuk ditulis, karena seseorang hanya dapat menggunakan F.applyatau F.callmemanggil fungsi dengan sejumlah variabel argumen, tetapi tidak ada F.constructfungsi ke newfungsi dengan sejumlah variabel argumen. Dengan Reflect, sekarang seseorang dapat menulis, di ES5:

var obj = Reflect.construct(F, args)


Perilaku penerusan default untuk Perangkap proxy

Saat menggunakan Proxyobjek untuk membungkus objek yang sudah ada, sangat umum untuk mencegat operasi, melakukan sesuatu, dan kemudian "melakukan hal default", yang biasanya menerapkan operasi yang dicegat ke objek yang dibungkus. Misalnya, saya ingin mencatat semua akses properti ke suatu objek obj:

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

The Reflectdan ProxyAPI dirancang bersama-sama , sehingga untuk setiap Proxyperangkap, ada sebuah metode yang sesuai pada Reflectyang "melakukan hal yang default". Karenanya, setiap kali Anda merasa ingin "melakukan default" di dalam penangan Proxy, hal yang benar untuk dilakukan adalah selalu memanggil metode yang sesuai dalam Reflectobjek:

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

Jenis pengembalian Reflectmetode dijamin kompatibel dengan jenis pengembalian Proxyperangkap.


Kontrol pengikatan pengakses ini

Di ES5, cukup mudah untuk melakukan akses properti umum atau pembaruan properti. Misalnya:

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

Metode Reflect.getdan Reflect.setmemungkinkan Anda melakukan hal yang sama, tetapi sebagai tambahan menerima sebagai argumen opsional terakhir sebuah receiverparameter yang memungkinkan Anda menyetel secara eksplisit this-binding ketika properti yang Anda dapatkan / setel adalah pengakses:

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)

Ini kadang-kadang berguna saat Anda membungkus objdan Anda ingin pengiriman otomatis apa pun di dalam pengakses dirutekan ulang ke pembungkus Anda, misalnya jika objdidefinisikan sebagai:

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

Menelepon Reflect.get(obj, "foo", wrapper)akan menyebabkan this.bar()panggilan dialihkan ke wrapper.


Hindari warisan __proto__

Pada beberapa browser, __proto__didefinisikan sebagai properti khusus yang memberikan akses ke prototipe objek. ES5 menstandarkan metode baru Object.getPrototypeOf(obj)untuk menanyakan prototipe. Reflect.getPrototypeOf(obj)tidak persis sama, kecuali itu Reflectjuga mendefinisikan yang sesuai Reflect.setPrototypeOf(obj, newProto)untuk menyetel prototipe objek. Ini adalah cara baru yang sesuai dengan ES6 untuk memperbarui prototipe objek.
Perhatikan bahwa: setPrototypeOf juga ada diObject (sebagai benar ditunjukkan oleh KNU 's komentar )!


EDIT:
Catatan samping (menangani komentar ke Q): Ada jawaban singkat dan sederhana di 'Q: ES6 Modules vs HTML Imports' yang menjelaskan Realmsdan Loaderobjek.

Penjelasan lain ditawarkan oleh tautan ini :

Objek realm mengabstraksikan gagasan lingkungan global yang berbeda, dengan objek globalnya sendiri, salinan pustaka standar, dan "intrinsik" (objek standar yang tidak terikat ke variabel global, seperti nilai awal Object.prototype).

Web yang dapat diperluas : Ini adalah padanan dinamis dari sumber yang sama <iframe>tanpa DOM.

Layak disebutkan: semua ini masih dalam konsep, ini bukan spesifikasi yang terukir di batu! Ini ES6, jadi ingatlah kompatibilitas browser!

Semoga ini membantu!

GitaarLAB
sumber
@ Spencer Killen: Baiklah .. apakah jawaban ini mengarahkan pikiran Anda ke arah yang benar dan menjelaskan bagaimana hal ini berkaitan dengan perbedaan antara Reflect.applydan target.apply? Atau apa yang harus saya tambahkan sebelum bounty berakhir?
GitaarLAB
2
setPrototypeOf juga ada di Object.
Knu
1
Perhatikan bahwa menggunakan Reflect.getsebagai implementasi default untuk proxy get tidak berfungsi dengan baik jika Anda membuat proxy objek dengan properti prototipe. Itu hanya mengeluh bahwa itu tidak berhasil. Namun jika Anda malah menggunakan Reflect.get(target, property)tanpa melewatkan receiver, maka itu berhasil.
CMCDragonkai
Pengujian saya di mana Anda selalu mengakses properti melalui proxy, menghasilkan situasi di mana targetadalah target asli yang dibungkus proxy, sedangkan receiverproxy itu sendiri. Tetapi sekali lagi ini bisa berbeda jika Anda mengelola untuk mengakses properti secara berbeda.
CMCDragonkai
Ini jawaban yang luar biasa! Terima kasih
rpivovar
5

Mengikuti draf dokumen yang ditemukan di wiki,

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

Kami mendapatkan baris tentang "satu objek biasa" yang dijelaskan dalam draf. Ia juga memiliki definisi fungsi.

Wiki harus dapat diandalkan karena Anda dapat menemukan tautannya dari situs web emcascript

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

Saya menemukan tautan pertama oleh google dan tidak beruntung menemukannya dengan mencari di wiki secara langsung.

Biru
sumber
Terima kasih, langkah-langkah yang diambil ketika setiap metode dipanggil yang tercantum dalam spesifikasi resmi tampaknya hampir sama dengan yang ada di tautan saya tetapi saya masih tidak tahu apa yang dilakukan setiap metode ketika dipanggil, misalnya di bawah Reflect. Terapkan langkah-langkah yang tercantum 4. Lakukan operasi abstrak PrepForTailCall. 5. Kembalikan hasil pemanggilan metode internal [[Call]] target dengan argumen thisArgument dan args ------- apa artinya?
Jim Jones
Tebakan terbaik saya dari melihatnya adalah bahwa itu merujuk pada rekursi ekor pada langkah 4 dan langkah 5 adalah referensi ke fungsi prototipe. Sepertinya ide umumnya adalah untuk memverifikasi bahwa metode terapkan dapat berjalan pada apa yang diterapkan (langkah 1-2), penanganan kesalahan (langkah 3) dan kemudian pemanggilan fungsi yang diterapkan sedang dijalankan (langkah 4-5). Tebakan terbaik saya dari pemindaian melalui dokumentasi adalah bahwa inti dari modul Reflect adalah ketika Anda melakukan fungsionalitas yang memerlukan beberapa bentuk introspeksi objek. Penggunaan panggilan juga mungkin mengapa itu "tidak memiliki metode internal [[Panggilan]]".
Biru