JavaScript Intermediate (Part 1)

Learn advanced JavaScript concepts including objects, arrays, DOM manipulation, and events.

JavaScript Intermediate Content

This content has been divided into two parts for better readability:

JavaScript Objects

Objects are collections of key-value pairs and are fundamental to JavaScript. They allow you to store related data and functionality together.

Creating Objects

There are several ways to create objects in JavaScript:

Object Literals

// Object literal syntax
const person = {
    firstName: "John",
    lastName: "Doe",
    age: 30,
    email: "[email protected]",
    isEmployed: true,
    hobbies: ["reading", "swimming", "coding"],
    address: {
        street: "123 Main St",
        city: "New York",
        country: "USA"
    }
};

Constructor Functions

// Constructor function
function Person(firstName, lastName, age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.getFullName = function() {
        return this.firstName + " " + this.lastName;
    };
}

const john = new Person("John", "Doe", 30);
console.log(john.getFullName()); // "John Doe"

ES6 Classes

// ES6 Class syntax
class Person {
    constructor(firstName, lastName, age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    
    getFullName() {
        return this.firstName + " " + this.lastName;
    }
    
    getAge() {
        return this.age;
    }
}

const jane = new Person("Jane", "Smith", 25);
console.log(jane.getFullName()); // "Jane Smith"

Object.create()

// Object.create() method
const personProto = {
    getFullName: function() {
        return this.firstName + " " + this.lastName;
    }
};

const john = Object.create(personProto);
john.firstName = "John";
john.lastName = "Doe";
john.age = 30;

console.log(john.getFullName()); // "John Doe"

Accessing Object Properties

There are two ways to access object properties:

// Dot notation
console.log(person.firstName); // "John"
console.log(person.address.city); // "New York"

// Bracket notation
console.log(person["lastName"]); // "Doe"

// Bracket notation is useful when property name is dynamic
const propertyName = "age";
console.log(person[propertyName]); // 30

Object Methods

Methods are functions that are stored as object properties.

const person = {
    firstName: "John",
    lastName: "Doe",
    getFullName: function() {
        return this.firstName + " " + this.lastName;
    },
    // Shorthand method syntax (ES6)
    greet() {
        return `Hello, my name is ${this.firstName}`;
    }
};

console.log(person.getFullName()); // "John Doe"
console.log(person.greet()); // "Hello, my name is John"

Object Destructuring

Object destructuring allows you to extract properties from objects and bind them to variables.

const person = {
    firstName: "John",
    lastName: "Doe",
    age: 30,
    address: {
        street: "123 Main St",
        city: "New York",
        country: "USA"
    }
};

// Basic destructuring
const { firstName, lastName, age } = person;
console.log(firstName); // "John"
console.log(lastName); // "Doe"
console.log(age); // 30

// Nested destructuring
const { address: { city, country } } = person;
console.log(city); // "New York"
console.log(country); // "USA"

// Assigning to new variable names
const { firstName: fName, lastName: lName } = person;
console.log(fName); // "John"
console.log(lName); // "Doe"

// Default values
const { zipCode = "10001" } = person.address;
console.log(zipCode); // "10001" (default value since it doesn't exist)

Object Methods and Properties

JavaScript provides several built-in methods for working with objects:

Method/Property Description Example
Object.keys() Returns an array of a given object's own enumerable property names Object.keys(person)
Object.values() Returns an array of a given object's own enumerable property values Object.values(person)
Object.entries() Returns an array of a given object's own enumerable property [key, value] pairs Object.entries(person)
Object.assign() Copies all enumerable own properties from one or more source objects to a target object Object.assign({}, person, { age: 31 })
Object.freeze() Freezes an object, preventing new properties from being added and existing properties from being removed or modified Object.freeze(person)
Object.seal() Seals an object, preventing new properties from being added and marking all existing properties as non-configurable Object.seal(person)
hasOwnProperty() Returns a boolean indicating whether the object has the specified property as its own property person.hasOwnProperty('firstName')
const person = {
    firstName: "John",
    lastName: "Doe",
    age: 30
};

// Object.keys()
console.log(Object.keys(person)); // ["firstName", "lastName", "age"]

// Object.values()
console.log(Object.values(person)); // ["John", "Doe", 30]

// Object.entries()
console.log(Object.entries(person)); 
// [["firstName", "John"], ["lastName", "Doe"], ["age", 30]]

// Object.assign()
const updatedPerson = Object.assign({}, person, { age: 31, email: "[email protected]" });
console.log(updatedPerson); 
// { firstName: "John", lastName: "Doe", age: 31, email: "[email protected]" }

// Spread operator (alternative to Object.assign)
const updatedPerson2 = { ...person, age: 32, email: "[email protected]" };
console.log(updatedPerson2);
// { firstName: "John", lastName: "Doe", age: 32, email: "[email protected]" }

Prototypes and Inheritance

JavaScript uses a prototype-based inheritance model. Every object has a prototype, which is another object that it inherits properties and methods from.

// Constructor function
function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

// Adding a method to the prototype
Person.prototype.getFullName = function() {
    return this.firstName + " " + this.lastName;
};

const john = new Person("John", "Doe");
console.log(john.getFullName()); // "John Doe"

// Inheritance with constructor functions
function Employee(firstName, lastName, position) {
    // Call the parent constructor
    Person.call(this, firstName, lastName);
    this.position = position;
}

// Inherit the Person prototype
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

// Add a method to Employee prototype
Employee.prototype.getDetails = function() {
    return `${this.getFullName()}, ${this.position}`;
};

const jane = new Employee("Jane", "Smith", "Developer");
console.log(jane.getFullName()); // "Jane Smith"
console.log(jane.getDetails()); // "Jane Smith, Developer"

ES6 Class Inheritance

ES6 classes provide a cleaner syntax for implementing inheritance.

// Parent class
class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    getFullName() {
        return this.firstName + " " + this.lastName;
    }
}

