文档章节

ES6 的 Symbol 类型及使用案例

达尔文
 达尔文
发布于 2017/01/04 21:46
字数 1550
阅读 5003
收藏 54

ES6 为 JavaScript 引入了一种新的基本类型:Symbol,它由全局 Symbol() 函数创建,每次调用 Symbol()函数,都会返回一个唯一的 Symbol。

let symbol1 = Symbol();
let symbol2 = Symbol();
 
console.log( symbol1 === symbol2 ); 
> false

因为每个 Symbol 值都是唯一的,因此该值不与其它任何值相等。

Symbol 是 JavaScript 中的新原始类型。

console.log( typeof symbol1 );
> "symbol"

Symbol 充当唯一的对象键。

let myObject = { 
    publicProperty: 'Value of myObject[ "publicProperty" ]'
};
 
myObject[ symbol1 ] = 'Value of myObject[ symbol1 ]';
myObject[ symbol2 ] = 'value of myObject[ symbol2 ]';
 
console.log( myObject );
> Object
>    publicProperty: "Value of myObject[ "publicProperty" ]"
>    Symbol(): "Value of myObject[ symbol1 ]"
>    Symbol(): "value of myObject[ symbol2 ]"
>    __proto__: Object
 
console.log( myObject[ symbol1 ] );
> Value of myObject[ symbol1 ]

当控制台打印myObject时,你能看到两个 Symbol 值都存储在对象中。"Symbol()" 是调用toString()的返回值,此值表示控制台中存在 Symbol 键。如果我们想访问正确的 Symbol,可以检索相应的值。

Symbol 键的属性不会在对象的 JSON 中显示,也不会在 for-in 循环和Object.keys中被枚举出来:

JSON.stringify( myObject )
> "{"publicProperty":"Value of myObject[ \"publicProperty\" ] "}"
 
for( var prop in myObject ) {
    console.log( prop, myObject[prop] );
}
> publicProperty Value of myObject[ "publicProperty" ] 
 
console.log( Object.keys( myObject ) );
> ["publicProperty"]

即使 Symbol 键的属性没有在上述案例中出现,这些属性在严格意义上也不是完全私有的。Object.getOwnPropertySymbols提供了一种检索对象的 Symbol 的方法。

Object.getOwnPropertySymbols(myObject)
> [Symbol(), Symbol()]
 
myObject[ Object.getOwnPropertySymbols(myObject)[0] ]
> "Value of myObject[ symbol1 ]"

如果你使用 Symbol 键来表示私有变量,要确保不要用Object.getOwnPropertySymbols来检索可能私有化的属性。在这种情况下,Object.getOwnPropertySymbols的唯一使用情况就是测试和调试。

你遵循上述规则,从代码开发的角度来看,对象键值是私有的,但在实际情况中,其他人仍能访问你的私有值。

虽然 Symbol 键不能被for...of扩展运算符和Object.keys枚举,但它们仍被包含在浅拷贝里:

clonedObject = Object.assign( {}, myObject );
 
console.log( clonedObject );
> Object
>    publicProperty: "Value of myObject[ "publicProperty" ]"
>    Symbol(): "Value of myObject[ symbol1 ]"
>    Symbol(): "value of myObject[ symbol2 ]"
>    __proto__: Object

正确命名 Symbol 对指明其用途至关重要,如果你需要额外的语义指导,还可在 Symbol 上附上一个描述。Symbol 的描述体现在 Symbol 的字符串值中。

let leftNode = Symbol( 'Binary tree node' );
let rightNode = Symbol( 'Binary tree node' );
 
console.log( leftNode )
> Symbol(Binary tree node)

始终提供 Symbol 的描述,并始终保持描述的唯一性。如果用 Symbol 访问私有属性,请将其描述视为变量名。

如果你将相同的描述传递给两个 Symbol,它们的值仍不相同。

console.log( leftNode === rightNode );
> false

全局 Symbol 注册表

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

当出现key1 === key2时就会有Symbol.for( key1 ) === Symbol.for( key2 )。这种对应关系甚至是跨 service worker 和 iframe 的。

let privateProperty1 = Symbol.for( 'firstName' );
let privateProperty2 = Symbol.for( 'firstName' );
 
myObject[ privateProperty1 ] = 'Dave';
myObject[ privateProperty2 ] = 'Zsolt';
 
console.log( myObject[ privateProperty1 ] );
// Zsolt

因为 Symbol 注册表中的 Symbol 值和字符串之间有一一对应的关系,所以我们也可以检索字符串键。使用Symbol.keyFor方法。

Symbol.keyFor( privateProperty1 );
> "firstName"
 
Symbol.keyFor( Symbol() );
> undefined

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 就行了。

外部库的做法也是这样。

知名 Symbol

这里有一些比较常用的 Symbol,用以访问和修改内部 JavaScript 行为。你可以用它们重新定义内置方法。运算符和循环。

演练

