ES6 Symbol讲解

简述

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内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。

猜你喜欢

转载自blog.csdn.net/weixin_44116302/article/details/103862268