// Child class
class Employee extends Person {
    constructor(firstName, lastName, position) {
        // Call parent constructor
        super(firstName, lastName);
        this.position = position;
    }
    
    getDetails() {
        return `${this.getFullName()}, ${this.position}`;
    }
}

const john = new Employee("John", "Doe", "Manager");
console.log(john.getFullName()); // "John Doe"
console.log(john.getDetails()); // "John Doe, Manager"

JavaScript Arrays

Arrays are ordered collections of values. They are used to store multiple values in a single variable.

Creating Arrays

// Array literal syntax
const fruits = ["Apple", "Banana", "Orange", "Mango"];

// Array constructor
const numbers = new Array(1, 2, 3, 4, 5);

// Empty array
const emptyArray = [];

// Array with mixed data types
const mixedArray = [1, "Hello", true, null, { name: "John" }, [1, 2, 3]];

Accessing Array Elements

const fruits = ["Apple", "Banana", "Orange", "Mango"];

// Accessing by index (zero-based)
console.log(fruits[0]); // "Apple"
console.log(fruits[2]); // "Orange"

// Length property
console.log(fruits.length); // 4

// Last element
console.log(fruits[fruits.length - 1]); // "Mango"

Modifying Arrays

const fruits = ["Apple", "Banana", "Orange"];

// Changing an element
fruits[1] = "Pear";
console.log(fruits); // ["Apple", "Pear", "Orange"]

// Adding elements
fruits.push("Mango"); // Add to end
console.log(fruits); // ["Apple", "Pear", "Orange", "Mango"]

fruits.unshift("Strawberry"); // Add to beginning
console.log(fruits); // ["Strawberry", "Apple", "Pear", "Orange", "Mango"]

// Removing elements
fruits.pop(); // Remove from end
console.log(fruits); // ["Strawberry", "Apple", "Pear", "Orange"]

fruits.shift(); // Remove from beginning
console.log(fruits); // ["Apple", "Pear", "Orange"]

// Splice - add/remove elements at any position
// splice(start, deleteCount, item1, item2, ...)
fruits.splice(1, 1, "Kiwi", "Banana");
console.log(fruits); // ["Apple", "Kiwi", "Banana", "Orange"]

Array Methods

JavaScript provides many powerful methods for working with arrays:

Method Description Example
concat() Merges two or more arrays and returns a new array array1.concat(array2)
join() Joins all elements of an array into a string array.join(", ")
slice() Returns a shallow copy of a portion of an array array.slice(1, 3)
indexOf() Returns the first index at which a given element can be found array.indexOf("Apple")
lastIndexOf() Returns the last index at which a given element can be found array.lastIndexOf("Apple")
includes() Determines whether an array includes a certain value array.includes("Apple")
reverse() Reverses the order of the elements in an array array.reverse()
sort() Sorts the elements of an array array.sort()
const fruits = ["Apple", "Banana", "Orange", "Mango"];
const vegetables = ["Carrot", "Broccoli"];

// concat
const food = fruits.concat(vegetables);
console.log(food); 
// ["Apple", "Banana", "Orange", "Mango", "Carrot", "Broccoli"]

// join
const fruitString = fruits.join(", ");
console.log(fruitString); // "Apple, Banana, Orange, Mango"

// slice
const citrus = fruits.slice(1, 3);
console.log(citrus); // ["Banana", "Orange"]

