专栏名称: 众成翻译
翻译,求知的另一种表达
目录
相关文章推荐
天池大数据科研平台  ·  DeepSeek开源放大招:FlashMLA ... ·  20 小时前  
大数据文摘  ·  重磅新书 | ... ·  昨天  
CDA数据分析师  ·  【干货】5分钟学会数据可视化:使用Pyech ... ·  昨天  
数据派THU  ·  EvalPlanner:基于“计划-执行”双 ... ·  3 天前  
数据派THU  ·  【ICLR2025】VEVO:基于自监督解耦 ... ·  4 天前  
51好读  ›  专栏  ›  众成翻译

ES6实践:Symbols及其使用

众成翻译  · 掘金  ·  · 2018-08-29 10:12

正文

虽然ES2015已经引入了许多开发人员期待已久的语言特性,但还有一些新特性不太为人所知和理解,其好处也不太清楚——比如symbols。

symbol(符号)是一种新的原始数据类型,一个确保不会和其它符号冲突的唯一令牌。从这个意义上讲,你可以把符号看作是一种 UUID (通用唯一识别码)。 让我们看看符号是如何工作的,以及我们能用它做些什么。

创建符号

创建符号非常直接,就是简单地调用 Symbol 函数。需要注意的是这只是一个标准的函数而不是一个对象构造器。使用new操作符调用它会导致一个类型错误。每次调用Symbol函数的时候,都会得到一个全新的唯一的值。

const foo = Symbol();
const bar = Symbol();

foo === bar
// <-- false


创建符号的时候可以给符号添加一个标签,方式是传递字符串作为第一个参数。标签并不能影响符号的值,只是利于调试,并且当符号的 toString() 方法被调用的时候会显示出来。创建具有相同标签的多个符号是可行的,但是这样做没有任何好处,并很可能引起疑惑。

let foo = Symbol('baz');
let bar = Symbol('baz');

foo === bar
// <-- false
console.log(foo);
// <-- Symbol(baz)


符号能用来做什么?

符号可以很好地替代作为类/模块常量的字符串或者整数:

class Application {
  constructor(mode) {
    switch (mode) {
      case Application.DEV:
        // Set up app for development environment
        break;
      case Application.PROD:
        // Set up app for production environment
        break;
      case default:
        throw new Error('Invalid application mode: ' + mode);
    }
  }
}

Application.DEV = Symbol('dev');
Application.PROD = Symbol('prod');

// Example use
const app = new Application(Application.DEV);


字符串和整数并不是唯一的值;比如数字 2 或者字符串 development 也可能在程序的其它地方被用于不同的目的。使用符号意味着我们可以对提供的值更有信心。

符号的另外一个有趣的用法是作为对象属性的键值。如果你曾经把JavaScript对象作为 hashmap (PHP术语中的关联数组或者Python中的字典)使用,你就会对使用括号语法获取/设置属性很熟悉:

const data = [];

data['name'] = 'Ted Mosby';
data['nickname'] = 'Teddy Westside';
data['city'] = 'New York';


使用括号语法,我们也可以使用符号做为属性的键值。这样做有很多优点。首先,可以确保基于符号的键值不会冲突,不像字符串键值有可能会和对象已有的属性或者方法冲突。其次,符号不会被 for ... in 枚举,并且会被 Object.keys() Object.getOwnPropertyNames() JSON.stringify() 等方法忽略。对于在序列化对象时不想被包含的属性来说,符号是一个理想的选择。

const user = {};
const email = Symbol();

user.name = 'Fred';
user.age = 30;
user[email] = '[email protected]';

Object.keys(user);
// <-- Array [ "name", "age" ]

Object.getOwnPropertyNames(user);
// <-- Array [ "name", "age" ]

JSON.stringify(user);
// <-- "{"name":"Fred","age":30}"


然而,值得注意的是,使用符号做为键值并不能保证私有。有一些新的工具允许访问符号类型的属性键值。 Object.getOwnPropertySymbols() 返回基于符号的键值组成的数组, Reflect.ownKeys() 返回所有键值,包含符号键值,组成的数组。

Object.getOwnPropertySymbols(user);
// <-- Array [ Symbol() ]

Reflect.ownKeys(user)
// <-- Array [ "name", "age", Symbol() ]


有名的符号

因为以符号做为键值的属性在ES6之前的代码中是不可见的,所以在保证向后兼容的同时,符号是给JavaScript现有类型添加新功能的理想选择。所谓“有名”的符号是预定义在 Symbol 函数上的属性,它们被用来自定义某些语言特性的行为,实现新的功能,比如迭代器。

Symbol.iterator 是一个有名的符号,被用来给对象添加一个特殊的方法,使得对象可以被迭代:

const band = ['Freddy', 'Brian', 'John', 'Roger'];
const iterator = band[Symbol.iterator]();

iterator.next().value;
// <-- { value: "Freddy", done: false }
iterator.next().value;
// <-- { value: "Brian", done: false }
iterator.next().value;
// <-- { value: "John", done: false }
iterator.next().value;
// <-- { value: "Roger", done: false }
iterator.next().value;
// <-- { value: undefined, done: true }


内建类型字符串,数组,类型数组,Map(映射)和Set(集合)都有一个默认的 Symbol.iterator 方法。当上述类型的实例被 for ... of 循环或者用于扩展运算符的时候就会调用这个方法。浏览器也开始使用 Symbol.iterator 键值让DOM结构,比如 NodeList HTMLCollection 以同样的方式被迭代。

全局注册表

规范还定义了一个运行时范围的符号注册表,这意味着你可以在不同的执行上下文,比如在文档、内嵌iframe或者service worker之间,存储和获取符号。

Symbol.for(key) 用来获取注册表中给定键值的符号。如果对应这个键值的符号不存在,会返回一个新的符号。正如你预期的那样,对于同一个值,后续的调用都会返回同一个符号。

Symbol.keyFor(symbol) 允许你获取给定符号的键值。如果注册表中不存在给定的符号,那么会返回 undefined

const debbie = Symbol.for('user');
const mike   = Symbol.for('user');

debbie === mike
// <-- true

Symbol.keyFor(debbie);
// <-- "user"


用例

在一些用例中,使用符号提供了优势。其中之一是在文章开头提到的,当给一个对象添加不想被对象序列化的时候包含在内的属性,就好像属性是“隐藏”的时候,使用符号就是一个很好的选择。







请到「今天看啥」查看全文