符号是通过工厂函数Symbol()
创建的原始值:
const mySymbol = Symbol('mySymbol');
该参数是可选的,并提供了一个描述,主要用于调试。
一方面,符号就像对象一样,Symbol()
创建的每个值都是唯一的,不按值进行比较:
> Symbol() === Symbol()
false
另一方面,它们也表现得像原始值 - 它们必须通过typeof
进行分类,它们可以是对象中的属性键:
const sym = Symbol();
assert.equal(typeof sym, 'symbol');
const obj = {
[sym]: 123,
};
符号的主要用例是:
假设您要创建表示红色,橙色,黄色,绿色,蓝色和紫色的常量。这样做的一个简单方法是使用字符串:
const COLOR_BLUE = 'Blue';
在正面,记录该常量会产生有用的输出。在负面,存在将颜色的无关值误认为存在风险的风险,因为具有相同内容的两个字符串被认为是相等的:
const MOOD_BLUE = 'Blue';
assert.equal(COLOR_BLUE, MOOD_BLUE);
我们可以通过符号来解决这个问题:
const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = 'Blue';
assert.notEqual(COLOR_BLUE, MOOD_BLUE);
让我们使用符号值常量来实现一个函数:
const COLOR_RED = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN = Symbol('Green');
const COLOR_BLUE = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_ORANGE:
return COLOR_BLUE;
case COLOR_YELLOW:
return COLOR_VIOLET;
case COLOR_GREEN:
return COLOR_RED;
case COLOR_BLUE:
return COLOR_ORANGE;
case COLOR_VIOLET:
return COLOR_YELLOW;
default:
throw new Exception('Unknown color: '+color);
}
}
assert.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET);
值得注意的是,如果用'Blue'
调用它,该函数会抛出异常:
assert.throws(() => getComplement('Blue'));
对象中属性(字段)的键在两个级别使用:
该计划在基层运作。它的键反映了问题领域。
库和 ECMAScript 在元级别上运行。例如,.toString
是元级别属性键:
以下代码演示了不同之处:
const point = {
x: 7,
y: 4,
toString() {
return `(${this.x}, ${this.y})`;
},
};
assert.equal(String(point), '(7, 4)');
属性.x
和.y
存在于基准级别。它们是point
编码的点的坐标,反映了问题域。方法.toString()
是元级属性。它告诉 JavaScript 如何对此对象进行字符串化。
元级别和基础级别绝不能发生冲突,这在编程语言生命后期引入新机制时变得更加困难。
符号可用作属性键并解决此问题:每个符号都是唯一的,不会与任何字符串或任何其他符号冲突。
作为一个例子,我们假设我们正在编写一个库,如果它们实现了一个特殊的方法,它会以不同的方式对待它们。这就是定义这种方法的属性键并为对象实现它的方法如下所示:
const specialMethod = Symbol('specialMethod');
const obj = {
[specialMethod](x) {
return x + x;
}
};
assert.equal(obj[specialMethod]('abc'), 'abcabc');
在关于对象的章节中更详细地解释了这种语法。
在 ECMAScript 中扮演特殊角色的符号称为 _ 公知符号 _。例子包括:
Symbol.iterator
:使对象 _ 可迭代 _。它是返回迭代器的方法的关键。迭代在其自己的章节中进行了解释。
Symbol.hasInstance
:自定义instanceof
的工作方式。这是该运算符右侧实现的方法的关键。例如:
class PrimitiveNull {
static [Symbol.hasInstance](x) {
return x === null;
}
}
assert.equal(null instanceof PrimitiveNull, true);
Symbol.toStringTag: influences the default .toString() method.
Symbol.toStringTag
:影响默认.toString()
方法。
注意:覆盖.toString()
通常更好。
> String({})
'[object Object]'
> String({ [Symbol.toStringTag]: 'is no money' })
'[object is no money]'
Symbol.toStringTag
:exercises/symbols/to_string_tag_test.js
Symbol.hasInstance
:exercises/symbols/has_instance_test.js
如果我们将符号sym
转换为另一种原始类型会发生什么? TBL。 17 有答案。
Table 17: The results of converting symbols to other primitive types.
转换成 | 显式转换 | 强制(隐含转换) |
---|---|---|
布尔 | Boolean(sym) → 好的 |
!sym → 好的 |
数 | Number(sym) → TypeError |
sym*2 → TypeError |
串 | String(sym) → 好的 |
''+sym → TypeError |
sym.toString() → 好的 |
${sym} → TypeError |
符号的一个关键缺陷是在将它们转换为其他内容时抛出异常的频率。那背后的想法是什么?首先,转换为数字永远不会有意义,应该加以警告。其次,将符号转换为字符串对于诊断输出确实很有用。但是,警告意外地将符号转换为字符串属性键也是有意义的:
const obj = {};
const sym = Symbol();
assert.throws(
() => { obj['__'+sym+'__'] = true },
{ message: 'Cannot convert a Symbol value to a string' });
缺点是异常使符号处理更复杂。通过 plus 运算符组装字符串时必须显式转换符号:
> const mySymbol = Symbol('mySymbol');
> 'Symbol I used: ' + mySymbol
TypeError: Cannot convert a Symbol value to a string
> 'Symbol I used: ' + String(mySymbol)
'Symbol I used: Symbol(mySymbol)'
参见测验应用程序。