JavaScript Reference Data Types: Objects, Arrays, and Functions
If basic data types are individual LEGO bricks, then reference data types are complex models built from these bricks. They can contain multiple values, can be nested and combined, and can express more complex data structures and business logic.
Reference Types vs Basic Types
Before delving deep into reference types, we need to understand their fundamental differences from basic types.
Storage Method Differences
// Basic types: store values directly
let num1 = 42;
let num2 = num1; // Copy value
num2 = 100; // Modifying num2 doesn't affect num1
console.log(num1); // 42
console.log(num2); // 100
// Reference types: store references (addresses)
let obj1 = { name: "Alice" };
let obj2 = obj1; // Copy reference, not copy object
obj2.name = "Bob"; // Modify through obj2, obj1 also changes!
console.log(obj1.name); // "Bob"
console.log(obj2.name); // "Bob"This is like buying a house: basic types are directly owning a copy of the house, while reference types hold the address of the house. Changing the house pointed to by the address, everyone holding this address will see the changes.
Comparison Method Differences
// Basic types: compare values
console.log(5 === 5); // true
console.log("hello" === "hello"); // true
// Reference types: compare references (addresses)
console.log({} === {}); // false (different objects, different addresses)
console.log([] === []); // false (different arrays, different addresses)
let arr1 = [1, 2, 3];
let arr2 = arr1; // Same reference
console.log(arr1 === arr2); // true (point to same array)
// Even with identical content, if they're different objects, they're not equal
let person1 = { name: "Charlie", age: 30 };
let person2 = { name: "Charlie", age: 30 };
console.log(person1 === person2); // false (different objects)Object Type
Objects are the most important data type in JavaScript, like a storage cabinet full of labeled boxes, where each box (property) has a label (key) and content (value).
Creating Objects
// Method 1: Object literal (most common)
let user = {
name: "David",
age: 28,
email: "[email protected]",
};
// Method 2: new Object()
let person = new Object();
person.name = "Emma";
person.age = 32;
// Method 3: Object.create()
let student = Object.create(null);
student.grade = "A";
// Method 4: Constructor function
function Person(name, age) {
this.name = name;
this.age = age;
}
let john = new Person("John", 25);Accessing and Modifying Properties
let user = {
name: "Sarah",
age: 27,
email: "[email protected]",
};
// Dot notation access
console.log(user.name); // "Sarah"
console.log(user.age); // 27
// Bracket notation access
console.log(user["email"]); // "[email protected]"
// Modify properties
user.age = 28;
user["email"] = "[email protected]";
// Add new properties
user.city = "London";
user["country"] = "UK";
console.log(user);
// {
// name: "Sarah",
// age: 28,
// email: "[email protected]",
// city: "London",
// country: "UK"
// }
// Delete properties
delete user.country;
console.log(user.country); // undefinedApplications of Bracket Notation
Bracket notation is more flexible and can use variables and special characters:
// Use variables as property names
let propertyName = "age";
console.log(user[propertyName]); // 28
// Property names containing spaces or special characters
let config = {
"api-key": "abc123",
"user name": "Tom",
"is-active": true,
};
console.log(config["api-key"]); // "abc123"
console.log(config["user name"]); // "Tom"
// config.api-key // This would cause an error!
// Dynamic property names
let key = "score";
let value = 95;
let exam = {
[key]: value, // ES6 computed property name
};
console.log(exam.score); // 95Object Methods
Objects can contain functions as properties, these functions are called methods:
let calculator = {
value: 0,
add: function (num) {
this.value += num;
return this;
},
subtract: function (num) {
this.value -= num;
return this;
},
// ES6 simplified syntax
multiply(num) {
this.value *= num;
return this;
},
getValue() {
return this.value;
},
};
calculator.add(10).multiply(2).subtract(5);
console.log(calculator.getValue()); // 15Checking if Properties Exist
let user = {
name: "Oliver",
age: 35,
};
// Method 1: in operator
console.log("name" in user); // true
console.log("email" in user); // false
// Method 2: hasOwnProperty method
console.log(user.hasOwnProperty("age")); // true
console.log(user.hasOwnProperty("email")); // false
// Method 3: Direct access and check (note: returns false when value is undefined)
console.log(user.name !== undefined); // true
console.log(user.email !== undefined); // falseTraversing Objects
let product = {
name: "Laptop",
price: 1200,
brand: "TechCorp",
inStock: true,
};
// for...in loop
for (let key in product) {
console.log(key + ": " + product[key]);
}
// name: Laptop
// price: 1200
// brand: TechCorp
// inStock: true
// Object.keys()
let keys = Object.keys(product);
console.log(keys); // ["name", "price", "brand", "inStock"]
// Object.values()
let values = Object.values(product);
console.log(values); // ["Laptop", 1200, "TechCorp", true]
// Object.entries()
let entries = Object.entries(product);
console.log(entries);
// [
// ["name", "Laptop"],
// ["price", 1200],
// ["brand", "TechCorp"],
// ["inStock", true]
// ]
entries.forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});Array Type
Arrays are ordered collections of data, like a row of numbered lockers, each position (index) can store one value.
Creating Arrays
// Method 1: Array literal (most common)
let fruits = ["apple", "banana", "orange"];
let numbers = [1, 2, 3, 4, 5];
let mixed = [1, "hello", true, null, { name: "Test" }];
// Method 2: Array constructor
let arr1 = new Array(); // Empty array
let arr2 = new Array(5); // Array with length 5 (empty)
let arr3 = new Array(1, 2, 3); // [1, 2, 3]
// Method 3: Array.of() (ES6)
let arr4 = Array.of(5); // [5] (avoids constructor ambiguity)
let arr5 = Array.of(1, 2, 3); // [1, 2, 3]
// Method 4: Array.from() (ES6)
let str = "hello";
let chars = Array.from(str); // ["h", "e", "l", "l", "o"]Accessing and Modifying Array Elements
let colors = ["red", "green", "blue"];
// Access elements (index starts from 0)
console.log(colors[0]); // "red"
console.log(colors[1]); // "green"
console.log(colors[2]); // "blue"
// Modify elements
colors[1] = "yellow";
console.log(colors); // ["red", "yellow", "blue"]
// Add elements
colors[3] = "purple";
console.log(colors); // ["red", "yellow", "blue", "purple"]
// Array length
console.log(colors.length); // 4
// Access the last element
console.log(colors[colors.length - 1]); // "purple"Common Array Methods
let numbers = [1, 2, 3, 4, 5];
// Add/delete elements
numbers.push(6); // Add to end: [1, 2, 3, 4, 5, 6]
numbers.pop(); // Delete from end: [1, 2, 3, 4, 5]
numbers.unshift(0); // Add to beginning: [0, 1, 2, 3, 4, 5]
numbers.shift(); // Delete from beginning: [1, 2, 3, 4, 5]
// splice: delete/insert/replace
let arr = [1, 2, 3, 4, 5];
arr.splice(2, 1); // Delete 1 from index 2: [1, 2, 4, 5]
arr.splice(2, 0, 3); // Insert 3 at index 2: [1, 2, 3, 4, 5]
arr.splice(2, 1, 99); // Replace 1 at index 2 with 99: [1, 2, 99, 4, 5]
// slice: Extract subarray (doesn't modify original array)
let original = [1, 2, 3, 4, 5];
let sub = original.slice(1, 4); // [2, 3, 4]
console.log(original); // [1, 2, 3, 4, 5] (original array unchanged)
// concat: Merge arrays
let arr1 = [1, 2];
let arr2 = [3, 4];
let combined = arr1.concat(arr2); // [1, 2, 3, 4]
// join: Convert to string
let fruits = ["apple", "banana", "orange"];
console.log(fruits.join()); // "apple,banana,orange"
console.log(fruits.join(" - ")); // "apple - banana - orange"
// reverse: Reverse array (modifies original array)
let nums = [1, 2, 3];
nums.reverse();
console.log(nums); // [3, 2, 1]
// sort: Sort array (modifies original array)
let values = [3, 1, 4, 1, 5, 9];
values.sort();
console.log(values); // [1, 1, 3, 4, 5, 9]
// Custom sorting
values.sort((a, b) => b - a); // Descending order
console.log(values); // [9, 5, 4, 3, 1, 1]Array Traversal
let fruits = ["apple", "banana", "orange"];
// for loop
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// for...of loop (ES6, recommended)
for (let fruit of fruits) {
console.log(fruit);
}
// forEach method
fruits.forEach(function (fruit, index) {
console.log(index + ": " + fruit);
});
// Simplified syntax
fruits.forEach((fruit, index) => {
console.log(`${index}: ${fruit}`);
});Advanced Array Methods
let numbers = [1, 2, 3, 4, 5];
// map: Map transformation
let doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter: Filter
let evens = numbers.filter((num) => num % 2 === 0);
console.log(evens); // [2, 4]
// reduce: Reduce
let sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
// find: Find first element that meets condition
let found = numbers.find((num) => num > 3);
console.log(found); // 4
// some: Check if any element meets condition
let hasEven = numbers.some((num) => num % 2 === 0);
console.log(hasEven); // true
// every: Check if all elements meet condition
let allPositive = numbers.every((num) => num > 0);
console.log(allPositive); // trueFunction Type
In JavaScript, functions are first-class objects, can be passed, assigned, and returned as values like variables.
Function Creation Methods
// Method 1: Function declaration
function greet(name) {
return "Hello, " + name + "!";
}
// Method 2: Function expression
let sayHi = function (name) {
return "Hi, " + name + "!";
};
// Method 3: Arrow function (ES6)
let welcome = (name) => {
return "Welcome, " + name + "!";
};
// Arrow function simplified syntax
let hey = (name) => "Hey, " + name + "!";
// Method 4: Function constructor (not recommended)
let multiply = new Function("a", "b", "return a * b");Functions as Values
// Function assigned to variable
let calculate = function (a, b) {
return a + b;
};
console.log(calculate(5, 3)); // 8
// Function as parameter (callback function)
function executeOperation(a, b, operation) {
return operation(a, b);
}
let add = (x, y) => x + y;
let multiply = (x, y) => x * y;
console.log(executeOperation(5, 3, add)); // 8
console.log(executeOperation(5, 3, multiply)); // 15
// Function as return value (higher-order function)
function createMultiplier(factor) {
return function (number) {
return number * factor;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15Other Reference Types
Date Object
// Create date object
let now = new Date();
let specificDate = new Date("2024-01-15");
let timestamp = new Date(1609459200000);
console.log(now); // Current date and time
// Date methods
console.log(now.getFullYear()); // Year
console.log(now.getMonth()); // Month (0-11)
console.log(now.getDate()); // Date (1-31)
console.log(now.getDay()); // Day of week (0-6, 0 is Sunday)
console.log(now.getHours()); // Hour
console.log(now.getMinutes()); // Minute
console.log(now.getTime()); // Timestamp (milliseconds)RegExp Regular Expressions
// Create regular expression
let pattern1 = /hello/i; // Literal method
let pattern2 = new RegExp("world", "i"); // Constructor method
// Test match
let text = "Hello World";
console.log(/hello/i.test(text)); // true
// Find match
let email = "[email protected]";
let match = email.match(/@(.+)$/);
console.log(match[1]); // "example.com"Value Passing vs Reference Passing
This is a key concept for understanding JavaScript:
// Basic types: value passing
function changeNum(x) {
x = 100;
}
let num = 42;
changeNum(num);
console.log(num); // 42 (not affected)
// Reference types: reference passing
function changePerson(obj) {
obj.name = "Changed";
}
let person = { name: "Original" };
changePerson(person);
console.log(person.name); // "Changed" (was modified!)
// But reassignment doesn't affect external
function reassignPerson(obj) {
obj = { name: "New Object" };
}
reassignPerson(person);
console.log(person.name); // "Changed" (didn't become "New Object")Deep Copy vs Shallow Copy
Shallow Copy
// Method 1: Spread operator
let original = { name: "Alice", age: 25 };
let copy1 = { ...original };
copy1.age = 30;
console.log(original.age); // 25 (not affected)
// Method 2: Object.assign()
let copy2 = Object.assign({}, original);
// Problem with shallow copy: nested objects are still references
let user = {
name: "Bob",
address: {
city: "New York",
},
};
let userCopy = { ...user };
userCopy.address.city = "London";
console.log(user.address.city); // "London" (was affected!)Deep Copy
// Method 1: JSON method (simple but limited)
let original = {
name: "Charlie",
scores: [85, 90, 95],
};
let deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.scores[0] = 100;
console.log(original.scores[0]); // 85 (not affected)
// Method 2: Recursive implementation
function deepClone(obj) {
if (obj === null || typeof obj !== "object") {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(deepClone);
}
let clone = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}Summary
Reference types are the core of JavaScript programming. Understanding how they work is crucial for writing high-quality code.
Key Points Review:
- Reference types store references (addresses), not values themselves
- Object: Key-value pair collections, the most fundamental reference type in JavaScript
- Array: Ordered collections of values, providing rich manipulation methods
- Function: Executable code blocks, also objects
- Reference type comparison compares references, not values
- When passing reference types as function parameters, a copy of the reference is passed
- Understand the differences and application scenarios between shallow copy and deep copy
- Basic types and reference types behave completely differently in assignment, comparison, and passing