ES6 之路 --Symbol

1,470 阅读5分钟

欢迎访问我的个人站点

1. 概述

简介

Symbol 是一种特殊的、不可变的数据类型,可以作为对象属性的标识符使用,表示独一无二的值。Symbol 对象是一个 symbol primitive data type 的隐式对象包装器。它是 JavaScript 语言的第七种数据类型,前 6 种分别是:Undefined、Null、Boolean、String、Number、Object。

语法

Symbol([description])

Parameters

description : 可选的字符串。可用于调试但不访问符号本身的符号的说明。如果不加参数,在控制台打印的都是 Symbol,不利于区分。

demo


var s1 = Symbol('symbol1');
s1 //Symbol(symbol1); 

因为 Symbol 函数返回的值都是独一无二的,所以 Symbol 函数返回的值都是不相等的。

//无参数
var s1 = Symbol();
var s2 = Symbol();

s1 === s2 // false

//有参数
 var s1 = Symbol('symbol');
 var s2 = Symbol('symbol');
 
 s1 === s2 //false

2. 作为属性名的 Symbol

由于每一个 Symbol 值都是不相等的,那么作为属性标识符是一种非常好的选择。

定义方式:

let symbolProp = Symbol();

var obj = {};
obj[symbolProp] = 'hello Symbol';

//或者
var obj = {
    [symbolProp] : 'hello Symbol';
}

//或者
var obj = {};
Object.defineProperty(obj,symbolProp,{value : 'hello Symbol'});

注意

定义属性的时候只能将 Symbol 值放在方括号里面,否则属性的键名会当做字符串而不是 Symbol 值。同理,在访问 Symbol 属性的时候也不能通过点运算符去访问,点运算符后面总是字符串,不会读取 Symbol 值作为标识符所指代的值.

Symbol 类型定义常量

常量的使用 Symbol 值最大的好处就是其他任何值都不可能有相同的值,用来设计 switch 语句是一种很好的方式。例如:消除魔术字符串(这里留给读者思考,如果有什么疑问,可以给我留言)

3. Symbol.for(),Symbol.keyFor()

Symbol.for()

对于 Symbol.for 方法需要记住两点:

  1. Symbol.for() 所返回的 Symbol 值的作用域是 == 整个代码库 ==(包括不同的 iframe 或者 service worker),是一个全局的变量, 第一次产生的时候就会登记下来。
  2. 调用 Symbol.for() 的时候,如果在全局环境中检索给定的 key 是否存在,如果不存在才会新建一个值, 而 Symbol() 不会,Symbol() 每次返回的都是不同的值。


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

Symbol.keyFor()

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

var s1 = Symbol.for('foo');
Symbol.keyFor(s1) //"foo"

var s2 = Symbol('foo');
Symbol.keyFor(s2);//undefiend

上面的代码中,变量 s2 属于未登记的 Symbol 值,所以返回 undefined

4. 属性名的遍历

Symbol 作为属性名,虽然不是私有属性,但是在 for...in,for...of 循环中,Object.keys(),Object.getOwnPropertyNames() 都不会获取到。通常通过两种方法达到 Symbol 属性的遍历。

  1. Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有 Symbol 值的属性。
  2. Reflect.ownKeys() 可以返回所有类型的键名,包括包括常规的键名和 Symbol 键名.

下面给出一个例子来解释上面所有的。

var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'hello';
obj[b] = 'world';

//获取不到
for(var i in obj){
    console.log(i); //无输出
}

Object.getOwnPropertyNames(obj);//[]


//可以获取
var objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols// [Symbol(a), Symbol(b)]

Reflect.ownKeys(obj);//[Symbol(a), Symbol(b)]

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

var size = Symbol('size');

class Collection {
    constructor(){
        this[size] = 0;
    }
    
    add(item){
        this[this[size]] = item;
        this[size]++;
    }
    
    static sizeOf(instance){
        return instance[size];
    }
}

var x = new Collection();
Collection.sizeOf(x); //0

x.add('foo');
Collection.sizeOf(x); //1

Object.keys(x)//['0']
Object.getOwnPropertyNames(x) //['0']
Object.getOwnPropertySymbols(x) //[Symbol(size)]

上面的代码中,对象 x 的 size 属性是一个 Symbol 值,所以 Object.keys(x)、Object.getOwnPropertyNames(x) 都无法获取它。这就造成了一种非私有的内部方法的效果。如果对 ES6 定义类方面还不清楚的,可以先不看这段,或者自己查查资料,后面的文章我也会分享出来,总的来说现在 JavaScript 的新标准越来越像 Java 了,比如新增的 const、let 块级作用域,class 定义类等等。

内置的 Symbol 值

除了自己定义的 Symbol 值外,JavaScript 有一些内置的 Symbol 表示的内部语言行为不在 ECMAScript 5 及以前暴露给开发者。这些 Symbol 可以被访问被下列属性:

1. Symbol.iterator

返回对象的默认迭代器的方法。被 for...of 使用

2. Symbol.math

与字符串匹配的方法,也用于判断对象是否可以用作正则表达式. 被 String.prototype.match() 使用。

3. Symbol.replace

一种方法取代匹配字符串的子串。被 String.prototype.replace() 使用。

4. Symbol.search

返回与正则表达式匹配的字符串内返回索引的方法。被 String.prototype.search() 使用。

5. Symbol.split

在与正则表达式匹配的索引处拆分字符串的方法。被 String.prototype.split() 使用.

6. Symbol.hasInstance

确定构造函数对象是否将对象作为实例识别的方法。被 instanceof 使用

7. Symbol.isConcatSpreadable

一个布尔值,指示对象是否应该被扁平化为数组元素。被 Array.prototype.concat() 使用.

8. Symbol.unscopables

从关联对象的环境绑定中排除其自身和继承的属性名称的对象值。被 with 使用

9. Symbol.species

用于创建派生对象的构造函数。

10. Symbol.toPrimitive

将对象转换为原始值的方法。

11. Symbol.toStringTag

用于对象的默认描述的字符串值。被 Object.prototype.toString() 使用.

我这里没给出具体的例子,针对这 11 个属性。忘读者自己主动去把这几个属性搞懂,对理解有些方法是非常有用的。

参考资料:

阮一峰的 ES6 标准入门

Symbol|-JavaScript|MDN