SymbolES6新增的原始数据类型,表示独一无二的值。
ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。

ES5的原始数据类型:stringnumberbooleannullundefind 所以目前有 6 个原始数据类型

**Symbol**主要是为了解决对象属性重名的问题!!!
**Symbol**是通过**Symbol()**函数返回的,但是该函数不是构造函数!!!

基础使用

基础用法:

  1. console.log(Symbol()); // Symbol()
  2. console.log(Symbol() == Symbol()); // false
  3. console.log(typeof Symbol()); // symbol

不能进行赋值属性:

  1. let s1 = Symbol();
  2. s1.a = "a";
  3. console.log(s1.a); // undefind

Symbol()方法可以传递一个参数,表示对Symbol的描述:

  1. let s1 = Symbol("s1");
  2. console.log(s1); // Symbol(s1)

Symbol()的值是字符串,标识名会转换为字符串:

  1. let obj = { a: 1 };
  2. let s1 = Symbol(obj);
  3. console.log(s1); // Symbol([object Object])

使用undefinednull

  1. console.log(Symbol(undefined)); // Symbol(),undefined 表示空的,和没有传参一样
  2. console.log(Symbol(null)); // Symbol(null)

symbol不能强制转换为number数据类型:

  1. let s1 = Symbol();
  2. console.log(String(s1)); // Symbol()
  3. console.log(Boolean(s1)); // true
  4. console.log(Number(s1)); // Cannot convert a Symbol value to a number

symbol可以使用!转换为布尔值:

  1. let s1 = Symbol();
  2. console.log(!s1); // false

访问symbol数据的原型:

  1. let s1 = Symbol();
  2. console.log(Object.getPrototypeOf(s1));

image.png

举例:

  1. let name = Symbol();
  2. let person = {};
  3. // person.name = "张三"; // 无效的,不会使用上面的 Symbo
  4. person[name] = "张三"; // 必须使用 [] 表达式
  5. console.log(person); // {Symbol(): '张三'}
  6. // =======分割=======
  7. let name = Symbol();
  8. let eat = Symbol();
  9. let person = {
  10. [name]: "张三",
  11. [eat]() {
  12. console.log(this[name] + "is eat");
  13. },
  14. };
  15. person[eat](); // 张三is eat

方法

Symbol.for()

有时,我们希望重新使用同一个Symbol值,Symbol.for()方法可以做到这一点。
它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建一个以该字符串为名称的Symbol值,并将其注册到全局。

  1. let s1 = Symbol("foo");
  2. let s2 = Symbol("foo");
  3. let s3 = Symbol.for("foo");
  4. let s4 = Symbol.for("foo");
  5. console.log(s1 === s2); // false
  6. console.log(s3 === s4); // true
  7. console.log(s1 === s4); // false

Symbol.for()Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

Symbol.keyFor()

该方法返回一个已登记的Symbol类型值的key(描述名),但是只能获取Symbol.for()返回的symbol

  1. let s = Symbol("foo");
  2. let s1 = Symbol.for("foo");
  3. console.log(Symbol.keyFor(s)); // undefined
  4. console.log(Symbol.keyFor(s1)); // foo

遍历

symbol的值是不能进行遍历的。

  1. let a = Symbol();
  2. let b = Symbol();
  3. const obj = {
  4. [a]: "a",
  5. [b]: "b",
  6. };
  7. console.log(obj); // {Symbol(): 'a', Symbol(): 'b'}
  8. console.log(Object.keys(obj)); // []
  9. for (const key in obj) {
  10. console.log(key); // 空的
  11. }
  12. let newObj = Object.assign({}, obj); // 但是可以进行合并
  13. console.log(newObj); // {Symbol(): 'a', Symbol(): 'b'}

所以Object提供了一个遍历Symbol的方法Object.getOwnPropertySymbols(),该方法只能遍历symbol的值。

  1. let a = Symbol();
  2. let b = Symbol();
  3. const obj = {
  4. [a]: "a",
  5. [b]: "b",
  6. "c": "c"
  7. };
  8. // 只遍历对象的 Symbol 值
  9. console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(), Symbol()]

iterator 迭代对象

:::info 迭代器的概念:对数据结构的读取的一种方式,有序的、连续的基于拉取的一种消耗数据的组织方式; ::: Symbol()函数的构造器上有很多的属性,属性对应部署了相关的方法:

  1. let s = Symbol();
  2. console.log(Object.getPrototypeOf(s));

image.png

而我们之前使用的instanceof来判断对象是否是构造函数实例背后就是使用hasInstance方法来实现的。

