Fundamental Data Types and Structures in JavaScript

JavaScript provides a variety of data types and structures to help developers manage and manipulate data efficiently. Understanding the differences between primitive types and objects, as well as how to use arrays, sets, and maps, is crucial for writing effective JavaScript code. This lesson delves into the fundamental data types, explores the built-in data structures, and teaches you how to utilize their methods to solve common programming problems.

Primitive Data Types

Overview of Primitives

JavaScript has seven primitive data types:

  1. Number: Represents numeric values.
  2. String: Represents sequences of characters.
  3. Boolean: Represents logical values true and false.
  4. Null: Represents the intentional absence of any object value.
  5. Undefined: Represents a variable that has been declared but not assigned a value.
  6. Symbol: Represents a unique and immutable identifier (introduced in ES6).
  7. BigInt: Represents whole numbers larger than 2^53 - 1 (introduced in ES2020).

Characteristics of Primitives

  • Immutable: Primitive values are immutable; they cannot be altered. Operations on primitives create new values.

    Example:

    let str = 'Hello'
    str[0] = 'h'
    console.log(str) // Outputs: 'Hello' (unchanged)
    
  • Passed by Value: When assigning or passing a primitive value, a copy is made.

    Example:

    let a = 10
    let b = a
    b = 20
    console.log(a) // Outputs: 10
    console.log(b) // Outputs: 20
    

Objects

Understanding Objects

  • Definition: An object is a collection of properties, where each property is a key-value pair.
  • Types of Objects:
    • Plain Objects ({}): Created using object literals or the Object constructor.
    • Arrays ([]): Ordered collections of values.
    • Functions: Callable objects.
    • Dates, RegExps, Errors, etc.

Primitives vs. Objects

  • Mutability:

    • Primitives are immutable.
    • Objects are mutable; their properties can be changed.

    Example:

    let obj = { name: 'Alice' }
    obj.name = 'Bob'
    console.log(obj.name) // Outputs: 'Bob'
    
  • Reference vs. Value:

    • Objects are passed by reference. Assigning an object copies the reference, not the object itself.

    Example:

    let obj1 = { value: 1 }
    let obj2 = obj1
    obj2.value = 2
    console.log(obj1.value) // Outputs: 2
    

Arrays

Creating Arrays

  • Array Literals:

    let fruits = ['apple', 'banana', 'cherry']
    
  • Array Constructor:

    let numbers = new Array(1, 2, 3)
    
  • Empty Arrays:

    let emptyArray = []
    

Array Methods

  • Adding and Removing Elements:

    • push(element): Adds an element to the end.

      fruits.push('date') // ['apple', 'banana', 'cherry', 'date']
      
    • pop(): Removes the last element.

      let lastFruit = fruits.pop() // 'date'; fruits: ['apple', 'banana', 'cherry']
      
    • unshift(element): Adds an element to the beginning.

      fruits.unshift('elderberry') // ['elderberry', 'apple', 'banana', 'cherry']
      
    • shift(): Removes the first element.

      let firstFruit = fruits.shift() // 'elderberry'; fruits: ['apple', 'banana', 'cherry']
      
  • Accessing Elements:

    let firstFruit = fruits[0] // 'apple'
    let lastFruit = fruits[fruits.length - 1] // 'cherry'
    
  • Array Length:

    let length = fruits.length // 3
    
  • Finding Elements:

    • indexOf(element): Returns the first index of the element or -1 if not found.

      let index = fruits.indexOf('banana') // 1
      
    • includes(element): Returns true if the array contains the element.

      let hasCherry = fruits.includes('cherry') // true
      
  • Transforming Arrays:

    • map(callback): Creates a new array by applying the callback to each element.

      let lengths = fruits.map((fruit) => fruit.length) // [5, 6, 6]
      
    • filter(callback): Creates a new array with elements that pass the test.

      let longFruits = fruits.filter((fruit) => fruit.length > 5) // ['banana', 'cherry']
      
    • reduce(callback, initialValue): Reduces the array to a single value.

      let totalLength = fruits.reduce((acc, fruit) => acc + fruit.length, 0) // 17
      
  • Iterating Over Arrays:

    • forEach(callback):

      fruits.forEach((fruit) => console.log(fruit))
      
  • Other Useful Methods:

    • slice(start, end): Returns a shallow copy of a portion of the array.

      let someFruits = fruits.slice(1, 3) // ['banana', 'cherry']
      
    • splice(start, deleteCount, items...): Adds/removes elements.

      fruits.splice(1, 1, 'blueberry') // Removes 'banana', adds 'blueberry'
      // fruits: ['apple', 'blueberry', 'cherry']
      
    • concat(array): Merges arrays.

      let moreFruits = fruits.concat(['fig', 'grape']) // ['apple', 'blueberry', 'cherry', 'fig', 'grape']
      
    • join(separator): Joins elements into a string.

      let fruitString = fruits.join(', ') // 'apple, blueberry, cherry'
      
    • reverse(): Reverses the array in place.

      fruits.reverse() // ['cherry', 'blueberry', 'apple']
      
    • sort(): Sorts the array.

      fruits.sort() // ['apple', 'blueberry', 'cherry']
      

