ES6-Symbol
Tao. Lv2

一、基础

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。

1. Symbol 值通过 Symbol 函数生成,使用 typeof,结果为 “symbol”

1
2
var s = Symbol();
console.log(typeof s); // "symbol"

2. Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。

3. instanceof 的结果为 false(未理解)

1
2
var s = Symbol('foo');
console.log(s instanceof Symbol); // false

5.26理解:instanceof是基于原型链上的查找,但Symbol函数不能使用new命令,不是对象

4. Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

1
2
3
4
5
var s1 = Symbol('foo');
console.log(s1); // Symbol(foo)

// 可以通过description来访问描述
s1.description // "foo"

5. 如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。

1
2
3
4
5
6
7
const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
console.log(sym); // Symbol(abc)

6. Symbol 函数的参数只是表示对当前 Symbol 值的描述,相同参数的 Symbol 函数的返回值是不相等的。

1
2
3
4
5
6
7
8
9
10
11
// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

console.log(s1 === s2); // false

// 有参数的情况
var s1 = Symbol('foo');
var s2 = Symbol('foo');

console.log(s1 === s2); // false

7. Symbol 值不能与其他类型的值进行运算,会报错。

1
2
3
var sym = Symbol('My symbol');

console.log("your symbol is " + sym); // TypeError: can't convert symbol to string

8. Symbol 值可以显式转为字符串。

1
2
3
4
var sym = Symbol('My symbol');

console.log(String(sym)); // 'Symbol(My symbol)'
console.log(sym.toString()); // 'Symbol(My symbol)'

9. Symbol 值可以作为标识符,用于对象的属性名,可以保证不会出现同名的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
var a = {
[mySymbol]: 'Hello!'
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
console.log(a[mySymbol]); // "Hello!"

10. Symbol 作为属性名,该属性不会出现在 for…in、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。

1
2
3
4
5
6
7
8
9
10
11
var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols);
// [Symbol(a), Symbol(b)]

11. 如果我们希望使用同一个 Symbol 值,可以使用 Symbol.for。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

1
2
3
4
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');

console.log(s1 === s2); // true

12. Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。

1
2
3
4
5
var s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s1)); // "foo"

var s2 = Symbol("foo");
console.log(Symbol.keyFor(s2) ); // undefined

二、应用场景

1:当做对象属性key来使用,但是注意用例中4种方式是无法获取到以Symbol为key的属性的。

用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let s_name= Symbol("name");
let obj= {
[s_name]: "lle",
age: 18,
title: "Engineer"
};

console.log(Object.keys(obj)); // ["age", "title"]

for(let key in obj) {
console.log(key); // 输出age, title
}

console.log(Object.getOwnPropertyNames(obj)); // ["age", "title"]

JSON.stringify(obj); // {"age":18,"title":"Engineer"}

以上这些常规的方式都是无法获取到的

如果要获取可以用个以下三种方式

1
2
3
4
5
obj[s_name];// lle

Object.getOwnPropertySymbols(obj); // [Symbol(name)]

Reflect.ownKeys(obj); // ["age", "title", Symbol(name)]

Symbol.iterator

对象的Symbol.iterator属性,指向该对象的默认遍历器方法(ES6阮一峰,未理解)

例子

1
2
3
4
5
6
7
8
9
Number.prototype[Symbol.iterator] = function*() {
let i = 0;
let num = this.valueOf();
while (i < num) {
yield i++;
}
}

console.log([...5]) // [0, 1, 2, 3, 4]

上面代码中,先定义了Number对象的遍历器接口,扩展运算符将5自动转成Number实例以后,就会调用这个接口,就会返回自定义的结果。

对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。

1
2
3
4
5
6
7
8
9
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};

// TypeError: Cannot spread non-iterable object.
let arr = [...arrayLike];

知乎:Symbol最大用处是给其他复合类型部署了Symbol.iterator迭代器,提供统一的遍历接口,直接用感觉用不到

个人理解:Symbol见得最多的场景就是Symbol.iterator,部署迭代器,提供遍历接口,如上面给Number定义遍历器的例子,因为Symbol代表独一无二的,因此定义时就避免了冲突的现象

三、模拟实现

参考讶羽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
(function() {
var root = this;

var generateName = (function(){
var postfix = 0;
return function(descString){
postfix++;
return '@@' + descString + '_' + postfix
}
})()

var SymbolPolyfill = function Symbol(description) {

if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

var descString = description === undefined ? undefined : String(description)

var symbol = Object.create({
toString: function() {
return this.__Name__;
},
valueOf: function() {
return this;
}
})

Object.defineProperties(symbol, {
'__Description__': {
value: descString,
writable: false,
enumerable: false,
configurable: false
},
'__Name__': {
value: generateName(descString),
writable: false,
enumerable: false,
configurable: false
}
});

return symbol;
}

var forMap = {};

Object.defineProperties(SymbolPolyfill, {
'for': {
value: function(description) {
var descString = description === undefined ? undefined : String(description)
return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
},
writable: true,
enumerable: false,
configurable: true
},
'keyFor': {
value: function(symbol) {
for (var key in forMap) {
if (forMap[key] === symbol) return key;
}
},
writable: true,
enumerable: false,
configurable: true
}
});

root.SymbolPolyfill = SymbolPolyfill;

})()

四、参考

阮一峰:https://es6.ruanyifeng.com/#docs/symbol

冴羽:https://juejin.cn/post/6844903619544760328

ES6 symbol类型的理解和应用场景归纳:https://www.jianshu.com/p/9805729c859d

五、总结

Symbol是ES6的特性,它代表创建后独一无二且不可变的数据类型

最多的应用场景是Symbol.iterator,部署迭代器,提供遍历接口,如上面给Number定义遍历器的例子,因为Symbol代表独一无二的,因此定义时就避免了冲突的现象

其他暂未遇到