原文地址:Metaprogramming in ES6: Symbols and why they’re awesome
原文作者:Keith Cirkel
译文出自:掘金翻译计划
转自:https://juejin.im/post/5a0e65c1f265da430702d6b9
译者:yoyoyohamapi
校对者:Usey95 IridescentMia
元编程(笼统地说)是所有关于一门语言的底层机制,如果程序可以被描述为 “制作程序”,元编程就能被描述为 “让程序来制作程序”。
比如,代码生成:eval。
再比如,反射(Reflection) —— 其用于发现和调整你的应用程序结构和语义。JavaScript 有几个工具来完成反射。函数有 Function#name、Function#length、以及 Function#bind、Function#call 和 Functin#apply。所有 Object 上可用的方法也算是反射,例如 Object.getOwnProperties。JavaScript 也有反射/内省运算符,如 typeof、instancesof 以及 delete。
反射是元编程中非常酷的一部分,因为它允许你改变应用程序的内部工作机制。
Symbols 是 ES6 一个全新的 API,它是实现了的反射(Reflection within implementation)—— 你将 Symbols 应用到你已有的类和对象上去改变它们的行为。
Symbols 是新的原始类型(primitive)。就像是 Number、String、和 Boolean 一样。Symbols 具有一个 Symbol 函数用于创建 Symbol。与别的原始类型不同,Symbols 没有字面量语法(例如,String 有 ”)—— 创建 Symbol 的唯一方式是使用类似构造函数而又非构造函数的 Symbol 函数:
Symbol(); // symbol
console.log(Symbol()); // 输出 "Symbol()" 至控制台
assert(typeof Symbol() === 'symbol')
// 类似构造函数而又非构造函数的 Symbol 函数
new Symbol(); // TypeError: Symbol is not a constructor
Symbols 能用作对象的 key (类似字符串 key),这意味着你可以分配无限多的具有唯一性的 Symbols 到一个对象上,这些 key 保证不会和现有的字符串 key 冲突,或者和其他 Symbol key 冲突。
并且,继续划重点,Symbols key 无法通过 for in、for of 或者 Object.getOwnPropertyNames 获得 —— 获得它们的唯一方式是 Object.getOwnPropertySymbols。
这意味着 Symbols 能够给对象提供一个隐藏层,帮助对象实现了一种全新的目的 —— 属性不可迭代,也不能够通过现有的反射工具获得,并且能被保证不会和对象任何已有属性冲突。
但是,这里也有个例外:Symbol.for()
JavaScript 也有另一个创建 Symbol 的方式来轻易地实现 Symbol 的获得和重用:Symbol.for()。该方法在 “全局 Symbol 注册中心” 创建了一个 Symbol。额外注意的一点:这个注册中心也是跨域的,意味着 iframe 或者 service worker 中的 Symbol 会与当前 frame Symbol 相等
Symbols 无法通过现有的反射工具读取。你需要一个新的方法 Object.getOwnPropertySymbols() 来访问对象上的 Symbols,这让 Symbol 适合存储那些你不想让别人直接获得的信息。使用 Object.getOwnPropertySymbols() 是一个非常特殊的用例,一般人可不知道 (嘘~)。
Symbols 不是私有的。作为双刃剑的另一面 —— 对象上所有的 Symbols 都可以直接通过 Object.getOwnPropertySymbols() 获得 —— 这不利于我们使用 Symbol 存储一些真正需要私有化的值。不要尝试使用 Symbols 存储对象中需要真正私有化的值 —— Symbol 总能被拿到。
Symbols 不总是唯一的。上文中就提到过了,Symbol.for() 将为你返回一个不唯一的 Symbol。不要总认为 Symbol 具有唯一性,除非你自己能够保证它的唯一性。
作为一个可替换字符串或者整型使用的唯一值
作为一个对象中放置元信息(metadata)的场所(记住,Symbols 不是私有的)
给予开发者在 API 中为对象添加钩子(hook)的能力(下面详细讲述这个)
假定我们有一个 console.log 风格的工具函数 —— 这个函数可以接受任何对象,并将其输出到控制台。它有自己的机制去决定如何在控制台显示对象 —— 但是你作为一个使用该 API 的开发者,得益于 inspect Symbol 实现的一个钩子,你能够提供一个方法去重写显示机制 :
// 从 API 的 Symbols 常量中获得这个充满魔力的 Inspect Symbol
var inspect = console.Symbols.INSPECT; // 这是一个 Symbols 对象
var myVeryOwnObject = {};
console.log(myVeryOwnObject); // 日志 `{}`
myVeryOwnObject[inspect] = function () { return 'DUUUDE'; };
console.log(myVeryOwnObject); // 日志输出 `DUUUDE`
这个审查(inspect)钩子大致实现如下:
console.log = function (…items) {
var output = '';
for(const item of items) {
if (typeof item[console.Symbols.INSPECT] === 'function') {
output += item[console.Symbols.INSPECT](item);
} else {
output += console.inspect[typeof item](item);
}
output += ' ';
}
process.stdout.write(output + '\n');
}
PS:钩子是什么?
提供一个可以影响默认的(或原有的)流程(机制)的时机
通常就是:一个库、一个框架、一个系统或一种语言,提供一个对外公开的接口,通过这个接口,用户能够影响库、框架、系统或程序的行为。