Symbol(理解和使用)

603 阅读5分钟

1. 概念:

symbol 是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

2. 使用

2.1 你可以在调用Symbol()函数时传入一个可选的字符串参数,相当于给你创建的Symbol实例一个描述信息:(仅仅是对于这个Symbol的描述,可以理解为是注释,即使字符串参数相同,但是Symbol类型的变量并不会相等)
//创建Symbol
let s = Symbol(); //函数
console.log(s, typeof s); //23-Symbol的基本使用.html:15 Symbol() 'symbol'
let str = 'another symbol';
let s1 = Symbol(str); //相当于let s1 = Symbol('another symbol')
let s2 = Symbol('YY');
let s3 = Symbol('YY');
console.log(s1, s2,s3); //Symbol(another symbol) Symbol(YY) Symbol(YY)
console.log(s2 == s3); //false
2.2 Symbol.for() 不管在哪里调用,都会被注册登记到全局环境

Symbol为我们提供了一个方法:Symbol.for(),它接收一个字符串作为参数(可选),然后会在全局中搜索有没有以该参数作为描述的Symbol值,如果有则直接返回该Symbol,否则将以该参数作为描述创建一个新的Symbol值,并将其注册的全局环境供搜索(另外: Symbol.for() 在创建Symbol值时是登记在全局环境中的, 不管有没有在全局环境运行)

let s4 = Symbol.for('SXL'); //函数对象
let s5 = Symbol.for('SXL'); 
let s6 = Symbol.for();
console.log(s4, s5, s6); //Symbol(SXL) Symbol(SXL) Symbol(undefined)
console.log(s4 == s5); //true
console.log(s4 === s5); //true
console.log(s4 == s6); //false

在这里插入图片描述 图片引自MDN:developer.mozilla.org/zh-CN/docs/…

Symbol.keyFor(sym) 方法用来获取全局symbol 注册表中与某个 symbol 关联的键。 如果全局注册表中查找到该symbol,则返回该symbol的key值,返回值为字符串类型。否则返回undefined

// 创建一个全局 Symbol
var globalSymbol = Symbol.for("global");
console.log(typeof Symbol.keyFor(globalSymbol), Symbol.keyFor(globalSymbol)); //string global
let sy = Symbol('yy')
console.log(Symbol.keyFor(sy)); //undefined
2.3 不能与其他数据进行运算或者是隐式转换
let s7 = Symbol();
let re1 = s7 + 100; // Cannot convert a Symbol value to a number
let re2 = s7 > 100; // Cannot convert a Symbol value to a number
let re3 = s7 + ''; // Cannot convert a Symbol value to a string
let re4 = s7 + s7; // Cannot convert a Symbol value to a number
2.4 toString()、String()、Boolean()可以进行强制转换
let s8 = Symbol('symbol');
console.log(typeof s8.toString(), s8.toString()); //string Symbol(symbol)
console.log(typeof String(s8), String(s8)); //string Symbol(symbol)
console.log(typeof Boolean(s8), Boolean(s8)); //boolean true
2.5 Symbol.hasInstance

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

class Person {}
var person = new Person()
console.log(person instanceof Person); // true
console.log(Person[Symbol.hasInstance](person));//true

重写Symbol.hasInstance方法,实际是重写了instanceof方法

class Person {
    constructor(age) {
        this.age = age;
    };
    static[Symbol.hasInstance](param) {
        return param % 2 === 0; //自己控制类型检测   true|false
    }
}
let person = new Person('2');
console.log(person instanceof Person);
console.log(person.age instanceof Person);
console.log(2 instanceof Person);
console.log(Person[Symbol.hasInstance](person));
console.log(Person[Symbol.hasInstance](person.age));
console.log(Person[Symbol.hasInstance](2));
完整示例:
class Person {
    [Symbol.hasInstance](param) {
        return param instanceof Array;
    }

    static[Symbol.hasInstance](param) {
        return Number(param) % 2 === 0;
    }
}
var person = new Person()
console.log([1, 2, 3] instanceof new Person()); // true 调用动态方法
console.log([1, 2, 3] instanceof Person); // false 调用静态方法
console.log([1, 2, 3] instanceof person); // true 调用实例(动态)方法
console.log(person[Symbol.hasInstance]([0, 1])); //true 调用动态方法
console.log(Person[Symbol.hasInstance]([0, 1])); //false 调用静态方法
console.log(person[Symbol.hasInstance]([0])); //true 调用动态方法
console.log(Person[Symbol.hasInstance]([0])); //true 调用静态方法
console.log(2 instanceof Person); //true 调用静态方法
console.log(Person[Symbol.hasInstance](2)); //true 调用静态方法
console.log(person instanceof Person); //false 因为重写了静态方法
2.6 Symbol.split、Symbol.match、Symbol.replace、Symbol.search(四个字符串方法)

