一、基础
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])
|
上面代码中,先定义了Number对象的遍历器接口,扩展运算符将5自动转成Number实例以后,就会调用这个接口,就会返回自定义的结果。
对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。
1 2 3 4 5 6 7 8 9
| let arrayLike = { '0': 'a', '1': 'b', '2': 'c', length: 3 };
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代表独一无二的,因此定义时就避免了冲突的现象
其他暂未遇到