简述
symbol 是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。
每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符,这是该数据类型仅有的目的。
语法
Symbol([description])
description为可选的,字符串类型,是对symbol的描述,可用于调试但不是访问symbol本身。
var sym1 = Symbol();
var sym2 = Symbol('foo');
var sym3 = Symbol('foo');
上面的代码创建了三个新的symbol类型。 注意,Symbol(“foo”) 不会强制将字符串 “foo” 转换成symbol类型。它每次都会创建一个新的 symbol类型:
Symbol("foo") === Symbol("foo"); // false
方法
Symbol.for(key)
通常情况下,我们在一个浏览器窗口中(window),使用Symbol()函数来定义Symbol实例就足够了。但是,如果你的应用涉及到多个window(最典型的就是页面中使用了),并需要这些window中使用的某些Symbol是同一个,那就不能使用Symbol()函数了,因为用它在不同window中创建的Symbol实例总是唯一的,而我们需要的是在所有这些window环境下保持一个共享的Symbol。这种情况下,我们就需要使用另一个API来创建或获取Symbol,那就是Symbol.for(),它可以注册或获取一个window间全局的Symbol实例。
Symbol.for(key) 方法会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
symbol 注册表中的记录结构:
字段名 | 字段值 |
---|---|
[[key]] | 一个字符串,用来标识每个 symbol |
[[symbol]] | 存储的 symbol 值 |
let gs1 = Symbol.for('global_symbol_1') // 创建一个 symbol 并放入 symbol 注册表中,键为 "global_symbol_1"
let gs2 = Symbol.for('global_symbol_1') // 从 symbol 注册表中读取键为"global_symbol_1"的 symbol
gs1 === gs2 // true
// 这样一个Symbol不光在单个window中是唯一的,在多个相关window间也是唯一的了。
var sym = Symbol.for("mario");
sym.toString();
// "Symbol(mario)",mario 既是该 symbol 在 symbol 注册表中的键名,又是该 symbol 自身的描述字符串
Symbol.keyFor(sym)
参数sym为必选参数,存储在 symbol 注册表中的某个 symbol。如果全局注册表中查找到该symbol,则返回该symbol的key值,形式为string。如果symbol未在注册表中,返回undefined。
// 创建一个 symbol 并放入 Symbol 注册表,key 为 "foo"
var globalSym = Symbol.for("foo");
Symbol.keyFor(globalSym); // "foo"
// 创建一个 symbol,但不放入 symbol 注册表中
var localSym = Symbol();
Symbol.keyFor(localSym); // undefined,所以是找不到 key 的
// well-known symbol 们并不在 symbol 注册表中
Symbol.keyFor(Symbol.iterator) // undefined
阻止创建一个显式的 Symbol 包装器对象
var sym = new Symbol(); // Uncaught TypeError: Symbol is not a constructor
围绕原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持。 然而,现有的原始包装器对象,如 new Boolean、new String以及new Number,因为遗留原因仍可被创建。
如果你真的想创建一个 Symbol 包装器对象 (Symbol wrapper object),你可以使用 Object() 函数:
var sym = Symbol("foo");
typeof sym; // "symbol"
var symObj = Object(sym);
typeof symObj; // "object"
对 symbol 使用 typeof 运算符
typeof运算符能帮助你识别 symbol 类型
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'
Symbols 与 for…in 迭代
Symbols 在 for…in 迭代中不可枚举。另外,Object.getOwnPropertyNames() 不会返回 symbol 对象的属性,但是你能使用 Object.getOwnPropertySymbols() 或使用新增的反射API得到它们。
var obj = {};
obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";
for (var i in obj) {
console.log(i); // logs "c" and "d"
}
Object.getOwnPropertySymbols(obj) // [Symbol(a), Symbol(b)]
Reflect.ownKeys(obj) // ["c", "d", Symbol(a), Symbol(b)]
Symbols 与 JSON.stringify()
当使用 JSON.strIngify() 时,以 symbol 值作为键的属性会被完全忽略:
JSON.stringify({[Symbol("foo")]: "foo"});
// '{}'
Symbol 包装器对象作为属性的键
当一个 Symbol 包装器对象作为一个属性的键时,这个对象将被强制转换为它包装过的 symbol 值:
var sym = Symbol("foo");
var obj = {[sym]: 1};
obj[sym]; // 1
obj[new Object(sym)]; // still 1
使用Symbol定义类的私有属性/方法
我们知道在JavaScript中,是没有如Java等面向对象语言的访问控制关键字private的,类上所有定义的属性或方法都是可公开访问的。因此这对我们进行API的设计时造成了一些困扰。
而有了Symbol以及模块化机制,类的私有属性和方法才变成可能。例如:
- 在文件 a.js中
const PASSWORD = Symbol()
class Login {
constructor(username, password) {
this.username = username
this[PASSWORD] = password
}
checkPassword(pwd) {
return this[PASSWORD] === pwd
}
}
export default Login
- 在文件 b.js 中
import Login from './a'
const login = new Login('admin', '123456')
login.checkPassword('123456') // true
login.PASSWORD // oh!no!
login[PASSWORD] // oh!no!
login["PASSWORD"] // oh!no!
由于Symbol常量PASSWORD被定义在a.js所在的模块中,外面的模块获取不到这个Symbol,也不可能再创建一个一模一样的Symbol出来(因为Symbol是唯一的),因此这个PASSWORD的Symbol只能被限制在a.js内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。