Почему свойства ES6 Symbol могут быть перечислены Object.defineProperty?
В свойствах ES6 можно определить как свойства символа:
var symbol = Symbol();
var object = {};
object[symbol] = 'value';
MDN определяет перечислимые свойства как "те, которые могут быть итерированы циклом for..in" (1). Свойства символа никогда не повторяются с помощью цикла for... in, поэтому их можно считать неперечислимыми (2).
Значит ли это, что вы можете это сделать:
Object.defineProperty(object, symbol, {
value: 'value',
enumerable: true
});
и что запрос объекта для этого дескриптора действительно подтверждает, что это свойство перечислимо:
Object.getOwnPropertyDescriptor(object, symbol)
// -> { enumerable: true }
Почему? Какая польза от этого?
(1) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
(2) for... in использует [[Enumerate]]
, который включает только строковые ключи. Вероятно, теперь определение в MDN должно быть изменено, если у нас есть свойства символа.
Ответы
Ответ 1
Да, есть причина для перечисления свойств Symbol
: Object.assign
:
let s1 = Symbol();
let s2 = Symbol();
let s3 = Symbol();
let original = {};
original[s1] = "value1"; // Enumerable
Object.defineProperty(original, s2, { // Enumerable
enumerable: true,
value: "value2"
});
Object.defineProperty(original, s3, { // Non-enumerable
value: "value3"
});
let copy = {};
Object.assign(copy, original);
console.log("copy[s1] is " + copy[s1]); // value1, because it was enumerable
console.log("copy[s2] is " + copy[s2]); // value2, because it was enumerable
console.log("copy[s3] is " + copy[s3]); // undefined, because it wasn't enumerable
Live Copy на Babel REPL.
Просто для ясности:
MDN определяет перечислимые свойства как "те, которые могут быть итерированы циклом for..in" (1).
Это просто неправильно для ES6 (ES2015). Это было разумное, хотя и упрощенное, определение в ES5 и более ранних версиях, но оно уже не было упрощено даже из-за Symbol
s. Я исправил статью.
Это CW, потому что это был результат комментариев по этому вопросу.
Ответ 2
Это связано с тем, что правила для перечисления включают предложение, требующее строковые ключи. Имейте в виду, что перечисление и запрос ключей - это разные операции с совершенно разными правилами.
Рассматривая раздел для for ... in
/for ... of
оценки головы (13.7.5.12), он утверждает, что итерация выполнена с помощью:
-
Если iterationKind перечисляется, то
с. Верните obj.[[Enumerate]]()
.
В описании [[Enumerate]]
(9.1.11) очень четко указано, что он:
Возвращает объект Iterator (25.1.1.2), метод next
выполняет итерацию по всем строковым ключам перечислимых свойств O
.
Проверка перечислимых свойств происходит позже в теле, а пример псевдокода делает это еще более ясным:
function* enumerate(obj) {
let visited=new Set;
for (let key of Reflect.ownKeys(obj)) {
if (typeof key === "string") { // type check happens first
let desc = Reflect.getOwnPropertyDescriptor(obj,key);
if (desc) {
visited.add(key);
if (desc.enumerable) yield key; // enumerable check later
}
}
}
...
}
(комментарии мои)
Очевидно, что свойства с нестрочными ключами не будут перечислены. Используя этот пример:
var symbol = Symbol();
var object = {};
Object.defineProperty(object, symbol, {
value: 'value',
enumerable: true
});
Object.defineProperty(object, 'foo', {
value: 'bar',
enumerable: true
});
Object.defineProperty(object, 'bar', {
value: 'baz',
enumerable: false
});
Object.defineProperty(object, () => {}, {
value: 'bin',
enumerable: true
});
for (let f in object) {
console.log(f, '=', object[f]);
}
for (let k of Object.getOwnPropertyNames(object)) {
console.log(k);
}
вы можете проверить это в Вавилоне и Тракере.
Однако вы увидите две интересные вещи:
-
getOwnPropertyNames
содержит неперечислимые свойства. Это имеет смысл, поскольку следует совершенно другим правилам.
-
for...in
включает свойства, отличные от string, при обоих транспилерах. Это, похоже, не соответствует спецификации, но соответствует поведению ES5.