Що робить об’єкт 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 ) "Поверне результат виклику внутрішнього методу [[Виклик]] цілі з аргументами 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

Дякую :), зі сторінки, на яку я посилався (не знаю, наскільки це правомірно), здається, що кожна область - це власний "контекст Java-сценарію", і завантажувач завантажує сфери як модулі чи щось інше, виходячи зі схожості між відображенням та проксі та той факт, що вбудована функціональність проксі типу "перевантажень" могла б мати Reflect.Loaderі Reflect.Realmмати щось із функцією перевантаження модуля?
Джим Джонс,

1
Схоже, це "статичний клас" (як JSON) зі статичними методами: isExtensibleі ownKeysт. Д. У ES 6, з фактичними класами, це корисно, щоб дізнатись більше про клас ( targetу 16.1.2, я думаю).
Руді

Відповіді:


129

ОНОВЛЕННЯ 2015: Як було відзначено 7 «s відповідь , тепер ES6 (ECMAScript 2015) була завершена, більш відповідна документація тепер доступна:


Оригінальна відповідь (для (історичного) розуміння та додаткових прикладів) :

Reflection proposal, Здається, просунулися до проекту ECMAScript 6 специфікації . Наразі цей документ описує Reflectметоди -object та містить лише таке про сам Reflect-object:

Об’єкт Reflect - це один звичайний об’єкт.

Значення внутрішнього слота [[Prototype]] об’єкта Reflect є стандартним вбудованим об’єктом prototype об’єкта (19.1.3).

Об'єкт Reflect не є функційним об'єктом. Він не має внутрішнього методу [[Construct]]; не можна використовувати об’єкт Reflect як конструктор з новим оператором. Об'єкт 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тому , що «робить річ за замовчуванням». Отже, всякий раз, коли ви виявляєте бажання "зробити за замовчуванням" річ всередині обробника проксі, правильно, що потрібно зробити, це завжди викликати відповідний метод в Reflectоб'єкті:

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

Тип повернення Reflectметодів гарантовано сумісний з типом повернення Proxyпасток.


Керуйте цим прив'язуванням аксесуарів

У ES5 досить просто зробити загальний доступ до властивостей або оновити властивості. Наприклад:

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

Методи Reflect.getand Reflect.setдозволяють робити те ж саме, але додатково приймають як останній необов’язковий аргумент receiverпараметр, який дозволяє явно встановити this-binding, коли властивість, яку ви отримуєте / встановлюєте, є accessor:

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, тому пам’ятайте про сумісність браузера!

Сподіваюся, це допомагає!


@ Спенсер Кіллен: Ну .. чи ця відповідь спрямувала ваші думки в правильному напрямку і пояснила, як це пов’язано з різницею між Reflect.applyі target.apply? Або що я повинен додати до закінчення нагороди?
GitaarLAB

2
setPrototypeOf також існує на Object.
Кну

1
Зверніть увагу, що використання Reflect.getв якості реалізації за замовчуванням для проксі get не працює добре, якщо ви проксіруєте об’єкт із властивостями прототипу. Просто скаржиться, що не працює. Однак якщо ви замість цього використовуєте, Reflect.get(target, property)не передаючи receiver, тоді це працює.
CMCDragonkai

Мої тести, де ви завжди отримуєте доступ до властивостей через проксі-сервер, призводять до ситуації, коли targetвихідна мета, яку проксі обертає, а receiverсама є проксі-сервером. Але знову ж це може бути інакше, якщо вам вдасться отримати доступ до властивостей інакше.
CMCDragonkai

5

Переглядаючи проект документа, який міститься у вікі,

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

Ми отримуємо рядок про "єдиний звичайний об'єкт", який він уточнює в проекті. Він також має визначення функцій.

Вікі повинна бути надійною, оскільки посилання на неї можна знайти на веб-сайті emcascript

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

Однак я знайшов перше посилання в Google і не пощастило його знайти, безпосередньо здійснивши пошук у вікі.


Дякую, кроки, зроблені при виклику кожного методу, перерахованого в офіційній специфікації, здаються майже такими ж, як і в моєму посиланні, але я все ще не можу зрозуміти, що робить кожен метод, коли його викликають, наприклад, у розділі Reflect. Застосуйте кроки, наведені в списку 4. Виконайте абстрактну операцію PrepareForTailCall. 5. Повернути результат виклику внутрішнього методу [[Call]] target з аргументами thisArgument та аргументами ------- що це означає?
Джим Джонс,

Моє найкраще здогадування, якщо поглянути на це, це те, що він посилається на рекурсію хвоста на кроці 4, а на кроці 5 - посилання на функцію прототипу. Схоже, загальна ідея полягає в тому, щоб перевірити, чи застосовується метод apply на тому, до чого він застосовується (кроки 1-2), дескриптор помилки (крок 3), а потім викликати функцію, до якої запускається (кроки 4-5). Моє найкраще здогадування із сканування документації полягає в тому, що суть модуля Reflect полягає у тому, коли ви виконуєте функціонал, який вимагає певної форми самоаналізу об’єкта. Використання виклику також, мабуть, тому, що воно "не має внутрішнього методу [[Call]]".
Синій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.