Iterating Over Arrays

  • For Loop:

    for (let i = 0; i < fruits.length; i++) {
      console.log(fruits[i])
    }
    
  • For...of Loop:

    for (let fruit of fruits) {
      console.log(fruit)
    }
    
  • Array Methods (forEach, map, etc.):

    fruits.forEach((fruit) => console.log(fruit))
    

Sets

Introduction to Sets

  • Definition: A Set is a collection of unique values.
  • Characteristics:
    • No duplicate elements.
    • Order is based on insertion.
    • Elements can be of any type.

Creating Sets

  • Empty Set:

    let mySet = new Set()
    
  • Set from Array:

    let numberSet = new Set([1, 2, 3, 4, 4, 5]) // {1, 2, 3, 4, 5}
    

Set Methods

  • Adding Elements:

    • add(value):

      mySet.add(1)
      mySet.add(2)
      
  • Deleting Elements:

    • delete(value):

      mySet.delete(1)
      
  • Checking for Elements:

    • has(value):

      mySet.has(2) // true
      
  • Size of Set:

    mySet.size // 1
    
  • Clearing a Set:

    • clear():

      mySet.clear() // mySet is now empty
      

Iterating Over Sets

  • For...of Loop:

    for (let value of mySet) {
      console.log(value)
    }
    
  • forEach Method:

    mySet.forEach((value) => console.log(value))
    

Use Cases for Sets

  • Removing Duplicates from an Array:

    let numbers = [1, 2, 2, 3, 4, 4, 5]
    let uniqueNumbers = [...new Set(numbers)] // [1, 2, 3, 4, 5]
    
  • Efficient Membership Testing:

    • Faster than arrays for checking the existence of a value.

Maps

Introduction to Maps

  • Definition: A Map is a collection of key-value pairs.
  • Characteristics:
    • Keys can be of any type, including objects and functions.
    • Remember the original insertion order of the keys.

Creating Maps

  • Empty Map:

    let myMap = new Map()
    
  • Map from Array of Pairs:

    let entries = [
      ['name', 'Alice'],
      ['age', 30],
    ]
    let userMap = new Map(entries)
    

Map Methods

  • Adding Entries:

    • set(key, value):

      myMap.set('city', 'New York')
      
  • Retrieving Values:

    • get(key):

      let city = myMap.get('city') // 'New York'
      
  • Checking for Keys:

    • has(key):

      myMap.has('city') // true
      
  • Deleting Entries:

    • delete(key):

      myMap.delete('city')
      
  • Size of Map:

    myMap.size // Number of key-value pairs
    
  • Clearing a Map:

    • clear():

      myMap.clear() // myMap is now empty
      

