Страницы

Поиск по вопросам

пятница, 10 января 2020 г.

Почему браузер дважды обращается к Symbol.unscopables?

#javascript #ecmascript_6




with(new Proxy({}, {
  has() { return true },
  get(obj, key, proxy) { return console.log(String(key)) } })
) {
  a--
}




Вывод в Chrome:

Symbol(Symbol.unscopables)
a
Symbol(Symbol.unscopables)


Вывод в Firefox:

Symbol(Symbol.unscopables)
Symbol(Symbol.unscopables)
a


Насколько я понимаю, одно обращение связано с чтением, а другое с записью.

Логично, что конструкция a-- должна записать значение в то же место, откуда прочитала.
Нет, это не так.

А двойное чтение из Symbol.unscopables как бы намекает, что это не так и у меня есть
возможность отдать нечто для чтения, но сказать, что в мой объект этого записывать не надо?

Неужели это так и задумано? Что на эту тему говорит стандарт?

Собственно, в Хроме и FF такое почти прокатывает - чтение и запись связаны с разными
объектами, однако работает по-разному:

Update: Safari 10 читает Symbol.unscopables только один раз.



var a, b, flag = true

with (a = { x: 7 })
  with (b = { x: 4, get [Symbol.unscopables]() { return { x: flag=!flag } } })
    x++

                 // Chrome   FF       Safari   Edge
console.log(a)   // {x:5}    {x:7}    {x:7}    {x:5}
console.log(b)   // {x:4}    {x:8}    {x:5}    {x:4}




PS: Этот вопрос по-английски.
    


Ответы

Ответ 1



Согласно спецификации, @@unscopables в этом случае должно читаться только один раз. Детали (к сожаления по английски) в https://mail.mozilla.org/pipermail/es-discuss/2017-February/047725.html Следовательно Chrome и Firefox не следуют спецификации, а Safari следует. Смотрите также https://bugzilla.mozilla.org/show_bug.cgi?id=1341061 и https://bugs.chromium.org/p/v8/issues/detail?id=5992

Ответ 2



Обратимся к спецификации, в таблице 1 находится описание символа @@unscopables An object valued property whose own property names are property names that are excluded from the with environment bindings of the associated object. Объект, в котором названия свойств совпадают со свойствами исключенными из with связывания. Значение этого свойства проверяется в функции HasBindings(N) Let envRec be the object Environment Record for which the method was invoked. Let bindings be the binding object for envRec. Let foundBinding be HasProperty(bindings, N) ReturnIfAbrupt(foundBinding). If foundBinding is false, return false. If the withEnvironment flag of envRec is false, return true. Let unscopables be Get(bindings, @@unscopables). ReturnIfAbrupt(unscopables). If Type(unscopables) is Object, then Let blocked be ToBoolean(Get(unscopables, N)). ReturnIfAbrupt(blocked). If blocked is true, return false. Return true. Здесь нас интересуют пункты начиная с 6. Если проверяемое свойство N находится в блоке with, оно всегда проверяется в объекте unscopables. Теперь перейдем к постфиксному декременту. В алгоритме есть два ключевых момента: вызов GetValue(lhs) - для получения текущего значения вызов PutValue(lhs, newValue) - для установки нового значения Каждая из этих функций в итоге вызывает описанную выше HasBinding. В обновленном пример добавлена функция set и в логах видно, что второй раз вызывается как раз перед ней. var value = 10; with(new Proxy({}, { has(o, k) { console.log('has', String(k)); return true; // k !== 'console'; }, get(obj, key, proxy) { console.log('get', String(key)); if (key === Symbol.unscopables) { return { a: false, console: true } } return value; }, set(o, k, v, proxy) { console.log('set', k, v); if (k == 'a') value = v; } })) { a--; } Что характерно firefox сначала получает все связанные поля, и лишь затем получает значения свойств. Далее можно разобрать второй пример: var a, b, flag = true var valueA = 7, valueB = 4; with(a = { get x() { console.log('get A', flag); return valueA; }, set x(v) { console.log('set A', flag); valueA = v; } }) with(b = { get x() { console.log('get B', flag); return valueB; }, set x(v) { console.log('set B', flag); valueB = v; }, get [Symbol.unscopables]() { var t = { x: flag = !flag }; console.log('Symbol.unscopables', t); return t; } }) x++ // Chrome FF console.log(valueA) // {x:5} {x:7} console.log(valueB) // {x:4} {x:8} Что тут происходит? постфиксная операция, которая внутри себя вызывает Put и Get getter Symbol.unscopables - который определяет находится ли свойство x в текущем with или нет. Значение флага меняется на противоположное. Как можно заметить по логам, сначала значения флага возвращается false, что в соответствии со спецификацией указывает на то, что данное имя находится в текущем скопе. Поэтому значение берется из объекта b. Далее флаг выставляется в true и при вычслении в каком скопе находится x в котороый надо положить, получаем, что он находится не текущем скопе, поэтому значение сохраняется в объекте a. Как видно - Chrome работает в соответствии со спецификацией. Что касается firefox. Как можно заметить, firefox не инвертирует значение возвращенное в объекте unscopables, как это указано в спецификации, поэтому значение сначала берется из внешнего with и присваивается значению из внутреннего. По результатам проверок, можно сделать вывод, что firefox сначала получает свойство куда записывать, и лишь затем свойство откуда брать значение. Из-за этого в примере, когда значение flag меняется на каждый вызов, происходит расхождение с работой Chrome и EDGE.

Комментариев нет:

Отправить комментарий