Symbol

233 阅读4分钟

概述

ES6 引入新的原始数据类型 Symbol ,表示独一无二的值(所有 Symbol 类型的值都不相等)。Symbol 不是对象,所以不能使用 new 命令,也不能添加属性。

如果 Symbol 的参数是一个对象,先调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。

Symbol 值不能和其他类型的值进行运算,否则会报错。

Symbol 值可以显示转为字符串。

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

let sym = Symbol('hahaha');
"my Symbol is " + sym // TypeError:can't convert symbol to string

String(sym); // 'Symbol(hahaha)'
sym.toString(); // 'Symbol(hahaha)'

Boolean(sym); // true
!sym ; // false

Number(sym); // TypeError
sym + 2; // TypeError

作为属性名的 Symbol

Symbol 值作为对象属性名时不能使用点运算符。因为点运算符后面总是字符串,所以不会读取 Symbol 值作为标识名所指代的值。

let mySymbol = Symbol();
let anSymbol = Symbol();
let a = {};

a.mySymbol = 'hh';
a[anSymbol] = 'aa';

a.anSymbol // undefined
a[anSymbol] 'aa'
a[mySymbol] // undefined
a['mySymbol'] // 'hh'

同理,使用 Symbol 值定义属性时,也必须放在方括号中。

let s = Symbol();
let obj = {
    [s](arg) {}
}

属性名的遍历

遍历 Symbol 值作为属性名的属性,只能通过 Object.getOwnPropertySymbols() 方法和 Reflect.ownKeys() 方法

Symbol.for()、Symbol.keyFor()

Symbol.for() 接收一个字符串作为参数,然后全局搜索有没有该参数作为名称的 Symbol 值。有的话就返回这个 Symbol 值,没有就新建并返回一个以该字符串为名称的 Symbol 值。这样就可以借助该方法重新使用同一个 Symbol 值。

Symbol.for() 和 Symbol() 的区别——Symbol.for() 有登记机制,Symbol() 没有

Symbol.keyFor() 方法返回一个已登记的 Symbol 类型值的 key。

Symbol.for('bar') === Symbol.for('bar'); // true
Symbol('bar') === Symbol('bar'); // false

let s1 = Symbol.for('foo');
let s2 = Symbol('foo');
Symbol.keyFor(s1); // 'foo'
Symbol.keyFor(s2); // undefined

内置的 Symbol 值

Symbol.hasInstance

用于判断某对象是否为某构造器的实例。使用 instanceof 操作符时实际上是调用该方法

class Even {
    static [Symbol.hasInstance] (obj) {
        return Number(obj) % 2 === 0;
    }
}

1 instanceof Even; // false
2 instanceof Even; // true
123423 instanceof Even; // false

Symbol.isConcatSpreadable

该属性等于一个布尔值,表示对象使用 Array.prototype.concat() 时是否可以展开。true 表示可以展开,数组和类数组对象的默认值都是 undefined,数组默认可以展开,类数组对象(这里的类数组对象特指那些属性名为:0、1、2、3...这样的对象,属性名为字符串的类数组对象展开的话值都是 undefined)需要把该属性设置为 true 才可以展开。

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

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

Symbol.species

该属性指向当前对象的构造函数,创造实例时,会调用该属性返回的函数当作构造函数来创造新的实例对象。定义 Symbol.species 属性要采用 get 读取器。

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

let a = new MyArray(1, 2, 3);
let mapped = a.map(x => x * x);
mapped instanceof MyArray // false
mapped instanceof Array // true

Symbol.match

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

String.prototype.match(regexp);
// 等同于
regexp[Symbol.match] (this)

class MyMatcher {
    [Symbol.match] (string) {
        return 'hello world'.indexOf(string);
    }
}
'e'.match(new MyMatcher()); // 1

Symbol.replace

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

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

Symbol.search

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

class MySearch {
    constructor(value) {
        this.value = value;
    }
    [Symbol.search](string) {
        return string.indexOf(this.value);
    }
}
'foobar'.search(new MySearch('foo')); // 0

Symbol.split

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

class MySplitter {
    constructor(value) {
        this.value = value;
    }
    [Symbol.split](string) {
        var index = string.indexOf(this.value);
        if(index === -1) {
            return string;
        }
        return [
            string.substr(0, index),
            string.substr(index + this.value.length)
        ];
    }
}

'foobar'.split(new MySplitter('foo')); // ['', 'bar']
'foobar'.split(new MySplitter('bar')); // ['foo', '']
'foobar'.split(new MySplitter('baz')) // 'foobar'

Symbol.iterator

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

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

[...myIterable] // [1, 2, 3]

Symbol.toPrimitive

该属性指向一个方法,对象被转换为原始类型的值时,会调用这个方法,返回该对象的原始类型值。

Symbol.toPrimitives 被调用时会接收一个字符串参数,表示当期运算的模式。一共有 3 种模式:

  • Number:需要转换为数值
  • String:需要转换为字符串
  • Default:可以转成数值,也可以转成字符串
let obj = {
    [Symbol.toPrimitive](hint) {
        switch (hint) {
            case 'number':
                return 123;
            case 'string':
                return 'str';
            case 'default':
                return 'default';
            default:
                throw new Error();
        }
    }
}

2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'

Symbol.toStringTag

该属性指向一个方法,在对象调用 Object.prototype.toString 方法时,如果该方法存在,其返回值会出现在 toString 方法返回的字符串中,表示对象的类型。即,这个属性可以用于定制 [object Object] 或 [object Array] 中 object 后面的字符串。

({[Symbol.toStringTag]: 'Foo'}.toString()) // '[object Foo]'

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

Symbol.unscopables

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

// 没有 unscopables 时
class MyClass {
    foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
    foo(); // 1
}

//有 uncsopables 时
class MyClass {
    foo() { return 1; }
    get [Symbol.unscopables]() {
        return { foo: true };
    }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
    foo(); // 2
}