欢迎访问我的博客https://qqqww.com/,祝码农同胞们早日走上人生巅峰,迎娶白富美~~~
1 引入Symbol的好处
为解决属性名冲突问题而生
ES5 中方法和属性的命名:
以前,我们给一个对象下的方法或者属性命名的时候,可能会与该对象原有的方法或者属性产生冲突,这样的场景想必大家也很熟悉,我们常用到的解决办法是将方法前加一段项目名称的简拼或者一些其他的方式,其根本原因就是给方发或者属性带上一个独一无二的标志,最大限度的防止命名上的冲突
ES6 中方法和属性的命名:
ES6
中引入了Symbol
数据类型,其基本解决思路也是和ES5
差不多的,只不过就是直接利用Symbol
能够表示一个独一无二的值,这就从根本上防止方法或者属性名的冲突
2 Symbol基本概念
Symbol
是ES6
中引入得一种新型的数据类型,它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值Boolean、字符串
String、数值Number、对象
Object`
2.1 基本用法
下面一个简单的例子说明如何生成
Symbol
值
let a = Symbol()
typeof a // 'symbol'
Symbol
值通过Symbol()
函数生成,且凡是属性名属于'symbol'
类型就不会与其他属性名冲突,我们来看看下面一个例子
let a1 = Symbol()
let a2 = Symbol()
a1 === a2 // false
看起来仿佛一模一样的
a1
和a2
实际上是不相等的,这进一步说明了Symbol
值是独一无二的
2.2 Symbol 中的参数
Symbol
函数中可以传参,只用以描述Symbol
实例,为了在控制台输出时利于区分
let a1 = Symbol('foo')
let a2 = Symbol('bar')
a1 // Symbol(foo)
a2 // Symbol(bar)
上述代码:
- 若不加参数,虽然二者都是独一无二的,二者是不一样的,这在上面的例子中也提到过,但是他们在控制台打印的结果却都是
Symbol()
- 现在给他们分别加上参数,在控制台打印出来就能一眼辨别了
- 那么加上参数,若参数是一样的,说明描述也一样,那他们的
Symbol
值相等吗?看下面一个例子
let a1 = Symbol('foo')
let a2 = Symbol('foo')
a1 // Symbol(foo)
a2 // Symbol(foo)
a1 === a2 // false
可见,依然不相等,这又进一步的证明了,
Symbol
值的独一无二
2.3 Symbol 转换
请看下面的四个例子,对应这四点:
- Symbol 值可以显式转为字符串
- Symbol 值也可以转为布尔值
- Symbol 值不能转为数值,且不能做数值运算
- Symbol 值不能与其他类型的值进行运算
let a1 = Symbol('foo')
String(a1) // 'Symbol(foo)'
a1.toString // 'Symbol(foo)'
let a1 = Symbol('foo')
Boolean(a1) // true
!a1 // false
let a1 = Symbol('foo')
Number(a1) // TypeError
a1 + 1 // TypeError
let a1 = Symbol('foo')
'hello' + a1 // TypeError: can't convert symbol to string
3 Symbol 值作为属性名
由于
Symbol
值的独一无二,将其作为对象的属性以防止属性名冲突问题再合适不过了注意:关于下面例子中
Object.definproperty
:
- 该方法会直接在一个对象上定义一个新对象,或者修改一个对象的现有属性,并返回这个对象
- 语法:
Object.defineProperty(obj, prop, descriptor)
- 参数:
- obj:要在obj上定义属性,就写obj
- prop:要修改的属性
- descriptor:属性值
- 例如下面例子中的:
Object.definproperty(obj3, a, { value: '我是symbol值a作为obj对象的属性' })
表示在对象obj3
上修改属性prop
,其属性值修改为'我是symbol值a作为obj对象的属性'
let a = Symbol()
// 写法一
let obj1 = {}
obj1[a] = '我是symbol值a作为obj对象的属性'
// 写法二
let obj2 = {
[a]: '我是symbol值a作为obj对象的属性'
}
// 第三种写法
let obj3 = {}
Object.definproperty(obj3, a, { value: '我是symbol值a作为obj对象的属性' })
obj1[a] // '我是symbol值a作为obj对象的属性'
obj2[a] // '我是symbol值a作为obj对象的属性'
obj3[a] // '我是symbol值a作为obj对象的属性'
注意:不能像以前对象那样用
.
运算
let a = Symbol()
let obj = {}
obj[a] = '我是symbol值a作为obj对象的属性'
obj[a] // '我是symbol值a作为obj对象的属性'
obj.a // undefined
注意:在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中
let a = Symbol()
let obj = {
[a]: function(x) { ... }
}
也可以简写为
let a = Symbol()
let obj = {
[a](x) { ... }
}
// 调用
obj[a](1)
4 Symbol 类型定义常量
定义一组常量,保证这组常量的值都是不相等的
const obj = {}
obj.alw = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
}
5 Symbol 消除魔术字符串
先解释几个名词:
什么叫魔术字符串:在代码中多次出现,且与代码形成强耦合的某一个具体的字符串或者数值,这种关联性太多的字符串就会导致变量含义不明确,所以应当尽量消除魔术字符串,改由含义清晰的变量代替
耦合性:常用来表示块之间的联系,耦合性越强,其代码块或者模块之间的联系就越强,设计代码块或者模块就是为了其功能的独立性,所以往往更希望低耦合
内聚性:常用来表示块内在的联系,内聚性越强,模块功能强度越强,即一个模块内各元素(语名之间、程序段之间)联系的就越紧密,所以一般就更希望高内聚
看了以上几个名词解释想必就能清晰地理解上面的魔术字符串了吧,简单粗暴的讲就是这样的变量出现多次,就和代码形成了强大的联系,不利于以后的修改和维护,下面的几个例子借用了阮一峰老师在《ES6标准入门第3版》中的例子
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 }); // 魔术字符串
常用的消除魔术字符串的方法,就是把它写成一个变量,这种方法也叫变量本地化,这样假如需要维护和修改就只需要修改最初定义的变量的值就行了
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 });
可以发现
shapeType.triangle
等于哪个值并不重要,只要确保不会跟其他shapeType
属性的值冲突即可。因此,这里就很适合改用Symbol
值
const shapeType = {
triangle: Symbol()
};
6 属性名的遍历
ES6
中提供有Object.getOwnPropertySymbols
方法,可以获取指定对象的所有Symbol
属性名
Object.getOwnPropertySymbols
方法返回一个数组,成员是当前对象的所有用作属性名的Symbol
值
let a = Symbol('a')
let b = Symbol('b')
let obj = {}
obj[a] = 'hello'
obj[b] = 'world'
const objs = Object.getOwnPropertySymbols(obj)
objs // [Symbol(a), Symbol(b)]
7 Symbol.for()
若想使用同一个
Symbol
值,就用Symbol.for()
方法
let a1 = Symbol.for('a')
let a2 = Symbol.for('a')
a1 === a2 // true
let a1 = Symbol.for()
let a2 = Symbol.for()
a1 === a2 // true
let a1 = Symbol.for('a')
let a2 = Symbol.for('b')
a1 === a2 // false
看上述例子,请注意,当参数两个需要重用的值参数要不然都不写,要不然一定要一样,否则二者不会相等
8 Symbol.keyFor()
Symbol.keyFor
方法返回一个已登记的 Symbol 类型值的key
// Symbol.for 有参数
let a1 = Symbol.for('a')
Symbol.keyFor(a1) // "a"
// Symbol.for 无参数
let a1 = Symbol.for()
Symbol.keyFor(a1) // undefined
// 无 Symbol.for 表明未登记
let a1 = Symbol()
Symbol.keyFor(a1) // undefined
从上述代码看出,往往返回的就是那个参数,但是当没有参数或者没有使用
Symbol.for()
方法的时候返回undefined
9 其他
剩下的一些内容,本来我自己也接触比较少,就没在这里写,详细请移步Symbol
10 参考文章
- 阮一峰老师的Symbol