演练1.用下划线来表示字段的私有,有什么利弊?用这种方法和 Symbol 比较。

let mySquare {
    _width: 5,
    getWidth() { return _width; }
}

利:

  • 开发者体验佳
  • 不会造成复杂的代码结构

弊:

  • 属性仅被表示为私有,在实践中并不是私有的,容易被破解
  • 不同于 Symbol,这种方式的公有和私有属性没有很好地区分,私有属性出现在对象的公有接口中,它们使用能被扩展运算符,Object.keysfor..of循环枚举。

演练2. 模拟 JavaScript 中的私有字段。

解决方案:当涉及到构造函数时,可以使用var, let, 或 const在构造函数中声明私有成员。

function F() {
   let privateProperty = 'b';
   this.publicProperty = 'a';
}
 
let f = new F();
 
// f.publicProperty returns 'a'
// f.privateProperty returns undefined 

为了对类使用相同的方法,我们必须放置方法定义:在可访问私有属性的作用域中的构造函数方法中使用私有属性的方法。我们将使用Object.assign来达到此目的。(灵感来自Managing private data of ES6 classes

class C {
    constructor() {
        let privateProperty = 'a';
        Object.assign( this, {
            logPrivateProperty() { console.log( privateProperty ); }
        } );
    }
}
 
let c = new C();
c.logPrivateProperty();

字段privateProperty在对象c中不可访问。

该解决方案也适用于我们扩展 C 类。

原文:ES6 Symbols and their Use Cases

编译:开源中国-达尔文

© 著作权归作者所有

共有 人打赏支持
达尔文

达尔文

粉丝 434
博文 25
码字总数 34746
作品 0
深圳
运营/编辑
私信 提问
加载中

评论(4)

whinc
whinc
Symbol.iterator 这个预设的 symbol 比较常用
李嘉图
李嘉图
mark
hantsy
hantsy
Exploring ES6 一书可以全面了解 ES 6 特性,
young7
young7
不错,Symbol的用法虽然之前略有所闻,但是具体的应用场景还是不太清楚,这篇文章给出了一些建议
【探秘ES6】系列专栏(八):JS的第七种基本类型Symbols

ES6作为新一代JavaScript标准,已正式与广大前端开发者见面。为了让大家对ES6的诸多新特性有更深入的了解,Mozilla Web开发者博客推出了《ES6 In Depth》系列文章。CSDN已获授权,将持续对该...

一配
2015/11/08
0
0
低门槛彻底理解JavaScript中的深拷贝和浅拷贝

在说深拷贝与浅拷贝前,我们先看两个简单的案例: 按照常规思维,应该和一样,不会因为另外一个值的改变而改变,而这里的 却随着的改变而改变了。同样是变量,为什么表现不一样呢?这就要引入...

lunaqi
05/11
0
0
ES6的Symbol竟然那么强大,面试中的加分点啊

symbol是es6出的一种类型,他也是属于原始类型的范畴(string, number, boolean, null, undefined, symbol) basic symbol for 这个东西是可共享,在创建的时候会检查全局是否寻在这个key的sym...

xiaohesong
11/02
0
0
[译]ES6 中的元编程:第一部分 —— Symbol,了不起的 Symbol

原文地址:Metaprogramming in ES6: Symbols and why they're awesome 原文作者:Keith Cirkel 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:yoyoyohamapi 校对者:...

吴晓军
2017/11/17
0
0
理解和使用ES6中的Symbol

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

一斤代码
07/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

代码块

package com.atguigu.java; //总结:由父及子,静态先行! class Root{ static{ System.out.println("Root的静态初始化块"); } { System.out.println("Root的普通初始化块"); } public Root......

architect刘源源
17分钟前
2
1
SpringCloud之Eureka

Eureka简介 什么是Eureka? Eureka是一种基于rest提供服务注册和发现的产品: Eureka-Server: 用于定位服务,以实现中间层服务器的负载平衡和故障转移。 Eureka-client:用于服务间的交互,内...

lc_fly1
31分钟前
1
0
系统维护和tcp连接

查看系统负载 1 w 命令 w命令用于显示系统当前负载 和系统已登录的用户. 查看系统CPU 和核数: cat /proc/cpuinfo| grep 'cpu cores' 第一行显示 :04:41:16 up 8:56, 1 user, load average: 0...

Fc丶
55分钟前
2
0
Mac Pro 下安装 Snappy 压缩工具

snappy 我这里就不做介绍了,直接可以移步 https://github.com/google/snappy/tree/master 查看源码及说明信息。 我这里下载 :https://github.com/google/snappy/releases/download/1.1.4/...

Ryan-瑞恩
58分钟前
3
0
iframe里弹出的层显示在整个网页上

通过在iframe页面添加js脚本,动态给父窗体创建一个div,然后设置让其显示在最顶层这样就可以了 在文件夹中创建两个文件,一个iframe页面,一个父页面index。

少年已不再年少
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部