symbol 特性

  • 唯一,不会重名

    1. Symbol('my') !== Symbol('my') // true my 只是描述符
  • 不可修改

  • 不可枚举
    Symbols 在 for…in 迭代中不可枚举。另外,Object.getOwnPropertyNames() 不会返回 symbol 对象的属性
  • 不能和其他类型的值进行运算
    ```javascript let sym = Symbol(‘My symbol’);

“your symbol is “ + sym // TypeError: can’t convert symbol to string your symbol is ${sym} // TypeError: can’t convert symbol to string

  1. - 不能用点运算符。<br />
  2. ```javascript
  3. const mySymbol = Symbol();
  4. const a = {};
  5. a.mySymbol = 'Hello!';
  6. a[mySymbol] // undefined
  7. a['mySymbol'] // "Hello!"

创建局部的 symbol

  1. // 使用 Symbol函数创建
  2. // Symbol([description])
  3. var mysymbol = Symbol("my symbol");
  4. var mysymbol1 = Symbol('my symbol');
  5. // symbol 可以具有字符串类型的描述,但是即使描述相同,symbol也不相等。
  6. // 每次都会创建一个新的 symbol类型:
  7. mysymbol == mysymbol1 // false
  8. var obj = {};
  9. obj[mysymbol] = function () {
  10. console.log("mysymbol")
  11. }
  12. obj[mysymbol1] = function () {
  13. console.log("mysymbol1")
  14. }
  15. obj // {Symbol(my symbol): ƒ, Symbol(my symbol): ƒ}
  16. // 使用 Symbol 定义的对象属性不会重名

创建 symbol 包装对象

不能使用new Symbol,因为生成的值不是对象,而是一种 symbol 的基本数据类型,因为 es6开始不支持创建显示的包装器对象,可以使用 Object()函数创建

  1. var sym = Symbol("foo");
  2. typeof sym; // "symbol"
  3. var symObj = Object(sym);
  4. typeof symObj; // "object"
  5. var obj = {[sym]: 1};
  6. obj[sym] // 1
  7. obj[symObj]; // still 1

在对象中查找 Symbol 属性

  1. Object.getOwnPropertySymbols(obj) // (2) [Symbol(my symbol), Symbol(my symbol)]

内置的symbol值

一些标准中提到的 symbol,可以在全局的 symbol 函数的属性中找到。

Symbol.hasInstance

对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance(foo)]。

  1. class MyClass {
  2. [Symbol.hasInstance](foo) {
  3. return foo instanceof Array;
  4. }
  5. }
  6. [1, 2, 3] instanceof new MyClass() // true

Symbol.isConcatSpreadable

对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。

  1. let arr1 = ['c', 'd'];
  2. ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
  3. arr1[Symbol.isConcatSpreadable] // undefined
  4. let arr2 = ['c', 'd'];
  5. arr2[Symbol.isConcatSpreadable] = false;
  6. ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']

上面代码说明,数组的默认行为是可以展开,Symbol.isConcatSpreadable默认等于undefined。该属性等于true时,也有展开的效果。
类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable属性设为true,才可以展开。

  1. let obj = {length: 2, 0: 'c', 1: 'd'};
  2. ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
  3. obj[Symbol.isConcatSpreadable] = true;
  4. ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']

Symbol.isConcatSpreadable属性也可以定义在类里面。

  1. class A1 extends Array {
  2. constructor(args) {
  3. super(args);
  4. this[Symbol.isConcatSpreadable] = true;
  5. }
  6. }
  7. class A2 extends Array {
  8. constructor(args) {
  9. super(args);
  10. }
  11. get [Symbol.isConcatSpreadable] () {
  12. return false;
  13. }
  14. }
  15. let a1 = new A1();
  16. a1[0] = 3;
  17. a1[1] = 4;
  18. let a2 = new A2();
  19. a2[0] = 5;
  20. a2[1] = 6;
  21. [1, 2].concat(a1).concat(a2)
  22. // [1, 2, 3, 4, [5, 6]]

注意,Symbol.isConcatSpreadable的位置差异,A1是定义在实例上,A2是定义在类本身,效果相同。

Symbol.species

对象的Symbol.species属性,指向一个构造函数。创建衍生对象时,会使用该属性。

  1. class MyArray extends Array {
  2. }
  3. const a = new MyArray(1, 2, 3);
  4. const b = a.map(x => x);
  5. const c = a.filter(x => x > 1);
  6. b instanceof MyArray // true
  7. c instanceof MyArray // true

