重学 Symbol

重学 Symbol

之前在写基础类型的笔记时暂时性的先跳过了 symbol,现在也有了一些项目的使用经验后,觉得还是需要重新回滚并且学习一下,温故而知新。

首先依旧回顾一下 symbol 的特点:

  • 是原始值

  • 唯一

  • 不可变

  • 可以提供私有属性,模仿其他语言中的 private

  • 不可以使用 new 关键词

    这是因为 JS 在实现的时候刻意避免了使用 new 关键词去调用原始值包装类型

  • Symbol 作为对象的 key 时不可被枚举

Symbol 的基本用法

虽然 Symbol 可以模拟私有类型,不过其刚开始被创造出来的意义是为了创建唯一属性,而非私有属性。

以下面代码为例:

const uid = Symbol('uid');

export const item = {
    
    
  [uid]: 'p1',
};
import {
    
     item } from './obj.js';

const uid = Symbol('uid');
item[uid] = 'p2';
console.log(item);

上面两段代码分别属于不同的 module,第一段中的 symbol 也没有被 expor。换言之,在其他地方使用的 item 这个对象的代码是无法获取到生成时创建的 symbol。

第二段代码用同样的字符串新创建了一个 Symbol,如果是其他属性的话,那么这时候 item 中对应的值可能就被重写了,不过 Symbol 不是:

在这里插入图片描述

可以看到,尽管两个 Symbol 中的字符串是一样的,但是 JS 会创建一个完全不同的 Symbol:

const uid2 = Symbol('uid');

console.log(uid === uid2);

在这里插入图片描述

搭配上 Symbol 不可被枚举的特性,就可以模拟成一个私有属性:

const uid = Symbol('uid');

item[uid] = 'p2';

console.log(item);
item.key = 'value';

console.log(Object.keys(item)); // 只有 key

在这里插入图片描述

项目中的使用案例

也因此,在我们的项目中,也会使用过 Symbol 去创建一些 ref 值(不需要被传到 API 中去的值),如:

class Example {
    
    
  constructor() {
    
    
    const privateKey = Symbol('private');
    this[privateKey] = 'private id';
    this.id = 0;
    this.value = 'value';
  }

  messageObj = () => {
    
    
    console.log(Object.keys(this));

    const messageObj = {
    
    };
    for (const key of Object.keys(this)) {
    
    
      if (!(this[key] instanceof Object)) {
    
    
        messageObj[key] = this[key];
      }
    }

    return messageObj;
  };
}

const example = new Example();
console.log(example.messageObj());

在这里插入图片描述

Symbol 的常用方法和属性

方法

我们的项目中用的是 export 的方法,不过怎么说呢……之前的开发做 ios 的,对 JS 不是很熟,正确的全局方法应该使用 Symbol.for() 去实现:

const sym1 = Symbol.for('shared symbol');
const sym2 = Symbol.for('shared symbol');

console.log(sym1 === sym2);

// 对比
const sym3 = Symbol('shared symbol');
console.log(sym3 === sym1);

在这里插入图片描述

如果要查找 symbol 是否被挂在到了全局分享上,就可以使用 keyFor() 进行查找:

console.log(Symbol.keyFor(sym3));
console.log(Symbol.keyFor(sym1));

在这里插入图片描述

鉴于每次调用 Symbol.for,JS 都会进行查找,如果没有注册当前输入的值,就会自动创建一个新的值这一操作,这个方法的使用范围还是比较有限的。

另外一个可能用得到的方法是 toString()

属性

属性又名 常用内置符号(Well-known Symbols),是 JS 内部暴露出来的,一些属于对象的内置属性。一些非常常用的功能(尤其是数组、set、map)都是 JS 内部实现的,不过也有一些情况下需要我们手动重写。

instanceof 就会隐式调用 [Symbol.hasInstance],而 [Symbol.isConcatSpreadable] 则是一个 boolean,这个值决定当 Array.prototype.concat() 中存在当前值是,它是会将当前值扁平化,还是保存为一个 array-like object 推进去。

目前实现的属性有下面这些:

在这里插入图片描述

对于日常开发来说,最重要的的是 iterator,其他的属性相对而言用的比较少。

另一个比较好用的是 toStringTag,对于开发和 debug 比较有帮助。这里不会列举所有的属性,毕竟这部分如果真的要用,参考 MDN 文档会更 up-to-date。

Symbol.toStringTag

主要使用方法如下:

class Example {
    
    }

const ex1 = new Example();
console.log(ex1.toString());

class Example2 {
    
    
  get [Symbol.toStringTag]() {
    
    
    return 'Example2';
  }
}

const ex2 = new Example2();
console.log(ex2.toString());

function Example3() {
    
    }
Object.defineProperty(Example3.prototype, Symbol.toStringTag, {
    
    
  get: function () {
    
    
    return 'Example3';
  },
});

const ex3 = new Example3();
console.log(ex3.toString());

function Example4() {
    
    }
Example4.prototype[Symbol.toStringTag] = 'Example4';

const ex4 = new Example4();
console.log(ex4.toString());

这个相对而言用于开发、debug 还是有一定效果的:

在这里插入图片描述

Symbol.iterator

Symbol.iterator 实现的方法是对象中的迭代器,所有可迭代对象——在 JS 中,这代表可以使用 for...of, [...arr] 或是 const [a, b, c] = arr 这种语法——在底层都会查找实现的迭代器。

这里继续举一个例子,如:

class Obj {
    
    
  constructor() {
    
    
    this.idx = 0;
    this.arr = [1, 2, 3];
  }

  *[Symbol.iterator]() {
    
    
    while (this.idx < this.arr.length) {
    
    
      yield this.arr[this.idx++];
    }
  }
}

const obj = new Obj();
for (const val of obj) {
    
    
  console.log(val);
}

const [a, b, c] = new Obj();
a;
b;
c;

在这里插入图片描述

Symbol.asyncIterator

本来写了个使用案例,后来发现那个更加偏向于 generator 的使用方式,没设么必要迭代实现,这里就丢一个 MDN 的案例:

const delayedResponses = {
    
    
  delays: [500, 1300, 3500],

  wait(delay) {
    
    
    return new Promise((resolve) => {
    
    
      setTimeout(resolve, delay);
    });
  },

  async *[Symbol.asyncIterator]() {
    
    
    for (const delay of this.delays) {
    
    
      await this.wait(delay);
      yield `Delayed response for ${
      
      delay} milliseconds`;
    }
  },
};

(async () => {
    
    
  for await (const response of delayedResponses) {
    
    
    console.log(response);
  }
})();

// Expected output: "Delayed response for 500 milliseconds"
// Expected output: "Delayed response for 1300 milliseconds"
// Expected output: "Delayed response for 3500 milliseconds"

这里主要还是搭配了一个异步的实现,可以在 for...of 循环中使用 await 去等结果。

猜你喜欢

转载自blog.csdn.net/weixin_42938619/article/details/130893848