javascript ·

ES6中的Symbol有什么卵用

在ES6中新增了一中类型,这个类型叫做Symbol,最大的特点号称独一无二,下面我们来说一下这东西怎么用,最后再说一下他用在哪。

首先要注意的一点是,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,而不是个对象

Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false
// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");
s1 === s2 // false

通过上面的代码我们可以发现这货还真是独一无二,即使两个变量赋予了相同的值,他还是不一样,返回false。有好事者就会提出一个想法就是s1给s2赋值,他想不相等呢?
ES6中的Symbol有什么卵用
很明显的结果,这俩相等。

作为一个独一无二的类型,他也有自己的脾气,那就是不能和任何类型进行运算,否则就会报错。但是他也有大众化的一点,那就是他可以作为属性名

作为属性名

var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = { [mySymbol]: 'Hello!'};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

如果你想问我用a.mySymbol来赋值可不可以,如果你用了,那么你就应该去补补基础了,因为点运算符后面的值必须是字符串,用点运算符访问会报错。

下面来看一下这个例子

let a = {};
a.name = 'oecom';
let name = Symbol();
a[name] = 'oecom.cn';
console.log(a.name,a[name]);             //oecom,oecom.cn

注意,我上面是用let声明变量,并且声明在了a.name后面,否则会报错的,这个就涉及到了变量提升了。通过这个例子应该就可以明白Symbol在属性名上的应用是什么效果了。

Symbol值作为属性名时,该属性还是公开属性,不是私有属性。这个有点类似于java中的protected属性(protected和private的区别:在类的外部都是不可以访问的,在类内的子类可以继承protected不可以继承private)。但是这里的Symbol在类外部也是可以访问的,只是不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。但有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名我们来看一下下面这个图
ES6中的Symbol有什么卵用

全局 Symbol 注册表

ES6 有一个用于创建 Symbol 的全局资源:Symbol 注册表,它为字符串和 Symbol 提供了一对一的关系。注册表使用 Symbol.for( key )返回 Symbol。

Symbol.for机制有点类似于单例模式,首先在全局中搜索有没有以该参数作为名称的Symbol值,如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。和直接的Symbol就点不同了。

var s1 = Symbol.for('oecom');
var s2 = Symbol.for('oecom');
s1===s2 //返回true
console.log(s1); //Symbol(oecom)

Symbol.keyFor方法返回一个已登记的Symbol类型值的key。实质就是检测该Symbol是否已创建

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

var s2 = Symbol("oecom");
Symbol.keyFor(s2) // undefined

说了这么多了,可能你还是感觉这东西有啥用啊,平时也用不到啊,那么接下来就说一下他的用途。

用途

1.创建枚举类型

枚举允许你定义具有语义名称和唯一值的常量。假定 Symbol 的值不同,它们能为枚举类型提供最好的值。

const directions = {
    UP   : Symbol( 'UP' ),
    DOWN : Symbol( 'DOWN' ),
    LEFT : Symbol( 'LEFT' ),
    RIGHT: Symbol( 'RIGHT' )
};

2.避免名称冲突

当使用 Symbol 作为变量时,我们不必建立可用标识符的全局注册表,也不必费心思想标识符名字,只需要创建一个 Symbol 就行了。一些外部库的做法也是这样。

3.设置半私有属性

根据for in 和for of循环均不能访问到Symbol,我们可以将其作为一个半私有属性

let Square = (function() {
    const _width = Symbol('width');
    class Square {
        constructor( width0 ) {
            this[_width] = width0;
        }
        getWidth() {
            return this[_width];
        }
    }
    return Square;
} )();

这样做的好处是,他人很难访问到我们对象的私有_width值,而且也能很好地区分,哪些属性是公有的,哪些属性是私有的。但这种方法的缺点也很明显:

  • 通过调用Object.getOwnPropertySymbols,我们可以使用 Symbol 键。
  • 如果要写很多的代码,这会使得开发者的体验不佳,访问私有属性不像 Java 或 TypeScript 那样方便。

如果你要用 Symbol 来表示私有字段,那你需要表明哪些属性不能被公开访问,如若有人试图违背这一规则,理应承担相应的后果。

参与评论