// indexOf
console.log(fruits.indexOf("Orange")); // 2
console.log(fruits.indexOf("Pear")); // -1 (not found)

// includes
console.log(fruits.includes("Mango")); // true
console.log(fruits.includes("Pear")); // false

// reverse (modifies the original array)
fruits.reverse();
console.log(fruits); // ["Mango", "Orange", "Banana", "Apple"]

// sort (modifies the original array)
fruits.sort();
console.log(fruits); // ["Apple", "Banana", "Mango", "Orange"]

Higher-Order Array Methods

JavaScript provides several higher-order methods that take functions as arguments, allowing for powerful array transformations:

Method Description Example
forEach() Executes a provided function once for each array element array.forEach(callback)
map() Creates a new array with the results of calling a function for every array element array.map(callback)
filter() Creates a new array with all elements that pass the test implemented by the provided function array.filter(callback)
reduce() Executes a reducer function on each element of the array, resulting in a single output value array.reduce(callback, initialValue)
find() Returns the first element in the array that satisfies the provided testing function array.find(callback)
findIndex() Returns the index of the first element in the array that satisfies the provided testing function array.findIndex(callback)
some() Tests whether at least one element in the array passes the test implemented by the provided function array.some(callback)
every() Tests whether all elements in the array pass the test implemented by the provided function array.every(callback)
const numbers = [1, 2, 3, 4, 5];
const people = [
    { name: "John", age: 25 },
    { name: "Jane", age: 30 },
    { name: "Bob", age: 20 },
    { name: "Alice", age: 35 }
];

// forEach
numbers.forEach(number => {
    console.log(number * 2);
}); // Logs: 2, 4, 6, 8, 10

