The JavaScript Language
Welcome to my JavaScript resource! This is a space where I share what I've been learning about the JavaScript language. As I continue to explore and grow my knowledge, I'll be posting insights, tips, and concepts that I've picked up along the way. I hope you find it helpful and informative!
Variables and Constants
JavaScript provides `var`, `let`, and `const` for declaring variables. Use `let` and `const` for modern JavaScript.
// Example
let name = "Chris";
const age = 25;
var isDeveloper = true;
// const cannot be reassigned
const PI = 3.14159;
// PI = 3.14; // Error!
// let can be reassigned
let score = 0;
score = 100; // OK!
Resources:
Data Types
JavaScript has 7 primitive types and 1 non-primitive type (Object). Understanding types is crucial for writing bug-free code.
// Primitive Types
const str = "Hello"; // String
const num = 42; // Number
const bool = true; // Boolean
const nothing = null; // Null
const notDefined = undefined; // Undefined
const sym = Symbol('id'); // Symbol
const bigNum = 9007199254740991n; // BigInt
// Non-primitive (Reference Type)
const obj = { name: "Chris" }; // Object
const arr = [1, 2, 3]; // Array (type of Object)
const func = () => {}; // Function (type of Object)
// Type checking
console.log(typeof str); // "string"
console.log(Array.isArray(arr)); // true
Resources:
Arrow Functions
Arrow functions provide a concise syntax for writing functions. They also do not bind their own `this`.
// Traditional function
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => a + b;
// With single parameter (no parentheses needed)
const double = x => x * 2;
// With no parameters
const getRandom = () => Math.random();
// Multi-line arrow function
const processUser = (user) => {
const name = user.name.toUpperCase();
const age = user.age + 1;
return { name, age };
};
// 'this' binding difference
const obj = {
name: "Chris",
greet: function() {
setTimeout(() => {
console.log(this.name); // 'Chris' - arrow function uses parent's this
}, 1000);
}
};
Resources:
Arrays and Array Methods
Arrays are ordered collections of values. JavaScript provides powerful methods to manipulate arrays without mutating the original.
// Creating arrays
const fruits = ['apple', 'banana', 'orange'];
const numbers = [1, 2, 3, 4, 5];
// Common array methods
// map - transform each element
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - keep elements that pass a test
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
// reduce - reduce array to single value
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15
// find - get first element that passes test
const found = fruits.find(f => f.startsWith('b'));
console.log(found); // 'banana'
// includes, some, every
console.log(fruits.includes('apple')); // true
console.log(numbers.some(n => n > 4)); // true
console.log(numbers.every(n => n < 10)); // true
// Destructuring arrays
const [first, second, ...rest] = fruits;
console.log(first); // 'apple'
console.log(rest); // ['orange']
Resources:
Objects and Object Methods
Objects are collections of key-value pairs. They're the building blocks of JavaScript applications.
// Creating objects
const user = {
name: "Chris",
age: 25,
skills: ["JavaScript", "CSS", "HTML"],
greet() {
return `Hi, I'm ${this.name}`;
}
};
// Accessing properties
console.log(user.name); // "Chris"
console.log(user['age']); // 25
console.log(user.greet()); // "Hi, I'm Chris"
// Object destructuring
const { name, age, city = "Kampala" } = user;
console.log(city); // "Kampala" (default value)
// Object methods
const keys = Object.keys(user); // ['name', 'age', 'skills', 'greet']
const values = Object.values(user); // ['Chris', 25, [...], function]
const entries = Object.entries(user); // [['name', 'Chris'], ...]
// Spread operator
const updatedUser = { ...user, age: 26, city: "Kampala" };
// Object.assign
const clone = Object.assign({}, user);
// Optional chaining
console.log(user?.address?.street); // undefined (no error)
// Dynamic property names
const prop = "favoriteColor";
const preferences = {
[prop]: "blue"
};
Resources:
Promises and Async/Await
Promises represent eventual completion (or failure) of an asynchronous operation. Async/await makes async code look synchronous.
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("Operation successful!");
} else {
reject("Operation failed!");
}
}, 1000);
});
// Using Promises with .then/.catch
myPromise
.then(result => console.log(result))
.catch(error => console.error(error));
// Async/Await syntax
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
} catch (error) {
console.error("Failed to fetch user:", error);
throw error;
}
}
// Multiple promises
async function fetchMultiple() {
// Wait for all promises
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return { users, posts };
}
// Promise.race - resolves with first settled promise
const fastest = await Promise.race([
fetch('/api/fast'),
fetch('/api/slow')
]);
Resources:
Destructuring Assignment
Destructuring allows you to unpack values from arrays or properties from objects into distinct variables. It makes code cleaner and more readable.
// Array destructuring
const colors = ['red', 'green', 'blue', 'yellow'];
const [primary, secondary, ...others] = colors;
console.log(primary); // 'red'
console.log(others); // ['blue', 'yellow']
// Swapping variables
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2, 1
// Object destructuring
const person = {
name: 'Chris',
age: 25,
location: {
city: 'Kampala',
country: 'Uganda'
}
};
// Basic destructuring with renaming
const { name: userName, age } = person;
console.log(userName); // 'Chris'
// Nested destructuring
const { location: { city, country } } = person;
console.log(city); // 'Kampala'
// Default values
const { theme = 'dark' } = person;
console.log(theme); // 'dark'
// Function parameter destructuring
function greet({ name, age = 18 }) {
return `${name} is ${age} years old`;
}
greet({ name: 'Chris', age: 25 }); // 'Chris is 25 years old'
// Rest in object destructuring
const { name, ...details } = person;
console.log(details); // { age: 25, location: {...} }
Resources:
Template Literals
Template literals provide an easy way to interpolate variables and expressions into strings. They also support multi-line strings.
// Basic string interpolation
const name = "Chris";
const age = 25;
const message = `Hello, my name is ${name} and I'm ${age} years old.`;
console.log(message);
// Multi-line strings
const multiline = `
This is a multi-line string.
It preserves line breaks and
makes formatting much easier!
`;
// Expression interpolation
const items = ['apple', 'banana', 'orange'];
console.log(`You have ${items.length} items in your cart.`);
console.log(`Total: $${(19.99 * 1.15).toFixed(2)}`);
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] || '';
return result + str + `<mark>${value}</mark>`;
}, '');
}
const highlighted = highlight`Hello ${name}, you are ${age} years old`;
// "Hello <mark>Chris</mark>, you are <mark>25</mark> years old"
// Nesting templates
const nested = `User: ${name} (${age > 18 ? `Adult: ${age}` : 'Minor'})`;
// Raw strings
const path = String.raw`C:\Users\Chris\Documents`;
console.log(path); // C:\Users\Chris\Documents (backslashes preserved)
Resources:
Spread and Rest Operators
The spread operator (...) expands iterables into individual elements, while rest collects multiple elements into an array.
// Spread with arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// Copy array (shallow)
const original = [1, 2, 3];
const copy = [...original];
// Spread with objects
const user = { name: 'Chris', age: 25 };
const location = { city: 'Kampala', country: 'Uganda' };
const profile = { ...user, ...location, age: 26 }; // age overwritten
// Rest parameters in functions
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Rest in destructuring
const [first, ...remaining] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(remaining); // [2, 3, 4, 5]
// Practical examples
// Convert NodeList to Array
const divs = [...document.querySelectorAll('div')];
// Max/Min of array
const scores = [89, 94, 76, 81];
const highest = Math.max(...scores); // 94
// Clone and modify
const updateUser = (user, updates) => ({
...user,
...updates,
updatedAt: new Date()
});
Resources:
Closures
A closure gives you access to an outer function's scope from an inner function. It's created when a function is defined, not when it's executed.
// Basic closure example
function outer(x) {
// Inner function has access to outer's variables
return function inner(y) {
return x + y;
};
}
const addFive = outer(5);
console.log(addFive(3)); // 8
console.log(addFive(7)); // 12
// Private variables with closures
function createCounter() {
let count = 0; // Private variable
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// console.log(count); // Error! count is not accessible
// Common pitfall - loops and closures
// Wrong way
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints 3, 3, 3
}
// Correct way - using let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints 0, 1, 2
}
// Or using IIFE
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(() => console.log(index), 100); // Prints 0, 1, 2
})(i);
}
Resources:
The 'this' Keyword
The 'this' keyword refers to the object that is executing the current function. Its value depends on how the function is called.
// 'this' in different contexts
// 1. Global context
console.log(this); // window (in browser)
// 2. Object method
const user = {
name: 'Chris',
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
user.greet(); // "Hello, I'm Chris"
// 3. Constructor function
function Person(name) {
this.name = name;
}
const person = new Person('Chris');
console.log(person.name); // 'Chris'
// 4. Arrow functions (inherit 'this')
const obj = {
name: 'Chris',
regularFunc: function() {
console.log(this.name); // 'Chris'
const arrowFunc = () => {
console.log(this.name); // 'Chris' (inherited)
};
arrowFunc();
}
};
// 5. Explicit binding - call, apply, bind
function introduce(greeting) {
console.log(`${greeting}, I'm ${this.name}`);
}
const user1 = { name: 'Chris' };
const user2 = { name: 'Jane' };
introduce.call(user1, 'Hi'); // "Hi, I'm Chris"
introduce.apply(user2, ['Hello']); // "Hello, I'm Jane"
const boundFunc = introduce.bind(user1);
boundFunc('Hey'); // "Hey, I'm Chris"
// Common mistake
const button = {
text: 'Click me',
click() {
console.log(this.text);
}
};
// setTimeout(button.click, 1000); // undefined - 'this' is lost
setTimeout(() => button.click(), 1000); // 'Click me' - works!
Resources:
Classes
ES6 classes provide a cleaner syntax for creating objects and implementing inheritance. They're syntactic sugar over JavaScript's prototype-based inheritance.
// Basic class syntax
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// Method
greet() {
return `Hi, I'm ${this.name}`;
}
// Getter
get info() {
return `${this.name} is ${this.age} years old`;
}
// Setter
set nickname(value) {
this._nickname = value;
}
// Static method
static species() {
return 'Homo sapiens';
}
}
const chris = new Person('Chris', 25);
console.log(chris.greet()); // "Hi, I'm Chris"
console.log(chris.info); // "Chris is 25 years old"
console.log(Person.species()); // "Homo sapiens"
// Inheritance
class Developer extends Person {
constructor(name, age, language) {
super(name, age); // Call parent constructor
this.language = language;
}
greet() {
return `${super.greet()} and I code in ${this.language}`;
}
code() {
return `${this.name} is coding in ${this.language}`;
}
}
const dev = new Developer('Chris', 25, 'JavaScript');
console.log(dev.greet()); // "Hi, I'm Chris and I code in JavaScript"
console.log(dev.code()); // "Chris is coding in JavaScript"
// Private fields (ES2022)
class BankAccount {
#balance = 0; // Private field
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.getBalance()); // 100
// console.log(account.#balance); // Error! Private field
Resources:
Modules (ES6)
Modules allow you to break up your code into separate files. Each module has its own scope and can export/import functionality.
// math.js - Named exports
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// main.js - Named imports
import { PI, add, multiply } from './math.js';
console.log(PI); // 3.14159
console.log(add(2, 3)); // 5
// Alternative import syntax
import * as math from './math.js';
console.log(math.PI); // 3.14159
console.log(math.add(2, 3)); // 5
// user.js - Default export
export default class User {
constructor(name) {
this.name = name;
}
}
// Can also export like this
const User = class { /* ... */ };
export default User;
// app.js - Default import
import User from './user.js';
const user = new User('Chris');
// Mixed exports
// utils.js
export default function log(message) {
console.log(message);
}
export const VERSION = '1.0.0';
export const API_URL = 'https://api.example.com';
// Using mixed imports
import log, { VERSION, API_URL } from './utils.js';
// Dynamic imports
async function loadModule() {
const module = await import('./heavy-module.js');
module.doSomething();
}
// Re-exporting
// index.js
export { add, multiply } from './math.js';
export { default as User } from './user.js';
Resources:
Error Handling
Proper error handling is crucial for robust applications. JavaScript provides try...catch for synchronous code and Promise methods for async errors.
// Basic try...catch
try {
// Code that might throw an error
const data = JSON.parse('invalid json');
} catch (error) {
console.error('Parsing failed:', error.message);
} finally {
console.log('This always runs');
}
// Throwing custom errors
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero!');
}
return a / b;
}
try {
const result = divide(10, 0);
} catch (error) {
console.error(error.message); // "Division by zero!"
}
// Custom error classes
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
function validateEmail(email) {
if (!email.includes('@')) {
throw new ValidationError('Invalid email format');
}
}
// Async error handling
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
throw error; // Re-throw if needed
}
}
// Promise error handling
fetchData()
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
// Global error handling
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
});
Resources:
Map and Set
Map and Set are collection types introduced in ES6. Map holds key-value pairs where keys can be any type, while Set stores unique values.
// Map - key-value pairs with any type of key
const map = new Map();
// Adding entries
map.set('name', 'Chris');
map.set(42, 'The answer');
map.set(true, 'Boolean key');
const objKey = { id: 1 };
map.set(objKey, 'Object as key!');
// Getting values
console.log(map.get('name')); // 'Chris'
console.log(map.get(42)); // 'The answer'
console.log(map.get(objKey)); // 'Object as key!'
// Map methods
console.log(map.has('name')); // true
console.log(map.size); // 4
map.delete(42);
// map.clear(); // Remove all entries
// Iterating Map
for (const [key, value] of map) {
console.log(`${key} => ${value}`);
}
// Creating Map from array
const userMap = new Map([
['name', 'Chris'],
['age', 25],
['city', 'Kampala']
]);
// Set - collection of unique values
const set = new Set();
// Adding values
set.add(1);
set.add(2);
set.add(2); // Duplicate, won't be added
set.add('hello');
console.log(set.size); // 3
console.log(set.has(2)); // true
// Creating Set from array (removes duplicates)
const numbers = [1, 2, 2, 3, 3, 3, 4];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4]
// Set operations
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);
// Union
const union = new Set([...setA, ...setB]); // {1, 2, 3, 4, 5}
// Intersection
const intersection = new Set(
[...setA].filter(x => setB.has(x))
); // {3}
// Difference
const difference = new Set(
[...setA].filter(x => !setB.has(x))
); // {1, 2}