为啥在迭代时省略了作为索引值的符号?

Posted

技术标签:

【中文标题】为啥在迭代时省略了作为索引值的符号?【英文标题】:Why is a Symbol as an index value omitted when iterated?为什么在迭代时省略了作为索引值的符号? 【发布时间】:2021-03-14 22:19:13 【问题描述】:

我确实按照 this idea 在 javascript 中使用符号创建了一个 Enum(-like) 结构:

const DATA = Object.freeze(
    GOOD:         Symbol("good"),
    BAD:          Symbol("bad"),
    I_DONT_CARE:  Symbol("i-dont-care"),
);

现在,我想使用(唯一)符号创建一个新对象来引用每个项目并扩展有关它的数据:

const TRANSLATION = Object.freeze(
    [DATA.GOOD]:         "Gut",
    [DATA.BAD]:          "Schlecht",
    [DATA.I_DONT_CARE]:  "Egal",
);

这确实是有效的even though e.g. TypeScript did not believe that and complained,但这也正在修复中。

但是,现在的问题是,如果我使用Object.entries(TRANSLATION)(或Object.keys(TRANSLATION) / Object.values(TRANSLATION))进一步编辑和转换该对象,我会得到一个空对象。

似乎所有Symbols 在迭代时都被忽略了:

myenum = 
  "bla": "blub",
  [Symbol("test")]: "testData"

console.log(Object.keys(myenum));

我不希望这样,我只想使用符号作为唯一键,这样我就可以将更多信息存储/“映射”到特定条目。

【问题讨论】:

这些方法就是这样定义的。 这是为了保持一致性。在 ES6 之前,我们有 Object.keys 但没有符号,所以 Object.keys(obj) 在 ES5 和 ES6 中必须以不同方式工作,这使得代码不稳定。 Object.entries 的添加较晚,但是如果迭代符号,它将与 Object.keys 不一致。 Object.getOwnPropertySymbols 仅提取符号或 Object.getOwnPropertyDescriptors 提取所有内容。 另外,还有Reflect.ownKeys(我一直忘记Reflect,因为我不经常使用它)。 【参考方案1】:

符号在 for...in 迭代中是不可枚举的。此外,Object.getOwnPropertyNames() 不会返回符号对象属性,但是,您可以使用 Object.getOwnPropertySymbols() 来获取这些属性。 - MDN

这就是符号的工作方式。它们不会被迭代。所以使用Object.keys() 会将它们从结果中排除。

但是,您可以使用 Object.getOwnPropertySymbols() 从对象中获取符号,然后将它们与 Object.keys() 组合以创建所有键的最终列表。

检查下面的示例以查看它是否有效。请注意,堆栈 sn-p 控制台将 Symbol 解释为 null。但是,当您打开浏览器的控制台时,您应该会看到实际的结果。

const myenum = 
  "bla": "blub",
  [Symbol("foo")]: "bar",
  [Symbol("roger")]: "roger"


const iterableKeys = Object.keys(myenum);
const symbolKeys = Object.getOwnPropertySymbols(myenum);
const keys = [...iterableKeys, ...symbolKeys];

console.log(keys);

const values = keys.map(key => myenum[key]);

console.log(values);

作为 VLAZ 建议使用 Reflect.ownKeys 的更短的替代方案

它的返回值等价于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。

这意味着它已经默认结合了Object.keys()(但使用Object.getOwnPropertyNames)和Object.getOwnPropertySymbols()

const myenum = 
  "bla": "blub",
  [Symbol("foo")]: "bar",
  [Symbol("roger")]: "roger"


const keys = Reflect.ownKeys(myenum);

console.log(keys);

const values = keys.map(key => myenum[key]);

console.log(values);

【讨论】:

【参考方案2】:

符号不会被迭代。从我的角度来看,它们对于隐藏属性和生成独特的重用值更有用。

我建议使用 TypeScript 实现,我认为它已经过迭代和实战测试。

TypeScript 支持枚举:

enum Direction 
  Up = 1,
  Down,
  Left,
  Right,

下面的JS得到transpiled:

var Direction;
(function (Direction) 
    Direction[Direction["Up"] = 1] = "Up";
    Direction[Direction["Down"] = 2] = "Down";
    Direction[Direction["Left"] = 3] = "Left";
    Direction[Direction["Right"] = 4] = "Right";
)(Direction || (Direction = ));

// resulting object 
// Direction = 
//   1: "Up",
//   2: "Down",
//   3: "Left",
//   4: "Right",
//   "Up": 1,
//   "Down": 2,
//   "Left": 3,
//   "Right": 4
// 

如果您从符号切换到唯一字符串,问题将得到解决。

【讨论】:

【参考方案3】:

啊,好吧,原因是:符号不是enumerable。幸运的是,有 Object.getOwnPropertySymbols() 至少返回一个包含所有符号的数组,类似于Object.keys

有了它,您当然还可以以老式的 pre-ECMAScript 2017 方式“模拟”更高级的功能,例如 OBject.values

const myenum = 
  "bla": "blub",
  [Symbol("test")]: "testData",
  [Symbol("goodsymbol")]: "baddata"

console.log(Object.getOwnPropertySymbols(myenum).map((mysymbol) => 
  const value = myenum[mysymbol];
  return [mysymbol, value];
));

【讨论】:

更正符号可枚举的。但是,它们不会被迭代。 The enumerable property is still taken into account in different cases. 另外,如果你想遍历所有键,我建议Reflect.ownKeys(myenum)。它也恰好更短。

以上是关于为啥在迭代时省略了作为索引值的符号?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Dart (Flutter) 中获取每个可迭代值的索引号

为啥将迭代器作为参数传递并在尾部位置递归时后缀失败并且前缀工作正常? [复制]

迭代时,选定网格行的索引为 -1

python之切片与迭代

我怎样才能加快这个迭代?

迭代器