Skip to content

Commit e256711

Browse files
committed
add Symbol type
1 parent aed27f6 commit e256711

File tree

1 file changed

+289
-0
lines changed

1 file changed

+289
-0
lines changed

pages/4.3-Symbol-type.md

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
# Symbol type
2+
3+
By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types.
4+
5+
Till now we’ve only seen strings. Now let’s see the advantages that symbols can give us.
6+
7+
## Symbols
8+
9+
“Symbol” value represents a unique identifier.
10+
11+
A value of this type can be created using `Symbol()`:
12+
13+
```
14+
// id is a new symbol
15+
let id = Symbol();
16+
```
17+
18+
We can also give symbol a description (also called a symbol name), mostly useful for debugging purposes:
19+
20+
```
21+
// id is a symbol with the description "id"
22+
let id = Symbol("id");
23+
```
24+
25+
Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn’t affect anything.
26+
27+
For instance, here are two symbols with the same description – they are not equal:
28+
29+
30+
```
31+
let id1 = Symbol("id");
32+
let id2 = Symbol("id");
33+
34+
alert(id1 == id2); // false
35+
```
36+
37+
If you are familiar with Ruby or another language that also has some sort of “symbols” – please don’t be misguided. JavaScript symbols are different.
38+
39+
40+
***
41+
42+
#### Symbols don’t auto-convert to a string
43+
44+
Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don’t auto-convert.
45+
46+
For instance, this `alert` will show an error:
47+
48+
```
49+
let id = Symbol("id");
50+
alert(id); // TypeError: Cannot convert a Symbol value to a string
51+
```
52+
53+
If we really want to show a symbol, we need to call `.toString()` on it, like here:
54+
55+
56+
```
57+
let id = Symbol("id");
58+
alert(id.toString()); // Symbol(id), now it works
59+
```
60+
61+
That’s a “language guard” against messing up, because strings and symbols are fundamentally different and should not occasionally convert one into another.
62+
63+
***
64+
65+
## “Hidden” properties
66+
67+
Symbols allow us to create “hidden” properties of an object, that no other part of code can occasionally access or overwrite.
68+
69+
For instance, if we want to store an “identifier” for the object `user`, we can use a symbol as a key for it:
70+
71+
72+
```
73+
let user = { name: "John" };
74+
let id = Symbol("id");
75+
76+
user[id] = "ID Value";
77+
alert( user[id] ); // we can access the data using the symbol as the key
78+
```
79+
80+
What’s the benefit over using `Symbol("id")` over a string `"id"`?
81+
82+
Let’s make the example a bit deeper to see that.
83+
84+
Imagine that another script wants to have its own “id” property inside `user`, for its own purposes. That may be another JavaScript library, so the scripts are completely unaware of each other.
85+
86+
Then that script can create its own `Symbol("id")`, like this:
87+
88+
```
89+
// ...
90+
let id = Symbol("id");
91+
92+
user[id] = "Their id value";
93+
```
94+
95+
There will be no conflict, because symbols are always different, even if they have the same name.
96+
97+
Now note that if we used a string `"id"` instead of a symbol for the same purpose, then there would be a conflict:
98+
99+
100+
```
101+
let user = { name: "John" };
102+
103+
// our script uses "id" property
104+
user.id = "ID Value";
105+
106+
// ...if later another script the uses "id" for its purposes...
107+
108+
user.id = "Their id value"
109+
// boom! overwritten! it did not mean to harm the colleague, but did it!
110+
```
111+
112+
### Symbols in a literal
113+
114+
If we want to use a symbol in an object literal, we need square brackets.
115+
116+
Like this:
117+
118+
```
119+
let id = Symbol("id");
120+
121+
let user = {
122+
name: "John",
123+
[id]: 123 // not just "id: 123"
124+
};
125+
```
126+
127+
That’s because we need the value from the variable `id` as the key, not the string “id”.
128+
129+
### Symbols are skipped by for…in
130+
131+
Symbolic properties do not participate in `for..in` loop.
132+
133+
For instance:
134+
135+
136+
```
137+
let id = Symbol("id");
138+
let user = {
139+
name: "John",
140+
age: 30,
141+
[id]: 123
142+
};
143+
144+
for (let key in user) alert(key); // name, age (no symbols)
145+
146+
// the direct access by the symbol works
147+
alert( "Direct: " + user[id] );
148+
```
149+
150+
That’s a part of the general “hiding” concept. If another script or a library loops over our object, it won’t unexpectedly access a symbolic property.
151+
152+
In contrast, [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign "Object.assign") copies both string and symbol properties:
153+
154+
155+
```
156+
let id = Symbol("id");
157+
let user = {
158+
[id]: 123
159+
};
160+
161+
let clone = Object.assign({}, user);
162+
163+
alert( clone[id] ); // 123
164+
```
165+
166+
There’s no paradox here. That’s by design. The idea is that when we clone an object or merge objects, we usually want all properties to be copied (including symbols like `id`).
167+
168+
***
169+
170+
#### Property keys of other types are coerced to strings
171+
172+
We can only use strings or symbols as keys in objects. Other types are converted to strings.
173+
174+
For instance, a number `0` becomes a string `"0"` when used as a property key:
175+
176+
```
177+
let obj = {
178+
0: "test" // same as "0": "test"
179+
};
180+
181+
// both alerts access the same property (the number 0 is converted to string "0")
182+
alert( obj["0"] ); // test
183+
alert( obj[0] ); // test (same property)
184+
```
185+
186+
***
187+
188+
## Global symbols
189+
190+
As we’ve seen, usually all symbols are different, even if they have the same names. But sometimes we want same-named symbols to be same entities.
191+
192+
For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property.
193+
194+
To achieve that, there exists a global symbol registry. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol.
195+
196+
In order to create or read a symbol in the registry, use `Symbol.for(key)`.
197+
198+
That call checks the global registry, and if there’s a symbol described as `key`, then returns it, otherwise creates a new symbol `Symbol(key)` and stores it in the registry by the given `key`.
199+
200+
For instance:
201+
202+
203+
```
204+
// read from the global registry
205+
let id = Symbol.for("id"); // if the symbol did not exist, it is created
206+
207+
// read it again
208+
let idAgain = Symbol.for("id");
209+
210+
// the same symbol
211+
alert( id === idAgain ); // true
212+
```
213+
214+
Symbols inside the registry are called global symbols. If we want an application-wide symbol, accessible everywhere in the code – that’s what they are for.
215+
216+
***
217+
218+
#### That sounds like Ruby
219+
220+
In some programming languages, like Ruby, there’s a single symbol per name.
221+
222+
In JavaScript, as we can see, that’s right for global symbols.
223+
224+
***
225+
226+
### Symbol.keyFor
227+
228+
For global symbols, not only `Symbol.for(key)` returns a symbol by name, but there’s a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol.
229+
230+
For instance:
231+
232+
233+
```
234+
let sym = Symbol.for("name");
235+
let sym2 = Symbol.for("id");
236+
237+
// get name from symbol
238+
alert( Symbol.keyFor(sym) ); // name
239+
alert( Symbol.keyFor(sym2) ); // id
240+
```
241+
242+
The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn’t work for non-global symbols. If the symbol is not global, it won’t be able to find it and return `undefined`.
243+
244+
For instance:
245+
246+
247+
```
248+
alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol
249+
250+
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol
251+
```
252+
253+
## System symbols
254+
255+
There exist many “system” symbols that JavaScript uses internally, and we can use them to fine-tune various aspects of our objects.
256+
257+
They are listed in the specification in the [Well-known symbols](https://tc39.github.io/ecma262/#sec-well-known-symbols "Well-known symbols") table:
258+
259+
* `Symbol.hasInstance`
260+
261+
* `Symbol.isConcatSpreadable`
262+
263+
* `Symbol.iterator`
264+
265+
* `Symbol.toPrimitive`
266+
267+
* ... and so on
268+
269+
For instance, `Symbol.toPrimitive` allows us to describe object to primitive conversion. We’ll see its use very soon.
270+
271+
Other symbols will also become familiar when we study the corresponding language features.
272+
273+
## Summary
274+
275+
`Symbol` is a primitive type for unique identifiers.
276+
277+
Symbols are created with `Symbol()` call with an optional description.
278+
279+
Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: `Symbol.for(key)` returns (creates if needed) a global symbol with `key` as the name. Multiple calls of `Symbol.for` return exactly the same symbol.
280+
281+
Symbols have two main use cases:
282+
283+
1. “Hidden” object properties. If we want to add a property into an object that “belongs” to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won’t be occasionally listed. Also it won’t be accessed directly, because another script does not have our symbol, so it will not occasionally intervene into its actions.
284+
285+
So we can “covertly” hide something into objects that we need, but others should not see, using symbolic properties.
286+
287+
2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we’ll use `Symbol.iterator` for iterables, `Symbol.toPrimitive` to setup object-to-primitive conversion and so on.
288+
289+
Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols "Object.getOwnPropertySymbols(obj)") that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys "Reflect.ownKeys(obj)") that returns all keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in methods and syntax constructs adhere to a common agreement that they are. And the one who explicitly calls the aforementioned methods probably understands well what he’s doing.

0 commit comments

Comments
 (0)