Iterating Over Maps

  • For...of Loop:

    for (let [key, value] of myMap) {
      console.log(`${key}: ${value}`)
    }
    
  • forEach Method:

    myMap.forEach((value, key) => {
      console.log(`${key}: ${value}`)
    })
    
  • Iterators:

    • keys(): Returns an iterator of keys.
    • values(): Returns an iterator of values.
    • entries(): Returns an iterator of [key, value] pairs.
    for (let key of myMap.keys()) {
      console.log(key)
    }
    

Use Cases for Maps

  • Storing Metadata:

    • Using objects as keys to store associated data.
    let objKey = {}
    myMap.set(objKey, 'Some value')
    
  • Counting Occurrences:

    let wordCount = new Map()
    let words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
    
    words.forEach((word) => {
      let count = wordCount.get(word) || 0
      wordCount.set(word, count + 1)
    })
    
    // wordCount: Map { 'apple' => 3, 'banana' => 2, 'cherry' => 1 }
    

WeakSet and WeakMap

Understanding WeakSet

  • Definition: A WeakSet is a collection of objects.

  • Characteristics:

    • Only objects can be added (no primitives).
    • Objects are held weakly (no strong references).
    • Does not prevent garbage collection if no other references exist.
    • No size property or clear method.
    • Not iterable.
  • Use Case:

    • Tracking objects without preventing garbage collection.

    Example:

    let ws = new WeakSet()
    let obj = {}
    ws.add(obj)
    // If obj is set to null or goes out of scope, it can be garbage collected.
    

Understanding WeakMap

  • Definition: A WeakMap is a collection of key-value pairs where keys are objects.

  • Characteristics:

    • Keys must be objects.
    • Keys are held weakly.
    • Does not prevent garbage collection of key objects.
    • No size property or clear method.
    • Not iterable.
  • Use Case:

    • Associating data with objects without affecting their garbage collection.

    Example:

    let wm = new WeakMap()
    let element = document.getElementById('myElement')
    wm.set(element, { eventHandlers: [] })
    // If element is removed from the DOM and there are no other references, it can be garbage collected.
    

Best Practices

Choosing the Right Data Structure

  • Arrays:

    • Use when you need ordered collections.
    • Good for storing lists where duplicates are allowed.
    • Provides a rich set of methods for manipulation.
  • Sets:

    • Use when you need to store unique values.
    • Efficient for membership testing.
    • No duplicates allowed.
  • Maps:

    • Use when you need key-value pairs with keys of any type.
    • Better performance for frequent additions/removals compared to objects.
  • Objects:

    • Use when you need simple key-value pairs with string/symbol keys.
    • Good for modeling entities.

Avoiding Pitfalls with Primitives and Objects

  • Don't Mutate Primitives:

    • Remember that primitives are immutable.
  • Be Careful with Object References:

    • Modifying an object through one reference affects all references.

    Example:

    let a = { value: 1 }
    let b = a
    b.value = 2
    console.log(a.value) // Outputs: 2
    
  • Cloning Objects:

    • Use shallow copies to prevent unintended side effects.
    let a = { value: 1 }
    let b = { ...a } // Shallow copy
    b.value = 2
    console.log(a.value) // Outputs: 1
    

Use Appropriate Methods

  • Use Built-in Methods:

    • They are optimized and reduce code complexity.
  • Immutable Operations:

    • Prefer methods that do not mutate the original data structure when immutability is desired.

    Example:

    • Use slice instead of splice when you don't want to mutate the array.
  • Error Handling with Maps and Sets:

    • Check for existence before accessing elements.

    Example:

    if (myMap.has(key)) {
      // Safe to access myMap.get(key)
    }
    

Exercises

Exercise 1: Primitives vs. Objects

Question:

What will be the output of the following code?

let a = 5
let b = a
b = b + 5

