Primitive and Reference Data Types in JavaScript
In the overview below, you see a list of primitive and reference data types, each with an example.
Let's take a closer look at the types before we point out the differences in terms of memory behavior.
You can check the data type of any variable with the typeof check.
let example = "hello";console.log(typeof example);
// outputs: > "string"
Primitive Data Types
Data Type null indicates that there is an object missing or invalid.
Special primitive type having additional usage for its value: if the object is not inherited, then
null
is shown;
let example = null;console.log(typeof example);
// outputs: > "object"
Data Type undefined as the naming indicated, the value is yet not defined.
let example;
let count = 6;if (count > 7) {
example = "gets a value";
}console.log(typeof example);
// outputs: > "undefined"
Data Type Boolean can only have two values true or false. It’s often used to write conditions to control the flow of a program.
let example = true;
if (example) {
example = false;
}console.log(typeof example);
// outputs: > "boolean"
Data Type Number represents a floating-point number between (2⁵³ − 1) and 2⁵³ − 1).
let example = 33; // or something more precise 33.33console.log(typeof example);
// outputs: > "number"
Data Type String is a sequence of characters used to represent text for example letters, digits, special characters, and control characters.
let example = "hello";console.log(typeof example);
// outputs: > "string"
Data Type BigInt holds numeric values and is relevant if you need to work with very large numbers that exceed the safe integer limit for the Data Type Number. You can check the max safe integer for numbers with Number.MAX_SAFE_INTEGER
console.log(Number.MAX_SAFE_INTEGER);
// outputs: 9007199254740991let example = 33n ** 33n;console.log(typeof example);
// outputs: > "bigint"console.log(example);
// outputs: > 129110040087761027839616029934664535539337183380513nlet example2 = 33 ** 33; // number data typeconsole.log(example2);
// outputs: > 1.2911004008776101e+50let example3 = 33 ** 33n; // number and bigint is not mixableconsole.log(example3);
// outputs: "Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions"
Data Type Symbol is a guaranteed way to ensure that the value is unique and immutable.
let example = Symbol("hello");console.log(typeof example);
// outputs: > "symbol"let example2 = Symbol("hello");console.log(example === example2);
// outputs: > false
Reference Data Types
Data Type Object holds key-value pairs with a unordered collection of data and instructions. Can be created with new Object()
or the literal syntax {}
let example = {hello: "world"}; // same as...
let example2 = new Object();
example2.hello = "world";console.log(typeof example);
// outputs: > "object"// console.dir gives you more insights about the inheritance in the // dev console -> see the image below
console.dir(example);
Data Type Function can encapsulate a piece of code and can be self inherited or called by other code.
// arrow functions are a compact alternative to function
let example = () => 2 + 2;// same as...
function example2() {
return 2 + 2;
}console.log(typeof example);
// outputs: > "function"// insights about the inheritence and scopes -> see the image below
console.dir(example);
For more details about Data Types, please visit the very good documentation from Mozilla here.
Memory behavior of Primitive Types
For each primitive type, the value will always be saved separately, even if you save variable example into example2.
let example = "hello";
let example2 = example;console.log(example);
console.log(example2);// outputs: > "hello"
// outputs: > "hello"
The key point here is a change of the variable example would not affect example2, because the value is copied.
example = "hello world";console.log(example);
console.log(example2);// outputs: > "hello world"
// outputs: > "hello"
Memory behavior of Reference Types
In contrast to primitive types, reference types like the name implies are only holding a reference to the value, in this example the object.
let example = {hello: "world"};
let example2 = example;console.log(example);
console.log(example2);// outputs: > {hello: "world"}
// outputs: > {hello: "world"}
The key point here is a change of the variable example does affect example2, because the value is only referenced.
example.additionalField = 33;console.log(example);
console.log(example2);// outputs: > {hello: "world", additionalField: 33}
// outputs: > {hello: "world", additionalField: 33}
If you hand over your reference to a function, your original object is affected by changes.
let example = {hello: "world"};const exampleFunction = (object) => {
object.number = 33;
return object;
}exampleFunction(example);console.log(example);
// outputs: > {hello: "world", number: 33}
You can easily solve this by using the spread operator and save a copy into a new variable. You can read more about the spread operator here.
let example = {hello: "world"};const exampleFunction = (object) => {
const newObject = {...object};
newObject.number = 333;
console.log(newObject);
return object;
}exampleFunction(example);console.log(example);
// outputs: > {hello: "world", number: 33}
// outputs: > {hello: "world"}
Keep in mind that almost everything you make with the new keyword is an instance of an object, also arrays no matter if you create them by calling new Array()
or just []
.
I hope I gave a well-rounded introduction to primitive and reference types. Your welcome to write any questions in the comments!
More content at plainenglish.io