• ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值,是 JS 的第七种数据类型
    • 通过调用Symbol()函数,来生成symbol这种数据类型
      • Symbol()函数接受String 或 Number类型的参数
        1. // 感受一下symbol类型的独一无二
        2. let s1 = Symbol();
        3. let s2 = Symbol();
        4. console.log(s1); // Symbol()
        5. console.log(s2); // Symbol()
        6. console.log(typeof s1); // symbol
        7. console.log(s1 === s2); // false 这两个不相等,所以和对象属性结合,有妙用

        Symbol函数的参数

        ```javascript // Symbol(Number|String)函数的传参 let s1 = Symbol(); // Symbol() let s2 = Symbol(‘’); // Symbol() let s3 = Symbol(undefined); // Symbol() let s4 = Symbol(123); // Symbol(123) let s5 = Symbol(‘abc’); // Symbol(abc) let s6 = Symbol(null); // Symbol(null) let s3 = Symbol(false); // Symbol(false) let s7 = Symbol(NaN); // Symbol(NaN) let s8 = Symbol({}); // Symbol([object Object])

// 传入的是数组,会将数组的括号去掉。把元素按顺序拼起来,并用逗号隔开 let s9 = Symbol([4, 5, 6]); // Symbol(4,5,6); let s9 = Symbol([4, 5, {}]); // Symbol(4,5,[object Object]);

  1. - Symbol 值不能与其他类型的值进行运算,会报错
  2. - Symbol 值可以转为字符串,也可以转为布尔值,但不能转数字
  3. ```javascript
  4. // 案例一:不能与其他类型的值,进行运算
  5. let sym = Symbol('My symbol');
  6. "your symbol is " + sym
  7. // TypeError: can't convert symbol to string
  8. `your symbol is ${sym}`
  9. // TypeError: can't convert symbol to string
  1. // symbol值转字符串
  2. let s1 = Symbol('123');
  3. s1.toString(); // Symbol(123) 是 String类型
  4. // symbol值转布尔值
  5. Boolean(s1); // 必为true
  6. // 转数字报错,
  7. Number(s1); // 报错

symbol类型与对象结合

  • 由于symbol类型的值是独一无二的,若 对象A 的key(属性名)属于 symbol 类型,就不会与其他属性名产生冲突
    • ES5 的对象属性名都是字符串,这容易造成属性名的冲突
    • 比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突 ```javascript // ES5 var obj = { run: function() { console.log(1) } } obj.run = function() { console.log(2) } obj.run(); // 2

// ES6 let s1 = Symbol(‘run’) let s2 = Symbol(‘run’) let obj = { s1: () => console.log(1) } obj[s2] = () => console.log(2)

objs1; // 1 objs2; // 2

  1. - Symbol 作为属性名,需要注意
  2. - 遍历对象的时候,该属性不会出现在`for...in``for...of`循环
  3. - 也不会被`Object.keys()``Object.getOwnPropertyNames()``JSON.stringify()`返回
  4. - 所以出现两个新的 API
  5. - `Object.getOwnPropertySymbols()`:可以获取指定对象的所有 Symbol 属性名
  6. - `Reflect.ownKeys()`:可以返回所有类型的键名,包括常规键名和 Symbol 键名
  7. ```javascript
  8. // 案例一
  9. let s1 = Symbol('a');
  10. let s2 = Symbol('b');
  11. let obj = {
  12. [s1]: 1,
  13. [s2]: 2,
  14. s3: 4
  15. };
  16. // for循环,只打印了s3
  17. for(let i in obj) {
  18. console.log(i);
  19. }
  20. console.log(Object.keys(obj)); // ['s3']
  21. console.log(Object.getOwnPropertyNames(obj)); // ['s3']
  22. console.log(JSON.stringify(obj)); // {"s3":4}
  23. console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(a), Symbol(b)]
  24. console.log(Reflect.ownKeys(obj)); // ['s3', Symbol(a), Symbol(b)]

Symbol的属性和方法

