Data Structures in JavaScript
In this article, I’m going to summarize everything you should know about data structures in JavaScript.
Data Types
The mind graph shows 7 primitive types (left) and 1 reference type object
. Among the primitives, Symbol
was introduced by ES2015 (ES6) and BigInt
by ES2020 (ES11), which means before ES6 there are only 5 primitive types in JavaScript (number
, boolean
, string
, undefined
, null
)
The difference between the primitive type and the reference type lies in their storage in memory:
- Primitive types are stored in the
Stack
.- The value is stored in the stack.
- Passed by value.
- When referenced or copied, an equivalent variable would be created with the exact same value.
- Reference types are stored in the
Heap
.- The address that points to the actual value in the heap is stored in the stack (i.e. the reference to the value)
- Passed by reference.
- All references are pointing to the same heap value, which means modification to the value would affect all the references.
Example 1 for References
1 | let a = { |
Since b
and a
are all pointing to the same object value, modification from b
affects the output of a.name
Example 2 for References
1 | let a = { |
Inside the function change
, we change the age
of the input param to 24, that’s the reason for the output of a
. However, we assigned a new object (with its new address) to the input param o
, so o
then points to a new object. So b
is assigned by the return value o
, a new object with age 30
.
Data Type Detection
Method 1: typeof
1 | typeof 1 // 'number' |
We can see we should only use typeof
for number
, string
, undefined
, symbol
, bigint
, because it’s not accurate for null
and reference types (except for function
).
Notice that the result for null
using typeof
is object
, the same for array []
.
Method 2: instanceof
1 | let Car = function () {} |
Notice that literal string
is not the instance of String
class.
Let’s try to implement instanceof
by ourself for further understanding!
1 | function isInstanceOf(value, type) { |
instanceof
can precisely check reference types, but not for primitive types.
So is there a perfect way to check all the data types? Yes, there is.
Method 3: Object.prototype.toString
1 | Object.prototype.toString({}) // '[object Object]' |
toString
is the prototype method of Object
, by calling it we could get a formated answer for types, i.e. [object Type]
(notice that every type is Capitalized). It’s accurate enough for us to detect any data types.
Let’s implement a type detecting method using Object.prototype.toString
under the hood:
1 | // return the type in a capitalized string |
Data Type Conversion
Watch out for the ==
equation operator! It would do implicitly type cast before comparison, and sometimes the result could be unexpected!
1 | '123' == 123 // true |
Therefore, we should always use ===
for comparison!
And some wired conversions:
1 | Number(null) // 0 |
Type Casting Rules
Type Cast for primitives includes Number()
, parseInt()
, parseFloat()
, toString()
, String()
, Boolean()
.
And those wired results of ==
are all because of the rules of type cast here. So let’s take a closer look.
Rules of Number()
- For boolean,
true
andfalse
would be converted to1
and0
respectively - For number, return itself
- For null, return
0
- For undefined, return
NaN
- For string:
- if the string only contains digits or hex that starts with
0x
(sign is allowed), then coverts it to decimal number - if the string is in a valid format of float, then converts it to float number
- if empty string, return
0
- else, return
NaN
- if the string only contains digits or hex that starts with
- For Symbol, throw Error
- For Object:
- If there is
[Symbol.toPrimitive]
method, calls it and returns the result - else, call its
valueOf()
method, and follows the above rules to return the converted value- if the result is
NaN
, the calltoString()
method, again follows the above rules to return the converted value
- if the result is
- return
NaN
if there is no other choice.
- If there is
1 | Number(true) // 1 |
Rules of Boolean()
Simple Rule:
- if value is
undefined
,null
,false
,''
,0 (+0 and -0)
,NaN
, returnfalse
- else, return
true
1 | Boolean(0) // false |
Implicit Type Cast
Regarding two values are not the same type, some operators would apply implicit type casting ahead of time, including logical Operators like (&&
, ||
, !
) , operators (+
, -
, *
, /
), relation operators (>
, <
, <=
, >=
), equality operator (==
) and condition if
while
.
Rules of ==
- if types are the same, no need to cast type
- if one of the value is
null
orundefined
, return true if the other one is alsonull
orundefined
, otherwise false - if one of them is
Symbol
, return false - if the two values are
string
andnumber
, thenstring
value would be converted tonumber
- if one of them is
boolean
, then converts tonumber
- if one of them is
object
and the other one isstring
,number
orsymbol
, then firstly convertsobject
to primitive type viavalueOf
ortoString
.
Rules of +
- if the two are
number
, sum them up and return the result - if the two are
string
, concat them and return - if one of them is
string
while the other isundefined
,null
, orboolean
, then callstoString
of the other value and concats the strings. - if one of them is
number
while the other isundefined
,null
, orboolean
, then converts the other to number and sum them up - if the two values are
string
andnumber
, then coverts number to string and concats them
1 | 1 + 2 // 3 |
Rules of Object
- If there is
Symbol.toPrimitive
, call it and return result - call
valueOf
, if the result is primitive, then return - call
toString
, if the result is primitive, then return - if none of the above result is primitive, throw error
1 | let obj = { |
Summary
We’ve discussed three aspects of data structures in JavaScript:
- The basics of data types
- The common type detection methods
- Type casting in JavaScript: be careful about the implicit casting!