Что делает объект Reflect в JavaScript?

87

Некоторое время назад я видел пустую заглушку на MDN для Reflectобъекта в javascript, но я не могу найти что-либо в 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 объект один обычный объект.», Чтобы мое понимание Reflectэто просто контейнер для Realmи Loaderобъектов, но я не знаю , что последнее сделать что- либо.
simonzack
Спасибо :), со страницы, на которую я ссылался (не знаю, насколько это законно), мне кажется, что каждое Realm - это собственный «контекст java-скрипта», и загрузчик загружает Realms, такие как модули или что-то в этом роде, на основе сходства между отражением и проксите , и тот факт , что прокси - сервер своего рода «перегрузки» , построенную в функциональности может Reflect.Loaderи Reflect.Realmесть что - то с перегрузкой функциональности модуля?
Джим Джонс
1
Похоже, что это «статический класс» (например, JSON) со статическими методами: isExtensibleи ownKeysт. Д. В ES 6 с реальными классами это полезно, чтобы узнать больше о классе ( я думаю, targetв 16.1.2 ).
Руди
Вы видели github.com/tvcutsem/harmony-reflect/wiki ?
kangax

Ответы:

129

ОБНОВЛЕНИЕ 2015: Как указано в ответе 7 -го , теперь, когда ES6 (ECMAScript 2015) был завершен, теперь доступна более подходящая документация:


Оригинальный ответ (для (исторического) понимания и дополнительных примеров) :

Reflection proposal, Кажется, продвинулись к проекту ECMAScript 6 Спецификация . В этом документе в настоящее время Reflectописываются методы -object и говорится только о самом Reflect-object:

Объект Reflect - это простой обычный объект.

Значение внутреннего слота [[Prototype]] объекта Reflect - это стандартный встроенный объект-прототип Object (19.1.3).

Объект Reflect не является функциональным объектом. У него нет внутреннего метода [[Construct]]; невозможно использовать объект Reflect в качестве конструктора с оператором new . У объекта Reflect также нет внутреннего метода [[Call]]; невозможно вызвать объект Reflect как функцию.

Тем не менее, есть краткое объяснение его цели в ES Harmony :

Модуль «@reflect» служит нескольким целям:
  • Теперь, когда у нас есть модули, модуль «@reflect» является более естественным местом для многих методов отражения, ранее определенных в Object. В целях обеспечения обратной совместимости маловероятно, что статические методы в Object исчезнут. Однако новые методы, скорее всего, следует добавлять в модуль «@reflect», а не в конструктор объекта.
  • Естественный дом для прокси, избавляющий от необходимости в глобальной привязке прокси.
  • Большинство методов в этом модуле однозначно сопоставляют ловушки прокси. Обработчики прокси-сервера нуждаются в этих методах для удобной пересылки операций, как показано ниже.



Итак Reflect объект предоставляет ряд служебных функций, многие из которых, по-видимому, перекрываются с методами ES5, определенными в глобальном объекте.

Однако это на самом деле не объясняет, какие существующие проблемы это намеревается решить или какие функции добавляются. Я подозревал, что это можно исправить, и действительно, приведенная выше спецификация гармонии связана с «ненормативной приблизительной реализацией этих методов» .

Изучение этого кода может дать (дальнейшее) представление о его использовании, но, к счастью, есть также вики, в которой изложен ряд причин, по которым объект Reflect полезен :
(Я скопировал (и отформатировал) следующий текст для использования в будущем. источник, так как это единственные примеры, которые я смог найти. Кроме того, они имеют смысл, уже имеют хорошее объяснение и касаются applyпримера вопроса .)


Более полезные возвращаемые значения

Многие операции в Reflectпохожи на операции ES5, определенные в Object, например, Reflect.getOwnPropertyDescriptorи Reflect.defineProperty. Однако, в то время как Object.defineProperty(obj, name, desc)будет либо возвращать, objкогда свойство было успешно определено, либо генерировать в TypeErrorпротивном случае, Reflect.defineProperty(obj, name, desc)указывается, что просто возвращает логическое значение, которое указывает, было ли свойство успешно определено. Это позволяет вам реорганизовать этот код:

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И ProxyAPI , были разработаны в тандеме , так что для каждой Proxyловушки, существует соответствующий метод на Reflectтом , что «делает вещь по умолчанию». Следовательно, всякий раз, когда вы обнаруживаете, что хотите "сделать стандартную" вещь внутри обработчика Proxy, правильнее всего будет всегда вызывать соответствующий метод в Reflectобъекте:

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