方法

  • Symbol.for(key):会先检查给定的key是否在symbol 注册表已经存在,如果不存在才会新建一个值
    • 该方法会把key登记 全局 symbol 注册表中供搜索
  • Symbol.keyFor(symbol):返回一个已登记的 Symbol 类型值的key,没有就是undefined ```javascript // 假设在index.html中,引入了a.js和b.js // a.js let s1 = Symbol.for(‘test’); let s2 = Symbol(‘456’); let obj = { s1: ()=> console.log(‘test’) }

Symbol.keyFor(s1); // test Symbol.keyFor(s2); // undefined // b.js let s3 = Symbol.for(‘test’); objs3; // test

  1. <a name="cxCwg"></a>
  2. ### 属性
  3. <a name="MiZ57"></a>
  4. #### Symbol.hasInstance
  5. - `Symbol.hasInstance`:既是`Symbol`的一个属性,也是 ES6 在 `函数和 class` 的原型(`__proto__`)上内置的一个方法
  6. - 作用:当其他对象使用`instanceof`运算符,判断是否为该构造函数的实例时,会调用这个方法
  7. - 所以 ES6 提供的这个方法,可以让我们改变`instanceof`的行为
  8. - `myClass[Symbol.hasInstance](obj)`:判断`obj`实例对象 的 `constructor` 是不是 `myClass` 或 `constructor的原型链上` 有没有 `myClass`
  9. ```javascript
  10. // 案例一
  11. class MyClass {}
  12. let c1 = new MyClass;
  13. // ES5可以这么写
  14. c1 instanceof MyClass;//true
  15. [] instanceof MyClass;//false
  16. // ES6可以这么写
  17. MyClass[Symbol.hasInstance](c1); // true
  1. // 案例二
  2. // 用 Symbol.hasInstance 改变 instanceof 的行为
  3. class myClass {
  4. // 动态方法,在myClass类的prototype上
  5. // 动态方法,修改类的实例的 instanceof 的指向
  6. [Symbol.hasInstance](instanceObject) {
  7. console.log(instanceObject)
  8. return Number(instanceObject) % 2 === 0;
  9. }
  10. // 静态方法,在myClass类上
  11. // 静态方法,修改类的 instanceof 的指向
  12. static [Symbol.hasInstance](instanceObject) {
  13. return instanceObject instanceof Array;
  14. }
  15. }
  16. let test = new myClass();
  17. test instanceof myClass; // false
  18. [] instanceof myClass; // true
  19. 2 instanceof test; // true