console.log(a) // ?
console.log(b) // ?

let obj1 = { value: 10 }
let obj2 = obj1
obj2.value = obj2.value + 10

console.log(obj1.value) // ?
console.log(obj2.value) // ?

Answer:

5
10
20
20

Explanation:

  • Primitives:

    • a is a primitive; b = a copies the value.
    • Modifying b does not affect a.
  • Objects:

    • obj1 and obj2 reference the same object.
    • Modifying obj2.value affects obj1.value because they point to the same object.

Exercise 2: Array Methods

Question:

Given the array let nums = [1, 2, 3, 4, 5];, write code to:

  1. Remove the first element.
  2. Add 0 to the beginning.
  3. Replace the middle element with 10.
  4. Create a new array with the squares of the numbers.
  5. Filter out numbers less than 3.

Answer:

// 1. Remove the first element
nums.shift() // nums: [2, 3, 4, 5]

// 2. Add `0` to the beginning
nums.unshift(0) // nums: [0, 2, 3, 4, 5]

// 3. Replace the middle element with `10`
let middleIndex = Math.floor(nums.length / 2)
nums[middleIndex] = 10 // nums: [0, 2, 10, 4, 5]

// 4. Create a new array with the squares of the numbers
let squares = nums.map((n) => n * n) // [0, 4, 100, 16, 25]

// 5. Filter out numbers less than `3`
let filteredNums = nums.filter((n) => n >= 3) // [10, 4, 5]

Exercise 3: Using Sets

Question:

Given two arrays:

let array1 = [1, 2, 3, 4]
let array2 = [3, 4, 5, 6]

Write code to find the intersection (common elements) of the two arrays using sets.

Answer:

let set1 = new Set(array1)
let set2 = new Set(array2)

let intersection = [...set1].filter((item) => set2.has(item)) // [3, 4]

Explanation:

  • Convert arrays to sets to utilize efficient membership testing.
  • Use filter to collect elements that are present in both sets.

Exercise 4: Working with Maps

Question:

Create a Map to store the number of occurrences of each character in the string 'hello world'.

Answer:

let str = 'hello world'
let charCount = new Map()

for (let char of str) {
  if (char !== ' ') {
    // Exclude spaces
    let count = charCount.get(char) || 0
    charCount.set(char, count + 1)
  }
}

// charCount: Map { 'h' => 1, 'e' => 1, 'l' => 3, 'o' => 2, 'w' => 1, 'r' => 1, 'd' => 1 }

Exercise 5: Understanding WeakMaps

Question:

Explain why a WeakMap is suitable for associating metadata with DOM elements in a web application.

Answer:

  • Reason:

    • WeakMap keys are held weakly, meaning that if the DOM element (key) has no other references and is removed from the DOM, it can be garbage collected.
    • This prevents memory leaks as the metadata associated with the element in the WeakMap will also be garbage collected.
    • A regular Map would prevent the element from being garbage collected because it holds a strong reference to the key.

Understanding fundamental data types and structures in JavaScript is essential for writing efficient and effective code. By mastering primitives, objects, arrays, sets, and maps, you can manipulate data more effectively, optimize performance, and solve complex programming challenges. This knowledge also prepares you for technical interviews and real-world development tasks.

Practice Problems

What are the differences between primitive data types and objects in JavaScript?

Loading...

How Map Filter WorksDifficulty: Easy

Explain how the `map` and `filter` methods work on arrays.

Loading...

Removing Array DuplicatesDifficulty: Medium

How do you remove duplicates from an array in JavaScript?

Loading...

JavaScript Object vs MapDifficulty: Medium

What are the key differences between an object and a Map in JavaScript?

Loading...

Choosing Between MapsDifficulty: Hard

When would you use a WeakMap over a Map?

Loading...

Let's continue exploring the next page. Take your time, and proceed when you're ready.

Lesson completed?

Found a bug, typo, or have feedback?

Let me know