Understanding primitive and reference types in JavaScript
Understanding JavaScript's primitive and reference types is a core concept. In this article, Olasunkanmi dives into the critical information needed to understand the basics.
Table of contents
If you are new to JavaScript, you may not be very familiar with the concept of data types. This may be because JavaScript is a loosely typed language.
Yet, as a JavaScript developer, you use data types regularly but may not completely understand how they work.
In JavaScript, there are two main categories of data types: primitive and reference types. This article breaks down these types, explores their differences, and provides practical examples. Whether you're a beginner or a seasoned developer, this guide will help you gain a solid grasp of JavaScript data types.
Let's begin.
Primitive data types
Primitive types are stored directly in memory and are easy and fast to manipulate, unlike more complex reference types.
A primitive itself is different from a variable assigned a primitive value. For instance:
let a = 5; console.log(a) //log 5 a = 6 console.log(a) //logs 6
5
in the above code snippet is a primitive but a
is just a variable with a primitive value. This means a
can be reassigned to a new variable, as seen above, but 5
, a primitive, cannot be changed or modified.
In JavaScript, we have seven primitive types:
- String
- Number
- Bigint
- Boolean
- Undefined
- Null
- Symbol
Now, let's discuss each of the primitive data types and their characteristics. Note that this article won't discuss the Symbol data type.
String
A string in JavaScript is a basic data type representing a sequence of characters, like words or sentences, stored in memory as UTF-16 character codes. Strings can be made using single quotes ('...'), double quotes ("..."), or backticks(`...`), with backticks being useful for template literals that insert variables or expressions.
Here's how you create and manipulate strings:
let name = 'John' // single quotes let place = "Portugal" // double quotes let sentence = `${name} lives in ${place}` // template literals with backticks console.log(sentence) // John lives in Portugal
String concatenation, using the +
operator, lets you join strings:
let firstName = 'John' let lastName = 'Doe' let name = firstName + " " + lastName console.log(name) // 'John Doe'
When a string is concatenated with a number or other value, JavaScript automatically converts it to a string using its toString()
method. For instance, 'hello' + 50
becomes 'hello50'
.
Number
The number data type in JavaScript handles numeric values, including integers and floating-point numbers. These are represented using the 64-bit floating-point format from the IEEE 754 standard, allowing them to store whole numbers and decimals.
It can hold positive floating-point numbers between 2^1074
and 2^1024
and negative ones between -2^-1074
and -2^1024
. For integers, it's safe within the range -(2^53 − 1)
to 2^53 − 1
, and you can perform various arithmetic operations like addition (+
), subtraction (-
), multiplication (*
), division (/
), and modulo (%
). Additionally, JavaScript offers built-in math functions such as Math.round()
, Math.floor()
, and Math.max()
.
However, it's worth noting that when using these arithmetic operations, JavaScript automatically converts values of other types to numbers. For example:
console.log('4' / '2') // logs 2 console.log('4' * '2') // logs 8
The +
operator is an exception, though, as it is used to concatenate two strings, as stated above in the strings section. Hence, '4' + '2'
will only concatenate both strings and result in '42'
.
Lastly, numbers support Infinity
and -Infinity
for positive and negative infinity, along with NaN
(not a number), which appears when a mathematical operation results in an unrepresentable value.
BigInt
BigInt
in JavaScript is a special numeric type that handles large integers. Unlike the regular Number
type, BigInt
can store numbers much larger than 2^53 − 1 (Number.MAX_SAFE_INTEGER)
.
Creating a BigInt
is easy; just add an n
to the end of an integer or use the BigInt()
function:
const x = BigInt(Number.MAX_SAFE_INTEGER * 2); // 18014398509481982n const myBigInt = 1n console.log(typeof myBigInt); // Output: bigint
Similar to regular numbers, you can use common operators with BigInt
, like +
, *
, -
, **
, and %
:
const x = 1n; const y = 2n; const add = x + y // 3n
However, keep in mind that mixing BigInt
values with regular Number
values in arithmetic operations will result in a TypeError
.
Introduced in ECMAScript 2020, BigInt
is relatively new in JavaScript, so be cautious about browser and environment support, especially in production code. Always check compatibility before using it.
Boolean
Boolean in JavaScript deals with logical values, having only two options: true
and false
.
They're commonly used in conditional statements to determine whether a piece of code should run. For example:
let a = 5; let b = 6; let condition = b > a // true if(condition) { console.log("b is greater than a"); } else { console.log("a is greater than b"); }
Importantly, not all values are automatically considered true or false in JavaScript. Empty strings, zero, and null
are evaluated as false
, while an empty array or object is considered true
:
console.log(Boolean('')) // false console.log(Boolean(0)) // false console.log(Boolean([])) // true console.log(Boolean({})) // true
Undefined
In JavaScript, undefined
signifies a value that hasn't been initialized or given a value yet. It's a basic value used when a variable or attribute lacks an assigned value. When a variable is declared but not given a value, it automatically becomes undefined
. For example:
let x; console.log(x) // Output: undefined
Here, x
is declared but isn't given a value, so it's undefined
.
Keep in mind, undefined
is considered a falsy value in JavaScript, meaning it is evaluated as false
in boolean contexts. Essentially, think of undefined
as a placeholder indicating that a variable hasn't been defined or assigned a value in JavaScript.
Null
In JavaScript, the Null type is simply represented by the value null
. It's a primitive value used to signify the intentional absence of an object or when a variable is deliberately assigned no value:
const x = null; console.log(x); // Output: null
Here, x
is explicitly set to null
, indicating it intentionally has no object value.
Remember, unlike the undefined
type, where the absence of a value is indicated, null
denotes the absence of an object.
Similar to undefined
, null
is considered a falsy value, meaning it evaluates to false
in boolean contexts.
Reference data types
Unlike primitive types, reference types in JavaScript are objects. This means they come with properties and methods that can be accessed and modified.
Reference data types aren't directly stored in variables. Instead, variables hold references or pointers to their memory locations. So, when you create a variable for a reference type, it doesn't store the object itself but a reference to it.
Unlike primitive types, reference types can be changed or mutated. Any alteration will be seen in all variables pointing to that object—more on this in the Objects section.
The primary reference types in JavaScript include:
- Objects
- Arrays
- Functions
Objects
In JavaScript, objects are reference data types represented by collections of key-value pairs. You create objects using curly braces, assigning them to variables. For example:
let person = { name: "John", age: 30, city: "New York" };
You can access properties using dot notation, modify values, and even embed functions as methods. For instance:
console.log(person.name); // "John" person.age = 31; person.city = "San Francisco"; console.log(person.age); // 31
When assigning an object to a variable, you store a reference to the object's memory location in the variable under the hood. This allows data and behavior to be shared between variables.
let person1 = { name: "John", age: 30, city: "New York" }; let person2 = person1; console.log(person1 === person2); // true
Here, person1
and person2
reference the same object. Changes made through one reference affect the other. As an illustration, examine the below code snippet :
person2.id = 2; console.log(person1); // { name: "John", age: 30, city: "New York", id: 2} person1.name = 'James' console.log(person2); // { name: "James", age: 30, city: "New York", id:2 }
Modifying person2
reflects in person1
, and vice versa, as both references point to the same object in memory. This demonstrates that objects in JavaScript are not directly copied but referenced, making changes visible through any reference pointing to the same object.
Arrays
Arrays in JavaScript are a type of reference that enables you to store multiple values in a single variable. Similar to objects, arrays are reference types and can be shared among variables, facilitating shared data and behavior.
Declared with square brackets, arrays can be accessed and modified akin to objects. Here's a quick example:
let fruits = ["apple", "banana", "orange"]; console.log(fruits[0]); // "apple" console.log(fruits.length); // 3 fruits[1] = "grape"; console.log(fruits); // ["apple", "grape", "orange"] fruits.push("watermelon"); console.log(fruits); // ["apple", "grape", "orange", "watermelon"] let removedFruit = fruits.pop(); console.log(fruits); // ["apple", "grape", "orange"] console.log(removedFruit); // "watermelon"
Arrays efficiently organize and manipulate data collections, providing methods and properties for data management and retrieval. Notably, arrays are zero-indexed, meaning the first element has an index of 0.
Despite being technically objects, arrays in JavaScript have distinct features. While checking their type using typeof
, for example console.log(typeof([]))
returns "object", arrays offer built-in functionality for indexing, managing lengths, and using array-specific methods. This makes them a potent tool for handling collections of values in JavaScript.
Functions
In JavaScript, functions are a special object type, making them reference types. Functions can be invoked to perform tasks or return values. They're considered reference types because they can be assigned to variables and passed as arguments to other functions.
Here's an example:
// Creating a function
function greet(name) {
console.log(`Hello, ${name}!`);
}
// Assigning a function to a variable
let myFunc = greet;
// Invoking the function through the variable
myFunc("John"); // Output: Hello, John!
In this snippet, greet is a function assigned to myFunc
, establishing a reference to its memory location. This demonstrates how myFunc
maintains a connection to the greet
function in memory whenever it is called with an argument.
Assigning a function to a variable creates a reference, not a copy. As reference types in JavaScript, functions offer powerful capabilities, supporting higher-order functions, callbacks, and functional programming paradigms.
The differences between primitive and reference types
The difference between primitive and reference types is how they are passed and stored in memory. Let's explore each:
1. Primitive types are passed by value in memory
When you assign a primitive value to a variable, a copy of the value is created and assigned to the new variable. Modifying this copy doesn't affect the original value.
For example:
let num1 = 50; let num2 = num1; num2 = 20; console.log(num1); // Output: 50 (original value remains unchanged)
Primitive data types are stored on a stack, a simple data structure used by the computer to store and retrieve data. Each variable containing a primitive value has its own space on the stack, independent of other variables.
In the code snippet above, the computer allocates space for num1
and stores its assigned value in the stack. Similarly, when num2
is created, the computer reserves additional space on the stack and stores the value 50
. The fact that both variables have the same assigned value is irrelevant in this context.
As a result, updating num2
won't affect the value of num1
- it remains unchanged.
2. Reference types are passed by reference in memory
Assigning an object or function to a variable creates a reference to the object's memory location. Multiple variables can refer to the same object, and changes through one reference affect others. For example:
let obj1 = { name: "John" }; let obj2 = obj1; obj2.name = "James"; console.log(obj1.name); // Output: James (change reflected in the original object)
When you declare an object, it is stored in the memory heap, while its reference or pointer is stored on the stack. The pointer is associated with the object's variable name and represents the location of the object in memory, as seen below.
Hence, in the code snippet, obj1
is initially assigned an object with the property name set to "John". When obj2
is assigned the value of obj1
, both variables now refer to the same object in the memory heap. Any modifications made to the object through either obj1
or obj2
will be reflected in both variables since they point to the same underlying object.
Understanding pass-by-value and pass-by-reference helps in making informed decisions about memory usage and avoiding unexpected behavior when working with different data types in JavaScript.
Conclusion
Throughout this article, we covered primitive types like boolean, null, undefined, number, and string, as well as reference types like objects, arrays, and functions.
Primitive types are immutable and stored by value, making them straightforward. On the flip side, reference types are mutable and stored by reference, posing unique challenges.
Understanding these nuances equips you to make informed decisions in JavaScript. Knowing how values are passed and manipulated, and understanding the strengths and limitations of each type, empowers you to write more robust and reliable applications.
Practice building projects like a pro
- Portfolio-ready projects
- Professional design files
- Curate your profile
- Unlimited solution refinement