Symbol.iterator

  • Symbol.iterator:既是Symbol的一个属性,也是 ES6 在 数据结构(Array/Map/Set/String/ TypeArray/函数arguments/NodeList) 上内置的一个方法

    • 一个数据结构 只要具有Symbol.iterator属性,就可以认为是“可遍历的”
    • 数据结构(Array/Map/Set/String/ TypeArray/函数arguments/NodeList)中,Symbol.iterator属性本身是一个函数,就是当前 数据结构 默认的遍历器 的生成函数。执行这个函数,就会返回一个遍历器
  • 遍历器(Iterator):

    • 为何出现:JS 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了MapSet。这样就有了四种数据集合,可以任意组合使用,定义自己的数据结构。比如数组的成员是MapMap的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构
    • 作用:
      • 一是为各种数据结构,提供一个统一的、简便的访问接口。
      • 二是使得数据结构的成员能够按某种次序排列。
      • 三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费
    • 遍历过程
      • 创建一个指针对象,指向当前数据结构的起始位置
        • 也就是说,遍历器对象本质上,就是一个指针对象
        • 比如:let iter = arr[Symbol.Iterator]()arr是数组的实例
        • 该对象(iter)上,有**next()**方法throw()return()方法
          • return()方法是,如果for...of循环提前退出(通常是因为出错,或者有break
          • throw()方法主要是配合 Generator函数使用,一般的遍历器对象用不到这个方法
      • 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
        • 比如:iter.next()
      • 不断调用指针对象的next方法,直到它指向数据结构的结束位置
        • 每次调用next方法,都返回一个包含valuedone两个属性的对象
          • value属性是当前成员的值
          • done属性是一个布尔值,表示遍历是否结束 ```javascript // 案例一:模拟遍历器的原理 function makeIterator(arr) { let nextIndex = 0; return { next: function () { return nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, done: true } } } } let arr = [1, 2, 5] let iter = makeIterator(arr);

console.log(iter.next()); // { value: 1, done: false } console.log(iter.next()); // { value: 2, done: false } console.log(iter.next()); // { value: 5, done: false } console.log(iter.next()); // { value: undefined, done: true }

  1. ```javascript
  2. // 案例二:用ES6内置的遍历器,代替自己模拟的遍历器
  3. let arr = [1, 2, 5]
  4. let iter = arr[Symbol.iterator]();
  5. console.log(iter.next()); // { value: 1, done: false }
  6. console.log(iter.next()); // { value: 2, done: false }
  7. console.log(iter.next()); // { value: 5, done: false }
  8. console.log(iter.next()); // { value: undefined, done: true }
  • 对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定
    • 本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。
    • 不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了 ```javascript // 案例三:给对象 部署 Iterator 接口 Object.prototype[Symbol.iterator] = function () { let nextIndex = 0; return { next: () => { let keys = Reflect.ownKeys(this); return nextIndex < keys.length ? { value: this[keys[nextIndex++]], done: false } : { value: undefined, done: true } } } }

let s1 = Symbol(); let obj = { a: 55,

};

// Object对象有了Iterator接口,下面这些场合就能用了 // for…of for(let val of obj) { console.log(val); }

// 顺带复习ES5的for in for(let key in obj) {}

// 扩展运算符… let arr = […obj]; // [55, 123]

// 用Array.fromz转成数组 let arr2 = Array.from(obj); // [55, 123]

  1. ```javascript
  2. // 案例四:自定义对象的遍历器的 return() 方法
  3. // return()方法必须返回一个对象,这是 Generator 语法决定的
  4. Object.prototype[Symbol.iterator] = function () {
  5. let nextIndex = 0;
  6. return {
  7. next: () => {
  8. let keys = Reflect.ownKeys(this);
  9. return nextIndex < keys.length ?
  10. { value: this[keys[nextIndex++]], done: false }
  11. : { value: undefined, done: true }
  12. },
  13. // 添加return()方法
  14. return() {
  15. console.log('return');
  16. return { done: true };
  17. }
  18. }
  19. }
  20. let s1 = Symbol();
  21. let obj = {
  22. a: 55,
  23. b: 2,
  24. [s1]: 123,
  25. };
  26. for (let val of obj) {
  27. if (val === 2) {
  28. break;
  29. }
  30. }
  • 会调用 Iterator 遍历器的场合
    • 解构赋值
    • 扩展运算符
    • yield*
    • for...of
    • Array.from()
    • Map()、Set()、WeakMap()、WeakSet()
      • 比如:new Map([ ['a', 1], ['b', 2] ])
    • Promise.all()、Promise.race()

Symbol 的应用

  1. 用于解决属性名冲突问题,构造唯一的属性名和变量
  2. 作为私有属性 ```javascript function Child(name) { const symbol = symbol() this[symbol] = ‘my symbol’ this.name = name; }

const obj = new Child(‘konsoue’) obj.name // konsoue // symbol 拿不到

  1. 3. 使得对象可遍历
  2. ```javascript
  3. const obj = {
  4. count: 0,
  5. [Symbol.iterator]: () => {
  6. return {
  7. next: () => {
  8. obj.count++
  9. if (obj.count <= 10) {
  10. return {
  11. value: obj.count,
  12. done: false
  13. }
  14. } else {
  15. return {
  16. value: undefined,
  17. done: true
  18. }
  19. }
  20. }
  21. }
  22. }
  23. }
  24. for (let item of obj) {
  25. console.log(item); // 1 2 3 4 5 6 7 8 9 10
  26. }
  1. const obj = {
  2. count: 0,
  3. name: 'konsoue',
  4. add: () => this.count++,
  5. [Symbol.iterator]: function() {
  6. let keyIndex = 0;
  7. const keys = Object.keys(this);
  8. const objWithNext = {
  9. next: () => {
  10. if (keyIndex === keys.length) return { value: undefined, done: true };
  11. const result = {
  12. value: this[keys[keyIndex]],
  13. done: false
  14. }
  15. keyIndex++
  16. return result;
  17. }
  18. }
  19. return objWithNext
  20. }
  21. };
  22. for (let item of obj) {
  23. console.log(item)
  24. }
  1. Object.prototype[Symbol.iterator] = function () {
  2. let keyIndex = 0;
  3. const keys = Object.keys(this);
  4. const targetWithNext = {
  5. next: () => {
  6. if (keyIndex === keys.length) return { value: undefined, done: true };
  7. const result = {
  8. value: this[keys[keyIndex]],
  9. done: false
  10. }
  11. keyIndex++
  12. return result;
  13. }
  14. }
  15. return targetWithNext
  16. }