而我们主要学习的是iterator: Symbol(Symbol.iterator)迭代方法。
以数组为例子,我们看看数组的原型:
image.png
可以看到数组的原型上有个Symbol(Symbol.iterator)方法。

接着我来执行一下:

  1. let arr = [1, 2, 3, 4];
  2. let iter = arr[Symbol.iterator];
  3. console.log(iter); // ƒ values() { [native code] }

因为Symbol返回是独一无二的标识,所以我们不能通过arr[Symbol("Symbol.iterator")]去访问,这样是访问一个新的Symbol标识,所以我们要直接访问Symbol函数上的iterator属性来访问迭代对象。

可以看到只返回了一个函数的标识,那我们来执行它:

  1. let arr = [1, 2, 3, 4];
  2. let iter = arr[Symbol.iterator](); // 获取迭代器接口
  3. console.log(iter); // Array Iterator {} // 返回一个迭代空对象

image.png
里面有个next方法,我们去执行它:

  1. let arr = [1, 2, 3, 4];
  2. let iter = arr[Symbol.iterator]();
  3. console.log(iter.next()); // {value: 1, done: false}
  4. console.log(iter.next()); // {value: 2, done: false}
  5. console.log(iter.next()); // {value: 3, done: false}
  6. console.log(iter.next()); // {value: 4, done: false}
  7. console.log(iter.next()); // {value: undefined, done: true}

可以看到我们每次调用next方法都会返回数组的value和表示是否完成的done

JavaScript中的数据结构有:objectarray、类数组、MapSetweakMapweakSettypeArray(二进制数据的缓存区)
其中object没有iterator的接口(方法),且数据的key不是有序的、连续的,所以不能进行迭代。

如果想要迭代以上数据结构(除object)的值的该怎么办呢?
ES6对部署了iterator接口的数据类型提供了一种统一的方式进行迭代,那就是for...of...
for...of...本身也是调用iterator.next()方法然后进行遍历。

  1. let arr = [1, 2, 3];
  2. // 迭代相应的数据结构
  3. for (const iterator of arr) {
  4. console.log(iterator); // 1 2 3
  5. }
  6. // for...in... 遍历数组
  7. for (const key in arr) {
  8. console.log(key); // 0 1 2
  9. }
  10. let obj = {
  11. name: "张三",
  12. age: 20,
  13. };
  14. // 对象是不能迭代
  15. for (const iterator of obj) {
  16. // obj is not iterable
  17. // 因为没有部署 iterable 接口
  18. // 如果想要迭代对象,必须给对象部署 iterable 接口
  19. console.log(iterator);
  20. }

根据以上的分解步骤,我们可以模拟一个iterator的接口:

  1. let arr = [1, 2, 3];
  2. function makeIterator(array) {
  3. let nextIndex = 0;
  4. // 返回一个对象,对象里有 next 方法
  5. return {
  6. next() {
  7. // 如果没有迭代完成
  8. // 返回 {value:xxx, done:false}
  9. return nextIndex < array.length
  10. ? {
  11. value: array[nextIndex++],
  12. done: false,
  13. }
  14. : {
  15. value: undefined,
  16. done: true,
  17. };
  18. // 如果迭代完成
  19. // 返回 {value:undefined, done:true}
  20. },
  21. };
  22. }
  23. let ite = makeIterator(arr);
  24. console.log(ite.next()); // {value: 1, done: false}
  25. console.log(ite.next()); // {value: 2, done: false}
  26. console.log(ite.next()); // {value: 3, done: false}
  27. console.log(ite.next()); // {value: undefined, done: true}

最后给对象新增一个iterator的接口:

  1. let obj = {
  2. start: [1, 2, 3, 4, 5],
  3. end: [10, 20, 30],
  4. [Symbol.iterator]() {
  5. let index = 0,
  6. list = [...this.start, ...this.end],
  7. len = list.length;
  8. return {
  9. next() {
  10. if (index < len) {
  11. return {
  12. value: list[index++],
  13. done: false,
  14. };
  15. } else {
  16. return {
  17. value: undefined,
  18. done: true,
  19. };
  20. }
  21. },
  22. };
  23. },
  24. };
  25. for (const iterator of obj) {
  26. console.log(iterator); // 1 2 3 4 5 10 20 30
  27. }
  28. // 部署了迭代器接口后,能够使用数组的拓展运算符
  29. let arr = [...obj];
  30. console.log(arr); // [1, 2, 3, 4, 5, 10, 20, 30]

:::danger ⚠️ 注意
部署了迭代器接口后,能够使用数组的拓展运算符。 :::