// map
const doubled = numbers.map(number => number * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

const names = people.map(person => person.name);
console.log(names); // ["John", "Jane", "Bob", "Alice"]

// filter
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(evenNumbers); // [2, 4]

const adults = people.filter(person => person.age >= 30);
console.log(adults); // [{ name: "Jane", age: 30 }, { name: "Alice", age: 35 }]

// reduce
const sum = numbers.reduce((total, number) => total + number, 0);
console.log(sum); // 15

const totalAge = people.reduce((total, person) => total + person.age, 0);
console.log(totalAge); // 110

// find
const firstEven = numbers.find(number => number % 2 === 0);
console.log(firstEven); // 2

const jane = people.find(person => person.name === "Jane");
console.log(jane); // { name: "Jane", age: 30 }

// some
const hasEven = numbers.some(number => number % 2 === 0);
console.log(hasEven); // true

// every
const allPositive = numbers.every(number => number > 0);
console.log(allPositive); // true

// Chaining methods
const result = numbers
    .filter(number => number % 2 === 0)
    .map(number => number * 3)
    .reduce((total, number) => total + number, 0);
console.log(result); // 18 (2*3 + 4*3)

Array Destructuring

Array destructuring allows you to extract values from arrays and assign them to variables.

const numbers = [1, 2, 3, 4, 5];

// Basic destructuring
const [first, second, third] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(third); // 3

// Skip elements
const [a, , c] = numbers;
console.log(a); // 1
console.log(c); // 3

// Rest pattern
const [head, ...tail] = numbers;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

// Default values
const [x, y, z = 10] = [1, 2];
console.log(x); // 1
console.log(y); // 2
console.log(z); // 10 (default value)

// Swapping variables
let m = 1;
let n = 2;
[m, n] = [n, m];
console.log(m); // 2
console.log(n); // 1

Spread Operator with Arrays

The spread operator (...) allows an array to be expanded in places where zero or more arguments or elements are expected.

const numbers1 = [1, 2, 3];
const numbers2 = [4, 5, 6];

// Combine arrays
const combined = [...numbers1, ...numbers2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// Copy an array
const copy = [...numbers1];
console.log(copy); // [1, 2, 3]

// Insert elements in the middle
const inserted = [...numbers1.slice(0, 2), 10, ...numbers1.slice(2)];
console.log(inserted); // [1, 2, 10, 3]

// Use with functions
function sum(a, b, c) {
    return a + b + c;
}

console.log(sum(...numbers1)); // 6 (1 + 2 + 3)

DOM Manipulation

The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page so that programs can change the document structure, style, and content.

Selecting DOM Elements

JavaScript provides several methods to select elements from the DOM:

Method Description Example
getElementById() Returns the element with the specified ID document.getElementById("myId")
getElementsByClassName() Returns a live HTMLCollection of elements with the specified class name document.getElementsByClassName("myClass")
getElementsByTagName() Returns a live HTMLCollection of elements with the specified tag name document.getElementsByTagName("div")
querySelector() Returns the first element that matches a specified CSS selector document.querySelector("#myId")
querySelectorAll() Returns a static NodeList of all elements that match a specified CSS selector document.querySelectorAll(".myClass")
// Select by ID
const header = document.getElementById("header");

// Select by class name
const items = document.getElementsByClassName("item");

// Select by tag name
const paragraphs = document.getElementsByTagName("p");

// Select using CSS selectors
const firstButton = document.querySelector("button");
const allButtons = document.querySelectorAll("button");

// Combining selectors
const activeItems = document.querySelectorAll(".item.active");
const navLinks = document.querySelectorAll("nav a");

// Selecting children
const firstChild = document.querySelector("ul > li:first-child");
const lastChild = document.querySelector("ul > li:last-child");

Modifying DOM Elements

Once you've selected elements, you can modify their content, attributes, and styles:

Changing Content

const element = document.getElementById("myElement");

// Change text content
element.textContent = "New text content";

// Change HTML content
element.innerHTML = "Bold text and italic text";

// Create text node
const textNode = document.createTextNode("Text node content");
element.appendChild(textNode);

Modifying Attributes

const link = document.getElementById("myLink");

// Get attribute
const href = link.getAttribute("href");
console.log(href);

// Set attribute
link.setAttribute("href", "https://example.com");
link.setAttribute("target", "_blank");

// Check if attribute exists
const hasTarget = link.hasAttribute("target");
console.log(hasTarget); // true

// Remove attribute
link.removeAttribute("target");

// Using properties (for standard attributes)
link.href = "https://example.org";
link.id = "newId";
link.className = "link active";

Modifying Styles

const element = document.getElementById("myElement");

// Using the style property
element.style.color = "red";
element.style.backgroundColor = "#f0f0f0";
element.style.padding = "10px";
element.style.borderRadius = "5px";

// Using CSS classes
element.className = "active highlight";

// Using classList (modern approach)
element.classList.add("active");
element.classList.remove("highlight");
element.classList.toggle("selected");
element.classList.replace("old-class", "new-class");
const hasClass = element.classList.contains("active");
console.log(hasClass); // true

Creating and Removing Elements

JavaScript allows you to dynamically create, insert, and remove elements from the DOM:

Creating Elements

// Create a new element
const newDiv = document.createElement("div");

// Add content
newDiv.textContent = "This is a new div";

// Add attributes
newDiv.id = "newDiv";
newDiv.className = "container";

// Add styles
newDiv.style.color = "blue";
newDiv.style.backgroundColor = "#f0f0f0";

// Create a complex element
const newList = document.createElement("ul");
for (let i = 1; i <= 3; i++) {
    const listItem = document.createElement("li");
    listItem.textContent = `Item ${i}`;
    newList.appendChild(listItem);
}

Adding Elements to the DOM

const parentElement = document.getElementById("parent");
const referenceElement = document.getElementById("reference");

// Append at the end of parent
parentElement.appendChild(newDiv);

// Insert before a reference element
parentElement.insertBefore(newList, referenceElement);

// Modern insertion methods
parentElement.append(newDiv); // Can append multiple nodes and text
parentElement.prepend(newDiv); // Insert at the beginning
referenceElement.before(newDiv); // Insert before reference
referenceElement.after(newDiv); // Insert after reference
referenceElement.replaceWith(newDiv); // Replace reference with new element

Removing Elements

const elementToRemove = document.getElementById("removeMe");

// Remove element (old way)
elementToRemove.parentNode.removeChild(elementToRemove);

// Modern way
elementToRemove.remove();

// Remove all children
const parent = document.getElementById("parent");
while (parent.firstChild) {
    parent.removeChild(parent.firstChild);
}

// Alternative way to remove all children
parent.innerHTML = "";

Traversing the DOM

You can navigate through the DOM tree using various properties:

const element = document.getElementById("myElement");

// Parent
const parent = element.parentNode; // or element.parentElement

// Children
const children = element.children; // HTMLCollection of child elements
const firstChild = element.firstElementChild;
const lastChild = element.lastElementChild;
const childCount = element.childElementCount;

// Siblings
const nextSibling = element.nextElementSibling;
const previousSibling = element.previousElementSibling;

// All nodes (including text nodes)
const childNodes = element.childNodes;
const firstNode = element.firstChild;
const lastNode = element.lastChild;
const nextNode = element.nextSibling;
const previousNode = element.previousSibling;

JavaScript Events

Events are actions or occurrences that happen in the browser, which can be detected and responded to with JavaScript. Examples include clicks, key presses, form submissions, and page loads.

Event Handlers

There are several ways to attach event handlers to elements:

HTML Attribute (Inline)

<button onclick="alert('Button clicked!')">Click Me</button>

DOM Property

const button = document.getElementById("myButton");
button.onclick = function() {
    alert("Button clicked!");
};

// Using arrow function
button.onclick = () => {
    alert("Button clicked!");
};

addEventListener Method (Recommended)

const button = document.getElementById("myButton");

// Add event listener
button.addEventListener("click", function() {
    alert("Button clicked!");
});

// Using arrow function
button.addEventListener("click", () => {
    alert("Button clicked!");
});

// Using named function
function handleClick() {
    alert("Button clicked!");
}
button.addEventListener("click", handleClick);

// Remove event listener
button.removeEventListener("click", handleClick);

Common Events

JavaScript supports many types of events:

Category Events
Mouse Events click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove
Keyboard Events keydown, keyup, keypress
Form Events submit, reset, change, input, focus, blur
Window Events load, resize, scroll, unload, beforeunload
Document Events DOMContentLoaded
Drag Events dragstart, drag, dragend, dragenter, dragover, dragleave, drop

The Event Object

When an event occurs, the browser creates an event object that contains information about the event. This object is automatically passed to the event handler function.

const button = document.getElementById("myButton");

button.addEventListener("click", function(event) {
    // The event object
    console.log(event);
    
    // Common properties
    console.log(event.type); // "click"
    console.log(event.target); // The element that triggered the event
    console.log(event.currentTarget); // The element that the event listener is attached to
    console.log(event.timeStamp); // Time when the event occurred
    
    // Mouse event properties
    console.log(event.clientX, event.clientY); // Mouse coordinates relative to the viewport
    console.log(event.pageX, event.pageY); // Mouse coordinates relative to the document
    console.log(event.button); // Which mouse button was pressed
    
    // Keyboard event properties
    // console.log(event.key); // The key value
    // console.log(event.code); // The physical key code
    // console.log(event.altKey, event.ctrlKey, event.shiftKey); // Modifier keys
    
    // Prevent default behavior
    event.preventDefault();
    
    // Stop propagation
    event.stopPropagation();
});

Event Propagation

When an event occurs on an element, it first runs the handlers on it, then on its parent, then all the way up on other ancestors. This is called event bubbling.

<div id="outer">
    <div id="inner">
        <button id="button">Click Me</button>
    </div>
</div>

<script>
    document.getElementById("outer").addEventListener("click", function() {
        console.log("Outer div clicked");
    });
    
    document.getElementById("inner").addEventListener("click", function() {
        console.log("Inner div clicked");
    });
    
    document.getElementById("button").addEventListener("click", function() {
        console.log("Button clicked");
    });
    
    // When the button is clicked, the output will be:
    // "Button clicked"
    // "Inner div clicked"
    // "Outer div clicked"
</script>

Stopping Propagation

document.getElementById("button").addEventListener("click", function(event) {
    console.log("Button clicked");
    event.stopPropagation(); // Prevents the event from bubbling up
});

Event Delegation

Event delegation is a technique where you attach a single event listener to a parent element instead of multiple listeners on child elements. It takes advantage of event bubbling.

<ul id="menu">
    <li data-action="save">Save</li>
    <li data-action="load">Load</li>
    <li data-action="delete">Delete</li>
</ul>

<script>
    // Instead of adding event listeners to each li
    document.getElementById("menu").addEventListener("click", function(event) {
        // Check if the clicked element is an li
        if (event.target.tagName === "LI") {
            const action = event.target.getAttribute("data-action");
            console.log("Action:", action);
            
            // Perform action based on the data attribute
            switch (action) {
                case "save":
                    saveData();
                    break;
                case "load":
                    loadData();
                    break;
                case "delete":
                    deleteData();
                    break;
            }
        }
    });
    
    function saveData() { console.log("Saving data..."); }
    function loadData() { console.log("Loading data..."); }
    function deleteData() { console.log("Deleting data..."); }
</script>

Custom Events

You can create and dispatch your own custom events using the CustomEvent constructor.

// Create a custom event
const customEvent = new CustomEvent("userLogin", {
    detail: {
        username: "john_doe",
        timestamp: new Date()
    },
    bubbles: true,
    cancelable: true
});

// Listen for the custom event
document.addEventListener("userLogin", function(event) {
    console.log("User logged in:", event.detail.username);
    console.log("Time:", event.detail.timestamp);
});

// Dispatch the event
document.dispatchEvent(customEvent);

Continue to Part 2

Continue to Part 2 to learn about Asynchronous JavaScript, Fetch API, ES6+ Features, and Modules.