ReflectГарантируется, что возвращаемый тип методов совместим с возвращаемым типом Proxyловушек.


Управление привязкой this для аксессоров

В 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-связывающего когда свойство , что вы получите / набор аксессор:

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и хотите, чтобы любые самоотправки в аксессоре перенаправлялись в вашу оболочку, например, если objопределено как:

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

Вызов 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 (как правильно указал КНС «s комментарий )!


РЕДАКТИРОВАТЬ:
примечание (обращаясь к комментариям к Q): есть короткий и простой ответ на «Q: Модули ES6 против импорта HTML», который объясняет Realmsи Loaderобъекты.

Другое объяснение предлагается по этой ссылке :

Объект области абстрагирует понятие отдельной глобальной среды со своим собственным глобальным объектом, копией стандартной библиотеки и «внутренними компонентами» (стандартные объекты, которые не привязаны к глобальным переменным, как начальное значение Object.prototype).

Расширяемый веб : это динамический эквивалент того же происхождения <iframe>без DOM.

Однако стоит упомянуть: все это еще в черновике, это не спецификация, выгравированная на камне! Это ES6, так что помните о совместимости с браузером!

Надеюсь это поможет!

GitaarLAB
источник
@Spencer Killen: Ну ... этот ответ направил ваши мысли в правильном направлении и объяснил, как это связано с разницей между Reflect.applyи target.apply? Или что мне добавить до окончания награды?
GitaarLAB
2
setPrototypeOf также существует на Object.
Knu 08
1
Обратите внимание, что использование Reflect.getв качестве реализации по умолчанию для прокси get не очень хорошо работает, если вы проксируете объект со свойствами прототипа. Он просто жалуется, что не работает. Однако, если вы вместо этого используете, Reflect.get(target, property)не передавая receiver, тогда он действительно работает.
CMCDragonkai 09
Мои тесты, в которых вы всегда получаете доступ к свойствам через прокси, приводят к ситуации, когда targetэто исходная цель, которую оборачивает прокси, а receiverсам прокси. Но опять же, это может быть иначе, если вам удастся получить доступ к свойствам по-другому.
CMCDragonkai 09
5

Следуя черновику документа, найденному в вики,

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

Получается строчка про «единичный обычный объект», проясняемая в черновике. В нем также есть определения функций.

Вики должна быть надежной, поскольку вы можете найти ссылку на нее на сайте emcascript.

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

Я нашел первую ссылку от Google, но мне не удалось найти ее, напрямую выполнив поиск в вики.

Синий
источник
Спасибо, шаги, предпринимаемые при вызове каждого метода, указанного в официальной спецификации, кажутся в значительной степени такими же, как и в моей ссылке, но я до сих пор не могу понять, что делает каждый метод при его вызове, например, в разделе Reflect. Выполните перечисленные шаги. 4. Выполните абстрактную операцию PrepareForTailCall. 5. Вернуть результат вызова внутреннего метода [[Call]] цели с аргументами thisArgument и args ------- что это означает?
Джим Джонс
Глядя на него, я могу предположить, что он ссылается на хвостовую рекурсию на шаге 4, а шаг 5 - это ссылка на функцию-прототип. Похоже, общая идея состоит в том, чтобы проверить, может ли метод apply работать на том, к чему он применен (шаги 1-2), обработчик ошибки (шаг 3), а затем вызвать функцию, против которой выполняется применение (шаги 4-5). Изучая документацию, я могу предположить, что смысл модуля Reflect - это когда вы выполняете функции, требующие некоторой формы самоанализа объекта. Использование call также, вероятно, является причиной того, что «не имеет внутреннего метода [[Call]]».
Blue