Value Types and Reference Types

advanced
Published

November 30, 2024

JavaScript, while dynamically typed, handles data differently based on whether it’s a value type or a reference type. Understanding this is important for writing efficient and predictable code, especially when dealing with object manipulation and function calls. This post will clarify the core differences and provide illustrative examples.

Value Types

Value types store their data directly within the variable. When you assign a value type variable to another, you’re creating a copy of the data. Changes to one variable don’t affect the other. In JavaScript, the primitive data types are value types:

  • Number: Represents numeric values (e.g., 10, 3.14, -5).
  • String: Represents textual data (e.g., "Hello", 'world!').
  • Boolean: Represents truthy or falsy values (true, false).
  • BigInt: Represents arbitrarily large integers.
  • Symbol: Represents unique and immutable values.
  • Null: Represents the intentional absence of a value.
  • Undefined: Represents a variable that has been declared but has not been assigned a value.
let num1 = 10;
let num2 = num1; // num2 is a copy of num1

num2 = 20;      // Changing num2 doesn't affect num1

console.log(num1); // Output: 10
console.log(num2); // Output: 20


let str1 = "hello";
let str2 = str1;

str2 = "goodbye";

console.log(str1); //Output: hello
console.log(str2); //Output: goodbye

Reference Types

Reference types, on the other hand, store a reference to the data’s location in memory (a pointer). When you assign a reference type variable to another, you’re creating a new reference pointing to the same data. Changes made through one variable will be reflected in the other. In JavaScript, objects, arrays, and functions are reference types.

let obj1 = { name: "Alice", age: 30 };
let obj2 = obj1; // obj2 now references the same object as obj1

obj2.age = 31;  // Modifying obj2 also modifies obj1

console.log(obj1.age); // Output: 31
console.log(obj2.age); // Output: 31


let arr1 = [1, 2, 3];
let arr2 = arr1;

arr2.push(4);

console.log(arr1); // Output: [1, 2, 3, 4]
console.log(arr2); // Output: [1, 2, 3, 4]

Best Practices

Understanding this difference for avoiding unexpected behavior. When working with objects or arrays, if you need to create an independent copy, use methods like Object.assign() for shallow copies or the spread syntax (...) for shallow copies, or libraries like Lodash’s cloneDeep() for deep copies.

let obj3 = {a: 1, b: {c: 2}};
let obj4 = {...obj3}; //Shallow copy - only copies the top level, nested objects are still shared by reference.
let obj5 = JSON.parse(JSON.stringify(obj3)); //Deep copy, but might have issues with functions or dates.
obj4.b.c = 3;
console.log(obj3.b.c) //Output: 3, because a shallow copy was made

By recognizing when you’re dealing with value types and reference types, you can write more predictable JavaScript code. Remember to use appropriate copying techniques when necessary to avoid unintended side effects.