Symbol数据类型
总览
Symbol笔记总结
# Symbol
Symbol(符号)是 ES 6 新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。 ES5 的对象属性名都是字符串,这容易造成属性名的冲突,符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
符号需要使用 Symbol()函数初始化。因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol
let s1 = Symbol();
let s2 = Symbol();
// 创建 Symbol 的时候,可以添加一个描述
let s3 = Symbol('foo');
let s4 = Symbol('foo');
console.log(s1) // Symbol()
console.log(s1 == s2); // false
console.log(s3 == s4); // false
// Symbol 值可以显式转为字符串。
console.log(s1.toString()); // Symbol()
console.log(s3.toString()); // Symbol(foo)
// Symbol 值也可以转为布尔值,但是不能转为数值。
console.log(Boolean(s1)); //true
//
console.log(s3.description); // foo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Symbol函数前不能使用new命令。这是因为生成的 Symbol 是一个原始类型的值,不是对象不能添加属性
Symbol 值不能与其他类型的值进行运算
Symbol 值作为对象属性名时,不能用点运算符
const mySymbol1 = Symbol();
const mySymbol2 = Symbol();
const a = {};
a.mySymbol1 = 'Hello!';
// 正确的写法
a[mySymbol2] = 'world!'
console.log(a[mySymbol1]); // undefined
console.log(a['mySymbol1']); // Hello!
console.log(a[mySymbol2]); // World!
console.log(a['mySymbol2']); // undefined
// 因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。
2
3
4
5
6
7
8
9
10
11
12
13
同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中
let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
--------------------
// 采用增强的对象写法,可以写得更简洁一些
let obj = {
[s](arg) { ... }
};
2
3
4
5
6
7
8
9
# Symbol 在开发实际的应用
function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
2
3
4
5
6
7
8
9
10
11
上面代码中,字符串Triangle
就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
const shapeType = {
// triangle: 'Triangle'
triangle: Symbol()
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });
2
3
4
5
6
7
8
9
10
11
12
13
14
由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
let size = Symbol('size');
class Collection {
constructor() {
this[size] = 0;
}
add(item) {
this[this[size]] = item;
this[size]++;
}
static sizeOf(instance) {
return instance[size];
}
}
let x = new Collection();
Collection.sizeOf(x) // 0
x.add('foo');
Collection.sizeOf(x) // 1
Object.keys(x) // ['0']
Object.getOwnPropertyNames(x) // ['0']
Object.getOwnPropertySymbols(x) // [Symbol(size)]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Symbol属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
const obj = {};
const foo = Symbol('oof');
obj[foo] = 'bar';
for (let i in obj) {
console.log(i); // 无输出
}
console.log(Object.getOwnPropertyNames(obj) ); // []
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(oof)]
-----------------------------------------------------------------
const a = Symbol(0)
let obj = {
[a]: 1,
id: 2,
num: 3
}
console.log(Reflect.ownKeys(obj)) // ['id', 'num', Symbol(0)]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Symbol.for()与Symbol.keyFor()
重新使用同一个 Symbol 值,Symbol.for()
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
Symbol.for()
的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同一个值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
2
3
Symbol.keyFor()
方法返回一个已全局登记的 Symbol 类型值的key
。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("abb");
Symbol.keyFor(s2) // undefined
2
3
4
# 参考
阮一峰 ECMAScript 6 (ES6) 标准入门教程 第三版 (opens new window)
JavaScript高级程序设计