目录
Symbol类型
介绍
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入
Symbol
的原因。ES6 引入了一种新的原始数据类型
Symbol
,表示独一无二的值,是一种类似于字符串的数据类型,它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。Symbol 值通过
Symbol
函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。symbol的特点:
提供独一无二的值
Symbol 值作为对象属性名时,不能用点运算符,要用中括号表示法
Symbol 值作为对象属性名时,该属性不会出现在
for...in
、for...of
循环中
Symbol函数
可以接受参数,表示对于这个唯一值的描述。即使参数一致,symbol值也不一样。// 没有描述符的symbol值 let s = Symbol(); // 有描述符的symbol值 let s1 = Symbol('hello'); console.log(s, s1); //Symbol() Symbol(hello) console.log(typeof s); //symbol
symbol值是一个独一无二的值。
// 没有参数的情况 let s1 = Symbol(); let s2 = Symbol(); s1 === s2 // false // 有参数的情况 let s1 = Symbol('foo'); let s2 = Symbol('foo'); s1 === s2 // false
Symbol.prototype.description
获取 Symbol 的描述。创建 Symbol 的时候,可以添加一个描述,ES2019 提供了一个实例属性
description
,直接返回 Symbol 的描述。如果没有描述则返回undefined。const sym = Symbol('foo'); sym.description // "foo"
Symbol的应用
1 给对象内追加属性
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
假设有一个obj内部的属性都是不可枚举属性,不动其他属性的情况下,现需要在obj内放一个值'张三',属性名无所谓。你知道obj对象内有什么属性吗?肯定不知道对不对,好,那现在往对象里添加一个属性,值为'张三',你敢添加吗?假设你要添加的属性名是name,那么会出现两种情况,要么obj内没有name属性,你添加成功,要么obj内有name属性,你覆盖了人家原来的值。这时候symbol的作用就出来了。
let obj = { // ...此处省略n多个属性 name: 'ronda' }; // 因为symbol是独一无二的,那么属性名就可以用symbol来表示。 let tempName = Symbol('name'); obj[tempName] = '张三'; // '张三'保存到了obj对象中 console.log(obj[tempName]); //张三 console.log(obj);
2 消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
案例一:
function getArea(shape, options) { let area = 0; switch (shape) { case 'Triangle': // 魔术字符串 area = .5 * options.width * options.height; break; /* ... more code ... */ } return area; } getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串
上面代码中,字符串
Triangle
就是一个魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。代码耦合:一个软件结构内不同模块之间互连程度的度量(耦合性也叫块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差,模块间耦合的高低取决于模块间接口的复杂性,调用的方式以及传递的信息。
常用的消除魔术字符串的方法,就是把它写成一个变量。
const shapeType = { triangle: 'Triangle' }; function getArea(shape, options) { let area = 0; switch (shape) { case shapeType.triangle: area = .5 * options.width * options.height; break; } return area; } getArea(shapeType.triangle, { width: 100, height: 100 });
上面代码中,我们把
Triangle
写成shapeType
对象的triangle
属性,这样就消除了强耦合。如果仔细分析,可以发现
shapeType.triangle
等于哪个值并不重要,只要确保不会跟其他shapeType
属性的值冲突即可。因此,这里就很适合改用 Symbol 值。const shapeType = { triangle: Symbol() }; // 通过shapeType.triangle 使用
上面代码中,除了将
shapeType.triangle
的值设为一个 Symbol,其他地方都不用修改。案例二:
let obj = { attr1: Symbol('one'), attr2: Symbol('two'), attr3: Symbol('three'), attr4: Symbol('four'), }; function test(param) { switch (param) { case obj.attr1: console.log('这是one的操作'); break; case obj.attr2: console.log('这是two的操作'); break; case obj.attr3: console.log('这是three的操作'); break; case obj.attr4: console.log('这是four的操作'); break; default: console.log('这是默认的操作'); break; } } test(obj.attr1); //这是one的操作
symbol属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在
for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。但是,它也不是私有属性,有一个
Object.getOwnPropertySymbols()
方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。const obj = {}; let a = Symbol('a'); let b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; const objectSymbols = Object.getOwnPropertySymbols(obj); objectSymbols // [Symbol(a), Symbol(b)]
下面是另一个例子,
Object.getOwnPropertySymbols()
方法与for...in
循环、Object.getOwnPropertyNames
方法进行对比的例子。const obj = {}; const foo = Symbol('foo'); obj[foo] = 'bar'; for (let i in obj) { console.log(i); // 无输出 } Object.getOwnPropertyNames(obj) // [] Object.getOwnPropertySymbols(obj) // [Symbol(foo)]
另一个新的 API,
Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]
由于以 Symbol 值作为键名,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
Symbol.for()&&Symbol.keyFor()
有时,我们希望重新使用同一个 Symbol 值,
Symbol.for()
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。全局 symbol 注册表中的一个记录结构如下:
字段名 字段值 [[key]] 一个字符串,用来标识每个 symbol [[symbol]] 存储的 symbol 值 Symbol.for(key)
Symbol.for(key) 方法会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
语法: Symbol.for(key); 参数: key 一个字符串,作为 symbol 注册表中与某 symbol 关联的键(同时也会作为该 symbol 的描述)。 返回值: 返回由给定的 key 找到的 symbol,否则就是返回新创建的 symbol。
Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中,键为 "foo" Symbol.for("foo"); // 从 symbol 注册表中读取键为"foo"的 symbol Symbol.for("bar") === Symbol.for("bar"); // true,证明了上面说的 Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 symbol let sym = Symbol.for("mario"); sym.toString(); // "Symbol(mario)",mario 既是该 symbol 在 symbol 注册表中的键名,又是该 symbol 自身的描述字符串,为了防止冲突,最好给你要放入 symbol 注册表中的 symbol 带上键前缀。 Symbol.for("mdn.foo"); Symbol.for("mdn.bar");
Symbol.keyFor(sym)
Symbol.keyFor(sym) 方法用来获取全局symbol 注册表中与某个 symbol 关联的键。 该方法返回一个已登记的 Symbol 类型值的key。 语法: Symbol.keyFor(sym); 参数: sym 必选参数,需要查找键值的某个 Symbol 。 返回值: 如果全局注册表中查找到该symbol,则返回该symbol的key值,返回值为字符串类型。否则返回undefined
// 创建一个全局 Symbol let globalSym = Symbol.for("foo"); Symbol.keyFor(globalSym); // "foo" let localSym = Symbol(); Symbol.keyFor(localSym); // undefined // 以下Symbol不是保存在全局Symbol注册表中 Symbol.keyFor(Symbol.iterator) // undefined
07-16
217
01-28
1500
07-02
303
11-27
02-21
2751