Symbol.split方法对此构造函数原型里的split方法进行重写,故只在此构造函数中生效

function Split() {}
Split.prototype[Symbol.split] = function(string) {
    return string
}
let split = new Split();
console.log(Split.prototype[Symbol.split]('aaa')); //aaa
console.log(split[Symbol.split]('aaa')); //aaa
console.log('str'.split(split)); //str
console.log('str'.split('t')); //['s', 'r']

Symbol.match方法对此构造函数原型里的split方法进行重写,故只在此构造函数中生效

class Match {
    [Symbol.match](string) {
        return [...string]
    }
}
let match = new Match();
console.log(Match.prototype[Symbol.match]('aaa')); //['a', 'a', 'a']
console.log(match[Symbol.match]('aaa')); //['a', 'a', 'a']
console.log('str'.match(match)); //['s', 't', 'r']
console.log('str'.match('t')); //['t', index: 1, input: 'str', groups: undefined]

当然Symbol.replace、Symbol.search也是同理

2.7 Symbol.isConcatSpreadable作为内置属性用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。
//是否可以展开
//如果对象有长度和数字键,并在参与concat,调用时分离出值
let obj = {
    0: 'yy',
    1: 21,
    length: 2
}
obj[Symbol.isConcatSpreadable] = true;
console.log([].concat(obj)); // ['yy', 21]

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1[Symbol.isConcatSpreadable] = true;
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2)); // [1, 2, 3, Array(3)]
2.8 Symbol.toPrimitive是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。(param参数的取值是 "number"、"string" 和 "default" 中的任意一个)
const obj1 = {
    [Symbol.toPrimitive](param) {
        if (param === 'string') {
            return 'string-' + param;
        } else if (param === 'number') {
            return 123;
        } else if (param === 'default') {
            return undefined;
        }
    }
};


console.log(JSON.stringify(obj1)); //{}
console.log(obj1.toString()); //[object Object]
console.log(String(obj1)); //string-string

console.log(Number(obj1)); //123
console.log(parseInt(obj1)); //NaN
console.log(parseFloat(obj1)); //NaN
console.log(+obj1); //123
console.log(-obj1); //-123

console.log(obj1 + ''); //undefined

console.log(Boolean(obj1)); //true
console.log(obj1 == 1); //false
2.9 Symbol.toStringTag 是一个内置 symbol,它通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签,通常只有内置的 Object.prototype.toString() 方法会去读取这个标签并把它包含在自己的返回值里

这里的StringTagClass类就是我的自定义的类型标签(Symbol.toStringTag)

class StringTagClass {
    get[Symbol.toStringTag]() {
        return "Symbol.toStringTag";
    }
}
let stringTag = new StringTagClass();
console.log(Object.prototype.toString.call(new StringTagClass())); // "[object Symbol.toStringTag]"
console.log(stringTag.toString.call('123')); //[object String]
console.log(stringTag.toString.call(123)); //[object Number]
console.log(stringTag.toString.call(true)); //[object Boolean]
console.log(stringTag.toString.call(Promise.resolve())); //[object Promise]
console.log(Object.prototype.toString.call(new Map())); //[object Map]
console.log(Object.prototype.toString.call(function*() {})); //[object GeneratorFunction]
3.0 应用场景:
(1)使用Symbol来替代常量

保证了常量的唯一性,避免不必要的错误

const GOODS_COUNT = Symbol();
const UNIT_PRICE = Symbol();
(2)使用Symbol来作为对象属性名(key) [避免重名冲突]

Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。

let game = {
    name: 'YY',
    up() {
        console.log('向上');
    },
    down() {
        console.log('向下');
    }
}
//声明一个对象
let methods = {
    up: Symbol(),
    down: Symbol()
};
game[methods.up] = function() {
    console.log('up');
}
game[methods.down] = function() {
    console.log('down');
}

或者

let youxi = {
    name: '狼人杀',
    [Symbol('say')]: function() {
        console.log('我可以发言');
    },
    [Symbol('zibao')]() {
        console.log('我可以自爆');
    }
}

对于Symbol类型的属性方法的调用方法,详情可见我的另一篇博客:blog.csdn.net/Y1_1Y1_1y1_…

利用不能枚举的特性,当使用JSON.stringify()将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外:

let game = {
    name: 'YY',
    [Symbol('name')]: 'yy',
}
console.log(JSON.stringify(game)); //{"name":"YY"}
console.log(game); //{name: 'YY', Symbol(name): 'yy'}