上面代码中,子类MyArray继承了父类ArrayaMyArray的实例,bca的衍生对象。你可能会认为,bc都是调用数组方法生成的,所以应该是数组(Array的实例),但实际上它们也是MyArray的实例。
Symbol.species属性就是为了解决这个问题而提供的。现在,我们可以为MyArray设置Symbol.species属性。

  1. class MyArray extends Array {
  2. static get [Symbol.species]() { return Array; }
  3. }

上面代码中,由于定义了Symbol.species属性,创建衍生对象时就会使用这个属性返回的函数,作为构造函数。这个例子也说明,定义Symbol.species属性要采用get取值器。默认的Symbol.species属性等同于下面的写法。

  1. static get [Symbol.species]() {
  2. return this;
  3. }

现在,再来看前面的例子。

  1. class MyArray extends Array {
  2. static get [Symbol.species]() { return Array; }
  3. }
  4. const a = new MyArray();
  5. const b = a.map(x => x);
  6. b instanceof MyArray // false
  7. b instanceof Array // true

Symbol.match

对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。

  1. String.prototype.match(regexp)
  2. // 等同于
  3. regexp[Symbol.match](this)
  4. class MyMatcher {
  5. [Symbol.match](string) {
  6. return 'hello world'.indexOf(string);
  7. }
  8. }
  9. 'e'.match(new MyMatcher()) // 1

Symbol.replace

对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。

  1. String.prototype.replace(searchValue, replaceValue)
  2. // 等同于
  3. searchValue[Symbol.replace](this, replaceValue)

下面是一个例子。

  1. const x = {};
  2. x[Symbol.replace] = (...s) => console.log(s);
  3. 'Hello'.replace(x, 'World') // ["Hello", "World"]

Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,上面例子是Hello,第二个参数是替换后的值,上面例子是World

Symbol.toPrimitive

Symbol.toStringTag

Symbol.iterator

我们可以使用 Symbol.iterator 来自定义 for…of 在对象上的行为:

  1. var o = new Object
  2. o[Symbol.iterator] = function() {
  3. var v = 0
  4. return {
  5. next: function() {
  6. return { value: v++, done: v > 10 }
  7. }
  8. }
  9. };
  10. for(var v of o)
  11. console.log(v); // 0 1 2 3 ... 9

代码中我们定义了iterator之后,用for(var v of o)就可以调用这个函数,然后我们可以根据函数的行为,产生一个for…of的行为。

这里我们给对象o添加了 Symbol.iterator 属性,并且按照迭代器的要求定义了一个0到10的迭代器,之后我们就可以在for of中愉快地使用这个o对象啦。

  1. const s = symbol('foo');
  2. console.log(s.toString()); // symbol(foo)
  3. // es2019
  4. console.log(s.description); // foo

Symbol.asyncIterator

**Symbol.asyncIterator** 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于[for await...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of)循环。

你可以通过设置[Symbol.asyncIterator]属性来自定义异步可迭代对象。

  1. const myAsyncIterable = new Object();
  2. myAsyncIterable[Symbol.asyncIterator] = async function*() {
  3. yield "hello";
  4. yield "async";
  5. yield "iteration!";
  6. };
  7. (async () => {
  8. for await (const x of myAsyncIterable) {
  9. console.log(x);
  10. // expected output:
  11. // "hello"
  12. // "async"
  13. // "iteration!"
  14. }
  15. })();

创建全局共享的 Symbol

Symbol.for()

它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

  1. Symbol.for('foo'); === Symbol.for('foo'); // true
  2. Symbol("bar") === Symbol("bar") // false

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

Symbol.keyFor()

方法返回一个已登记的 Symbol 类型值的key

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

Symbol.for()的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同一个值。

  1. iframe = document.createElement('iframe');
  2. iframe.src = String(window.location);
  3. document.body.appendChild(iframe);
  4. iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
  5. // true

实例

消除魔术字符串

  1. // 如果使用到一个字符串
  2. // 可以写成变量,定义枚举
  3. const type = {
  4. type1: 'type1',
  5. type2: 'type2'
  6. }
  7. // type.type1 等于什么值不重要,只要确保不会跟其他属性的值冲突即可
  8. const type = {
  9. type1: Symbol('type1'),
  10. type2: Symbol('type2')
  11. }

属性名遍历

Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。
但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

Reflect.ownKeys()方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

  1. let obj = {
  2. [Symbol('my_key')]: 1,
  3. enum: 2,
  4. nonEnum: 3
  5. };
  6. Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]

由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。

  1. var symSex = Symbol('sex')
  2. function Func (name,sex) {
  3. this[symSex] = sex;
  4. this.name = name;
  5. }
  6. var func = new Func("foo",'ladayboy')
  7. Object.getOwnPropertyNames(func) // ['name']