symbol

  • 新的原始数据类型,表示独一无二的值,可以理解为类似字符串的数据类型
  • 不可使用 new,可接收一个字符串作为参数,该字符串作为实例的描述

description 属性

ES2019 提供了一个实例属性 description,直接返回 Symbol 的描述

let s = Symbol('sss');
s.toString(); // 'Symbol(sss)'
s.description; // sss

如果参数是一个对象,则会调用该对象的 toString 方法,将其转为字符串

let obj = {
    toString() {
        return 'abc';
    },
};
let s2 = Symbol(obj);
s2; // 'Symbol(abc)'

不能与其他类型的值进行运算

let s3 = Symbol('s3');
'aaa' + s3; // 错误

可显式转为字符串

String(s3);
s3.toString();

可转为布尔值,但是不能转为数值

let s4 = Symbol('s4');
Boolean(s4); // 正确
Number(s4); // 错误

作为对象的属性名时,需加中括号,取值时不可通过点运算符来访问,当通过 Object.defineProperty 设置时,无需加中括号

let obj2 = {
    [s]: 'hello',
    s: 'world',
};
obj2[s];
obj2.s;

Symbol 作为属性名时,该属性不会出现在 for...infor...of 循环中,也不会被 Object.keys()Object.getOwnPropertyNames()JSON.stringify() 返回

Object.getOwnPropertySymbols() 可获取指定对象的所有 Symbol 属性名,返回该键的 Symbol

Object.getOwnPropertySymbols(obj2); // ['Symbol(sss)']

Reflect.ownKeys() 可获取所有类型的键

Symbol.for()

重复使用同一个 Symbol 值

接受一个字符串作为参数,然后搜索是否有该参数作为名称的 Symbol 值,有就返回该值,没有就新建一个,并注册到全局。Symbol() 不会被登记到全局环境,因此就算使用同一个字符串作参数,返回的实例也不相等

let s5 = Symbol.for('s5');
let s6 = Symbol.for('s5');
s5 === s6; // true

Symbol.keyFor()

返回一个已登记的 Symbol 类型值的 key,若未登记则返回 undefined

let s7 = Symbol.for('s7');
let s8 = Symbol('s8');
Symbol.keyFor(s7); // s7
Symbol.keyFor(s8);

内置的 Symbol 值

Symbol.hasInstance

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

Symbol.isConcatSpreadable

该属性等于一个布尔值,表示该对象用于 Array.prorotype.concat() 时,是否可以展开,默认值为 undefined,当该值为默认值或者 true 时,表示可展开

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

Symbol.species

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

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

上面示例中,b、c 虽然调用数组方法生成,但是它们同时也是 MyArray 的实例

Symbol.species 就是用来解决该问题的,定义 Symbol.species 时需要采用 get 取值器

以下示例更改了 MyArray2 的 Symbol.species,使其返回 Object

class MyArray2 extends Array {
    static get [Symbol.species]() {
        return Object;
    }
}
let a2 = new MyArray2();
let b2 = a.map(x => x);
b instanceof MyArray2; // false
b instanceof Object; // true

Symbol.replace

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

如:String.prototype.replace(searchValue, replaceValue),等同于:searchValue[Symbol.replace](this, replaceValue)

接受两个参数,第一个是 replace 方法正在作用的对象(hello),第二个是替换后的值(world)

let x = {};
x[Symbol.replace] = (s, b) => console.log(s, b);
'hello'.replace(x, 'world');

该属性指向一个方法,String.prototype.search

如:String.prototype.search(regexp),等同于:regexp[Symbol.search](this)

Symbol.split

该属性指向一个方法,String.prototype.split

如:String.prototype.split(separator, limit),等同于:separator[Symbol.split](this, limit)

Symbol.iterator

该属性指向该对象的默认遍历器方法

对象进行 for...of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器

let myIterator = {};
myIterator[Symbol.iterator] = function () {
    return {
        next() {
            if (this._first) {
                this._first = false;
                return { value: 'ok', done: false };
            } else {
                return { done: true };
            }
        },
        _first: true,
    };
};
console.log([...myIterator]);

Symbol.toPrimitive

该属性指向一个方法,当对象被转为原始类型值时,会调用该方法

返回该对象对应的原始类型值

接受一个字符串参数,表示当前运算的模式:

  • Number:需转成数值
  • String:需转成字符串
  • Defalut:可以转成数值,也可转成字符串
let obj3 = {
    [Symbol.toPrimitive](hint) {
        switch (hint) {
            case 'number':
                return 123;
            case 'string':
                return 'str';
            case 'default':
                return 'default';
            default:
                throw new Error();
        }
    },
};
console.log(2 * obj3);
console.log(3 + obj3);
console.log(obj3 == 'default');
console.log(String(obj3));

Symbol.toStringTag

该属性指向一个方法

在该对象上调用 Object.prototype.toString 方法时,如果这个属性存在,它的返回值会出现在 toString 方法返回的字符串之中,表示对象的类型

也就是说,该属性可以用来定制 [object Object][object Array]object 后面那个字符串

{ [Symbol.toStringTag]: 'Foo' }.toString() // [object Foo]
class Collection {
    get [Symbol.toStringTag]() {
        return 'xxx';
    }
}
let cc = new Collection();
Object.prototype.toString.call(cc) // [object xxx]

ES6 新增内置对象的 Symbol.toStringTag 属性值如下:

  • JSON[Symbol.toStringTag]:'JSON'
  • Math[Symbol.toStringTag]:'Math'
  • Module 对象 M[Symbol.toStringTag]:'Module'
  • ArrayBuffer.prototype[Symbol.toStringTag]:'ArrayBuffer'
  • DataView.prototype[Symbol.toStringTag]:'DataView'
  • Map.prototype[Symbol.toStringTag]:'Map'
  • Promise.prototype[Symbol.toStringTag]:'Promise'
  • Set.prototype[Symbol.toStringTag]:'Set'
  • %TypedArray%.prototype[Symbol.toStringTag]:'Uint8Array'等
  • WeakMap.prototype[Symbol.toStringTag]:'WeakMap'
  • WeakSet.prototype[Symbol.toStringTag]:'WeakSet'
  • %MapIteratorPrototype%[Symbol.toStringTag]:'Map Iterator'
  • %SetIteratorPrototype%[Symbol.toStringTag]:'Set Iterator'
  • %StringIteratorPrototype%[Symbol.toStringTag]:'String Iterator'
  • Symbol.prototype[Symbol.toStringTag]:'Symbol'
  • Generator.prototype[Symbol.toStringTag]:'Generator'
  • GeneratorFunction.prototype[Symbol.toStringTag]:'GeneratorFunction'

Symbol.unscopables

该属性指向一个对象,当对象使用了 with 关键字时,哪些属性会被 with 环境排除

此处输出的结果表明数组的这些属性会被 with 命令排除

Array.prototype[Symbol.unscopables];
console.log(Object.keys(Array.prototype[Symbol.unscopables]));

没有 unscopables

class MyClass {
    foo() {
        return 1;
    }
}
var foo = function () {
    return 2;
};
with (MyClass.prototype) {
    console.log(foo());
}

有 unscopables

class MyClass2 {
    foo2() {
        return 1;
    }
    get [Symbol.unscopables]() {
        return {
            foo2: true,
        };
    }
}
var foo2 = function () {
    return 2;
};
with (MyClass2.prototype) {
    console.log(foo2());
}

上面代码通过指定 Symbol.unscopables 属性,使得 with 语法块不会在当前作用域寻找 foo 属性,即 foo 将指向外层作用域的变量

Last Updated:
Contributors: zhangfei