Symbol的用法
引入原因:避免属性名冲突,保证每个属性名独一无二
JavaScript的数据类型:undefined、null、Boolean、Number、String、Symbol、Object
对象属性名的两种形式:字符串、Symbol类型
基础知识
Symbol()
:生成Symbol值- 可接受一个字符串作为参数,表示Symbol实例的描述,加入的目的是方便区分各个Symbol值
- 若参数是一个对象,将调用对象的toString方法转为字符串形式,比如Object对象调用方法会生成
[object Object]
- 由于Symbol值是一个原始类型,而非对象,所以不能使用new等命令
- 每个使用该函数生成的Symbol值都是独一无二的
- Symbol值不能与其他值进行运算,比如转为字符串形式(与字符串使用+号、使用反引号引入symbol、new构造函数)、转为数字会报类型错误
- Symbol可显式转为字符串形式(使用String()、toString()),可以转为Boolean
- 每个symbol值都是不相等的,所以symbol可以作为标识符,用于对象的属性名(通过方括号/定义属性的方式指定属性名),能够防止键名被改写/覆盖,写法如:🍊
- Symbol类型可以用来定义一组常量,保证这组常量的值都是不相等的,比如用在switch语句中🧃
- Symbol作为属性名时,是一个公开属性
- symbol可消除魔术字符串(在代码中多次出现、与代码形成强耦合的某些具体的字符串/数值),规范代码风格🥦
- 遍历symbol值,可以用
Object.getOwnPropertySymbols()
、Reflect.ownKeys()
(返回所有的键名)进行属性名的遍历 - 由于常规方法不会遍历到symbol值,可以利用该特性,定义一些非私有的,但希望只用于内部的方法
Symbol.prototype.description
:返回symbol值的描述(字符串的参数)Symbol.for()
:接受一个字符串作为参数,其他类型会转为字符串- 表示注册一个symbol值到全局,之后若再次以该参数注册,会返回之前的值
- 利用全局注册的特性,可以在不同的iframe和service worker中取到同一个值
Symbol.keyFor()
:返回一个已登记的symbol类型值的key(参数),若无参,返回undefined
javascript
// 🍊:symbol作为对象的属性名
// 第一种写法:
let s = Symbol()
let a = {}
a[s] = 'hello'
// 第二种写法:
let s = Symbol()
let t = Symbol()
let a = {
[s]: 'hello',
[t] (arg) {
// ...
}
}
// 第三种写法:
let s = Symbol()
let a = {}
Object.defineProperty(a, s, {
value: 'hello'
})
// 🧃:symbol用来定义常量
const COLOR_RED = Symbol()
const COLOR_BLUE = Symbol()
function getComplete(color) {
switch (color) {
case COLOR_RED:
return COLOR_BLUE
case COLOR_BLUE:
return COLOR_RED
default:
throw new Error('undefined color')
}
}
// 🥦:消除魔法字符串
const shapeType = {
triangle: 'Triangle'
}
function getArea (shape, options) {
let area = 0
switch (shape) {
// 使用symbol值代替常规常量,避免出现魔法字符串,此时并不需要知道该值是何值,只需确保不发生冲突即可
case shapeType.triangle:
area = .5 * options.width * options.height
break
}
return area
}
// 使用symbol值代替常规常量,避免出现魔法字符串
getArea(shapeType.triangle, { width: 3, height: 9})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
内置的symbol值
定义:都指向语言定义的内部方法
Symbol.hasInstance
:当其他对象使用instanceof判断是否为该对象的实例时,会调用这个方法,返回一个布尔值🍋Symbol.isConcatSpreadable
:对象的该属性为一个布尔值,表示对象用于Array.prototype.concat
是否可以展开🍇- 哪个对象设置了该属性为false,则该对象在和concat一起使用(调用该方法/方法参数形式)时不可展开
Symbol.species
:指向一个构造函数(返回一个构造函数),当创建衍生对象时,调用该属性🍎- 作用:实例对象运行过程中,需要再次调用自身的构造函数时,会调用该属性指定的构造函数
- 用途:当子类使用继承的方法时,希望返回基类的实例,而非子类的
- 衍生对象:比如使用数组a的一些方法返回一个新数组b时,b是a的衍生对象,会调用a实例对应的对象的该属性,比如promise的then方法
Symbol.match
:指向一个方法,当执行str.match(myObj)
等于myobj[Symbol.match](this)
时,会调用该属性,返回该方法的返回值🌹Symbol.replace
:指向一个方法,当该对象被String.prototype.replace调用时,返回该方法的返回值🍀Symbol.search
:指向一个方法,当对象被String.prototype.search调用时,返回该方法的返回值🍁Symbol.split
:指向一个方法,当对象被String.prototype.split调用时,返回该方法的返回值🍧Symbol.iterator
:指向对象默认的遍历器方法,当对对象进行遍历时,会调用该方法🥩Symbol.toPrimitive
:指向一个方法,当该对象被转为原始类型时,调用这个方法,返回该对象在当前场合对应的原始类型值🌷- 被调用时,回接受一个参数,表示当前的运算模式,共有三种:
- Number:该场合需要转为数值
- String:该场合需要转为字符串
- Default:该场合可为数值,可为字符串
- 被调用时,回接受一个参数,表示当前的运算模式,共有三种:
Symbol.toStringTag
:指向一个对象属性,当对象调用String.prototype.toString时,若该属性存在,该属性的返回值回出现在toString方法的字符串中,即[object xxx]🧅Symbol.unscopables
:指向对象的属性,返回一个对象,该对象指定了使用with关键字时,哪些属性可以被with环境排除🍜
javascript
// 🍋:Symbol.hasInstance(方法)
class MyClass {
// 该方法返回一个布尔值(非布尔值会转为布尔值)
[Symbol.hasInstance](foo) {
return foo instanceof Array
}
}
// 下面的语句,将调用`Foo[Symbol.hasInstance](foo)`
[1, 2, 3] instanceof new MyClass()
// 🍇:Symbol.isConcatSpreadable(属性)
// 1. 定义在字面量对象上:当连接一个数组时,默认等于undefined,与值为true时效果相同
let arr = ['a', 'b']
arr[Symbol.isConcatSpreadable] = false
['c', 'd'].concat(arr, 'e') // ['c', 'd', ['a', 'b'], 'e']
// 2. 连接一个类数组对象时,默认为false
let likeArr = { length: 2, 0: 'c', 1: 'd' }
likeArr[Symbol.isConcatSpreadable] = true
['a', 'b'].concat(likeArr, 'e') // ['a', 'b', 'c', 'd', 'e']
// 3. 定义在类中,定义在实例中
class Arr extends Array {
constructor (args) {
super(args)
this[Symbol.isConcatSpreadable] = false
}
}
// 4. 定义在类中,定义在类本身,效果和3相同
class Arr extends Array {
constructor (args) {
super(args)
}
get [Symbol.isConcatSpreadable] () {
return false
}
}
// 🍎:Symbol.species(属性):
// 设置该属性
class Arr extends Array {
static get [Symbol.species] () {
// 未设置默认指向的是他本身(也就是this:Arr)
return Array
}
}
// 🌹:Symbol.match(方法):
String.prototype.match(regExp)
// 等同于:
regExp[Symbol.match](this)
class MyClass {
[Symbol.match] (str) {
return 'hello world'.indexOf(str)
}
}
'e'.match(new MyClass()) // 1
// 🍀:Symbol.replace(方法):
String.prototype.replace(searchVal, replaceVal)
// 等同于:
searchVal[Symbol.replace(this, replaceVal)]
const x = {}
x[Symbol.replace] = (...s) => console.log(s)
'hello'.replace(x, 'world') // ['hello', 'world']
// 等同于:
x[Symbol.replace]('hello', 'world')
// 🍁:Symbol.search(方法):
String.prototype.search(regExp)
// 等同于:
regExp[Symbol.search](this)
class MySearch {
constructor (value) {
this.value = value
}
[Symbol.search] (string) {
return string.indexOf(this.value)
}
}
'foo bar'.search(new MySearch('foo')) // 0
// 🍧:Symbol.split(方法):
String.prototype.split(sepatator, limit)
// 等同于:
separator[Symbol.split](this, limit)
class MySplitter {
constructor (value) {
this.value = value
}
[Symbol.split] (string) {
let index = string.indexOf(this.value)
if (index === -1) {
return string
}
return [
string.subStr(0, index),
string.subStr(index + this.value.length)
]
}
}
'foobar'.split(new Mysplitter('foo')) // ['', 'bar']
// 等同于:
new Mysplitter('foo')[Symbol.split]('foobar')
// 🥩:Symbol.iterator(方法):
const myIterator = {}
myIterator[Symbol.iterator] = function* () {
yield 1
yield 2
yield 3
}
[...myIterator] // [1, 2,3]
class Collection {
*[Symbol.iterator] () {
let i = 0
while (this[i] !== undefined) {
yield this[i]
++i
}
}
}
let myCollection = new Collection()
myCollection[0] = 1
myCollection[1] = 2
for (let i of myCollection) {
console.log(i)
}
// 1
// 2
// 🌷:Symbol.toPrimitive
let obj = {
[Symbol.toPrimitive] (hint) {
switch (hint) {
case 'number':
return 123
case 'string':
return 'string'
case 'default':
return 'default'
default:
throw new Error()
}
}
}
2 * obj // 246
2 + obj // '2default'
obj === 'default' // true
String(obj) // 'string'
// 🧅:Symbol.toStringTag
({ [Symbol.toStringTag]: 'foo' }.toString) // [object foo]
class Collection {
get [Symbol.toStringTag] () {
return 'jade'
}
}
let x = new Collection()
Object.prototype.toString.call(x) // [object jade]
// 🍜:Symbol.unscopable
// 未指定该属性时
class MyClass {
foo () {
return 1
}
}
var foo = function () {
return 2
}
with (MyClass.prototype) {
foo() // 1
}
// 指定该属性时
class MyClass {
foo () {
return 1
}
get [Symbol.unscopables] () {
return {
foo: true
}
}
}
var foo = function () {
return 2
}
// 此时with语句不会在当前作用域寻找foo属性,而是指向外层作用域
with (MyClass.prototype) {
foo() // 2
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216