Symbol是ES6新增的原始数据类型,表示独一无二的值。ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。
ES5的原始数据类型:string、number、boolean、null、undefind所以目前有 6 个原始数据类型
**Symbol**主要是为了解决对象属性重名的问题!!!**Symbol**是通过**Symbol()**函数返回的,但是该函数不是构造函数!!!
基础使用
基础用法:
console.log(Symbol()); // Symbol()console.log(Symbol() == Symbol()); // falseconsole.log(typeof Symbol()); // symbol
不能进行赋值属性:
let s1 = Symbol();s1.a = "a";console.log(s1.a); // undefind
Symbol()方法可以传递一个参数,表示对Symbol的描述:
let s1 = Symbol("s1");console.log(s1); // Symbol(s1)
Symbol()的值是字符串,标识名会转换为字符串:
let obj = { a: 1 };let s1 = Symbol(obj);console.log(s1); // Symbol([object Object])
使用undefined和null:
console.log(Symbol(undefined)); // Symbol(),undefined 表示空的,和没有传参一样console.log(Symbol(null)); // Symbol(null)
symbol不能强制转换为number数据类型:
let s1 = Symbol();console.log(String(s1)); // Symbol()console.log(Boolean(s1)); // trueconsole.log(Number(s1)); // Cannot convert a Symbol value to a number
symbol可以使用!转换为布尔值:
let s1 = Symbol();console.log(!s1); // false
访问symbol数据的原型:
let s1 = Symbol();console.log(Object.getPrototypeOf(s1));

举例:
let name = Symbol();let person = {};// person.name = "张三"; // 无效的,不会使用上面的 Symboperson[name] = "张三"; // 必须使用 [] 表达式console.log(person); // {Symbol(): '张三'}// =======分割=======let name = Symbol();let eat = Symbol();let person = {[name]: "张三",[eat]() {console.log(this[name] + "is eat");},};person[eat](); // 张三is eat
方法
Symbol.for()
有时,我们希望重新使用同一个Symbol值,Symbol.for()方法可以做到这一点。
它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建一个以该字符串为名称的Symbol值,并将其注册到全局。
let s1 = Symbol("foo");let s2 = Symbol("foo");let s3 = Symbol.for("foo");let s4 = Symbol.for("foo");console.log(s1 === s2); // falseconsole.log(s3 === s4); // trueconsole.log(s1 === s4); // false
Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
Symbol.keyFor()
该方法返回一个已登记的Symbol类型值的key(描述名),但是只能获取Symbol.for()返回的symbol
let s = Symbol("foo");let s1 = Symbol.for("foo");console.log(Symbol.keyFor(s)); // undefinedconsole.log(Symbol.keyFor(s1)); // foo
遍历
symbol的值是不能进行遍历的。
let a = Symbol();let b = Symbol();const obj = {[a]: "a",[b]: "b",};console.log(obj); // {Symbol(): 'a', Symbol(): 'b'}console.log(Object.keys(obj)); // []for (const key in obj) {console.log(key); // 空的}let newObj = Object.assign({}, obj); // 但是可以进行合并console.log(newObj); // {Symbol(): 'a', Symbol(): 'b'}
所以Object提供了一个遍历Symbol的方法Object.getOwnPropertySymbols(),该方法只能遍历symbol的值。
let a = Symbol();let b = Symbol();const obj = {[a]: "a",[b]: "b","c": "c"};// 只遍历对象的 Symbol 值console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(), Symbol()]
iterator 迭代对象
:::info
迭代器的概念:对数据结构的读取的一种方式,有序的、连续的基于拉取的一种消耗数据的组织方式;
:::
Symbol()函数的构造器上有很多的属性,属性对应部署了相关的方法:
let s = Symbol();console.log(Object.getPrototypeOf(s));

而我们之前使用的instanceof来判断对象是否是构造函数实例背后就是使用hasInstance方法来实现的。
而我们主要学习的是iterator: Symbol(Symbol.iterator)迭代方法。
以数组为例子,我们看看数组的原型:
可以看到数组的原型上有个Symbol(Symbol.iterator)方法。
接着我来执行一下:
let arr = [1, 2, 3, 4];let iter = arr[Symbol.iterator];console.log(iter); // ƒ values() { [native code] }
因为
Symbol返回是独一无二的标识,所以我们不能通过arr[Symbol("Symbol.iterator")]去访问,这样是访问一个新的Symbol标识,所以我们要直接访问Symbol函数上的iterator属性来访问迭代对象。
可以看到只返回了一个函数的标识,那我们来执行它:
let arr = [1, 2, 3, 4];let iter = arr[Symbol.iterator](); // 获取迭代器接口console.log(iter); // Array Iterator {} // 返回一个迭代空对象

里面有个next方法,我们去执行它:
let arr = [1, 2, 3, 4];let iter = arr[Symbol.iterator]();console.log(iter.next()); // {value: 1, done: false}console.log(iter.next()); // {value: 2, done: false}console.log(iter.next()); // {value: 3, done: false}console.log(iter.next()); // {value: 4, done: false}console.log(iter.next()); // {value: undefined, done: true}
可以看到我们每次调用next方法都会返回数组的value和表示是否完成的done。
JavaScript中的数据结构有:object、array、类数组、Map、Set、weakMap、weakSet、typeArray(二进制数据的缓存区)
其中object没有iterator的接口(方法),且数据的key不是有序的、连续的,所以不能进行迭代。
如果想要迭代以上数据结构(除object)的值的该怎么办呢?ES6对部署了iterator接口的数据类型提供了一种统一的方式进行迭代,那就是for...of...for...of...本身也是调用iterator.next()方法然后进行遍历。
let arr = [1, 2, 3];// 迭代相应的数据结构for (const iterator of arr) {console.log(iterator); // 1 2 3}// for...in... 遍历数组for (const key in arr) {console.log(key); // 0 1 2}let obj = {name: "张三",age: 20,};// 对象是不能迭代for (const iterator of obj) {// obj is not iterable// 因为没有部署 iterable 接口// 如果想要迭代对象,必须给对象部署 iterable 接口console.log(iterator);}
根据以上的分解步骤,我们可以模拟一个iterator的接口:
let arr = [1, 2, 3];function makeIterator(array) {let nextIndex = 0;// 返回一个对象,对象里有 next 方法return {next() {// 如果没有迭代完成// 返回 {value:xxx, done:false}return nextIndex < array.length? {value: array[nextIndex++],done: false,}: {value: undefined,done: true,};// 如果迭代完成// 返回 {value:undefined, done:true}},};}let ite = makeIterator(arr);console.log(ite.next()); // {value: 1, done: false}console.log(ite.next()); // {value: 2, done: false}console.log(ite.next()); // {value: 3, done: false}console.log(ite.next()); // {value: undefined, done: true}
最后给对象新增一个iterator的接口:
let obj = {start: [1, 2, 3, 4, 5],end: [10, 20, 30],[Symbol.iterator]() {let index = 0,list = [...this.start, ...this.end],len = list.length;return {next() {if (index < len) {return {value: list[index++],done: false,};} else {return {value: undefined,done: true,};}},};},};for (const iterator of obj) {console.log(iterator); // 1 2 3 4 5 10 20 30}// 部署了迭代器接口后,能够使用数组的拓展运算符let arr = [...obj];console.log(arr); // [1, 2, 3, 4, 5, 10, 20, 30]
:::danger
⚠️ 注意
部署了迭代器接口后,能够使用数组的拓展运算符。
:::
