ES6之symbol

为什么需要使用Symbol

考虑到以下场景:

// a library
var L = (function() {
    var prop1 = 'prop1';
    function haha() { console.log('world haha'); }
    // ...
    return {
        prop1 : prop1,
        haha : someFn
    };
})();

// using
L.haha = function() { console.log('hello world!'); };

// call
L.haha();   // hello world!

看出问题来了么?一个公共库暴露出了一些方法和属性,但是在我们调用的时候我们并不知道它其中有什么方法或者属性,这样当我们在这个库上进行定义了相同的属性的时候,这个库就会被暴露的属性就有可能会被重写。

Symbol引入

所以为了保证变量的唯一性,ES6在原本的6中基础数据类型(Undefined、Null、Boolean、String、Number、Object)下中引入了Symbol类型,它是独一无二的。

// using
var haha = Symbol();
L[haha] = function() { console.log('hello world!'); };

// call
L.haha();   // world haha
L[haha]();  // hello world!

Symbol值

Symbol值通过Symbol函数生成,由于生成的值是一个原始类型,不是对象,所以不能使用new关键字,否则会报错。Symbol值是唯一的,不会和其他属性名产生冲突。

var symbol1 = Symbol();
var symbol2 = Symbol();

console.log(typeof symbol1);    // symbol
symbol1 === symbol2;            // false

// Uncaught TypeError: Symbol is not a constructor
var symbol = new Symbol();      

注:Symbol值不能和其他值运算,否则会报错。

var s1 = 'str1';
var symbol = Symbol();

// Uncaught TypeError: Cannot convert a Symbol value to a string
console.log(s1 + symbol);   

Symbol参数

Symbol函数可以接受一个字符串作为参数,表示对这个Symbol值的描述。如果传递的是一个对象,那么会调用这个对象的toString方法。

var foo = Symbol('foo');
var bar = Symbol('bar');
var obj = {
    toString: function() {
        return 'hello';
    }
};
var o = Symbol(obj);

console.log(foo);   // Symbol(foo)
console.log(bar);   // Symbol(bar)
console.log(o);     // Symbol(hello)

Symbol作为属性名

前面介绍到了,为了保证属性名的不冲突才引入了Symbol。作为对象的属性名,Symbol主要有一下三种写法:

let obj = {};
let s = Symbol();

// 第一种写法
obj[s] = 'mySymbol';

// 第二种写法
obj = {
    [s]: 'mySymbol'
}
// 第三种写法
Object.defineProperty(obj, s, { value: 'mySymbol' });
obj.s;      // undefined

obj.s = 'mySymbol2';
obj[s]      // mySymbol2
obj['s']    // mySymbol

遍历Symbol属性

Symbol值作为属性,无法使用for...in或者for...of来获取,也无法使用Object.keys或者Object.getOwnPropertyNames来获取。那么通过什么办法来获取Symbol属性值呢?我们可以使用Object.getOwnPropertySymbols()来获取一个对象上的属性名,通过Reflect.ownKeys()来获取所有类型的属性名。

let s1 = Symbol('s1');
let s2 = Symbol('s2');
let testObj = { 
    normal: 'normal',
    [s1]: s1,
    [s2]: s2
};

// for...in
for (let key in testObj) {
    if (Object.prototype.hasOwnProperty.call(testObj, key)) {
        console.log('key', key);    // key normal
    }
}

// getOwnPropertyNames
console.log(Object.getOwnPropertyNames(testObj));   // ["normal"]

// getOwnPropertySymbols
console.log(Object.getOwnPropertySymbols(testObj)); // [Symbol(s1), Symbol(s2)]

// Reflect.ownKeys
console.log(Reflect.ownKeys(testObj));  // ["normal", Symbol(s1), Symbol(s2)]

Symbol.for()和Symbol.keyFor()

如果想获取同一个Symbol值,我们可以给Symbol.for()中传递一个字符串参数。生成的这个Symbol值会在全局环境中登记,如果下次还传输这个相同的key值的时候,就会在全局环境中搜索是否有已经存在了这个key值的Symbol值,如果有就会返回。Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key

let s1 = Symbol.for('s1');
let s2 = Symbol.for('s1');

s1 === s2;  // true
console.log(Symbol.keyFor(s1)); // s1

内置的Symbol值

作为对象的内置Symbol属性值,会在特定的情景下触发。比如Symbol.split会在该对象被String.prototype.split调用时触发,而Symbol.itertor指向的函数会在使用for...of遍历这个对象的时候触发。具体其他的内置属性可以参看文档http://es6.ruanyifeng.com/#docs/symbol#内置的Symbol值;

应用场景

作为半私有属性键

即使 Symbol 不能使属性私有,它们也能用作带有私有属性的符号。你可以使用 Symbol 来分隔公有和私有属性的枚举,Symbol 能使它更清楚。

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

只要你能隐藏_width就行了,隐藏_width的方法之一是创建闭包:

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

创建枚举类型

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

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

避免名称冲突

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JavaEdge

SpringMVC之Controller查找(Spring4.0.3/Spring5.0.4源码进化对比)0 摘要1 SpringMVC请求流程2 SpringMVC初始化过程总结

44260
来自专栏JavaEdge

@NotBlank注解地正确使用

55770
来自专栏大前端开发

理解和使用ES6中的Symbol

ES6中引入了一种新的基础数据类型:Symbol,不过很多开发者可能都不怎么了解它,或者觉得在实际的开发工作中并没有什么场景应用到它,那么今天我们来讲讲这个数据...

37630
来自专栏小樱的经验随笔

Codeforces 708A Letters Cyclic Shift

A. Letters Cyclic Shift time limit per test:1 second memory limit per test:256 m...

352100
来自专栏公众号_薛勤的博客

Spring MVC数据绑定入门总结

基本类型参数不可为空 正例:http://localhost:8080/demo/he?id=2 反例:http://localhost:8080/de...

8120
来自专栏Google Dart

为控制器配置日期处理 原

6310
来自专栏郭少华

Spring boot之JSON(二)

22240
来自专栏xingoo, 一个梦想做发明家的程序员

【Spring实战】—— 6 内部Bean

本篇文章讲解了Spring的通过内部Bean设置Bean的属性。   类似内部类,内部Bean与普通的Bean关联不同的是:   1 普通的Bean,在其...

20370
来自专栏Spring相关

通过Groovy来消除代码噪声

Java是在JVM上运行的最广泛使用的编程语言。不过,还有很多其他基于JVM的语言,比如Groovy,Scala,JRuby,Jython,Kotlin等等。其...

9120
来自专栏Hongten

Struts2 ActionWildcard(通配符配置)约定优于配置

新建web project:struts2_0500_actionwildcard

10420

扫码关注云+社区

领取腾讯云代金券