| name | javascript-fundamentals |
| description | Core JavaScript language features, patterns, and best practices including ES6+ syntax, async/await, closures, prototypes, and modern development patterns |
| category | frontend |
| tags | javascript, es6, async, closures, prototypes, programming |
| version | 1.0.0 |
JavaScript Fundamentals
A comprehensive guide to core JavaScript concepts, modern ES6+ features, asynchronous programming patterns, and industry best practices for building robust applications.
When to Use This Skill
This skill is essential when:
- Writing JavaScript Code: Building web applications, Node.js backends, or any JavaScript-based project
- Code Reviews: Evaluating code quality, identifying anti-patterns, suggesting improvements
- Teaching/Mentoring: Explaining JavaScript concepts, debugging issues, pair programming
- Refactoring Legacy Code: Modernizing codebases with ES6+ features and better patterns
- Architecture Design: Choosing appropriate patterns and structures for your application
- Performance Optimization: Understanding memory management, event loops, and efficient patterns
- Debugging: Tracing execution flow, understanding scope chains, and async behavior
- Interview Preparation: Mastering fundamental concepts tested in technical interviews
Core Concepts
Variables and Scope
JavaScript has three ways to declare variables, each with different scoping rules:
var: Function-scoped, hoisted, can be redeclared
function varExample() {
var x = 1;
if (true) {
var x = 2; // Same variable!
console.log(x); // 2
}
console.log(x); // 2
}
let: Block-scoped, not hoisted to usable state, cannot be redeclared
function letExample() {
let x = 1;
if (true) {
let x = 2; // Different variable
console.log(x); // 2
}
console.log(x); // 1
}
const: Block-scoped, must be initialized, reference cannot be reassigned
const PI = 3.14159;
// PI = 3; // Error: Assignment to constant variable
const user = { name: 'John' };
user.name = 'Jane'; // OK - modifying object properties
// user = {}; // Error - reassigning reference
Best Practice: Use const by default, let when reassignment is needed, avoid var.
Data Types
JavaScript has 7 primitive types and objects:
Primitives:
String: Text dataNumber: Integers and floating-point numbersBigInt: Arbitrary precision integersBoolean: true/falseundefined: Uninitialized variablenull: Intentional absence of valueSymbol: Unique identifiers
Objects: Collections of key-value pairs, including arrays, functions, dates, etc.
// Type checking
typeof "hello" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" (historical bug!)
typeof {} // "object"
typeof [] // "object"
typeof function(){} // "function"
// Better array checking
Array.isArray([]) // true
Array.isArray({}) // false
// Null checking
value === null // true only for null
value == null // true for null AND undefined
Functions
Functions are first-class citizens in JavaScript - they can be assigned to variables, passed as arguments, and returned from other functions.
Function Declaration:
function greet(name) {
return `Hello, ${name}!`;
}
Function Expression:
const greet = function(name) {
return `Hello, ${name}!`;
};
Arrow Function (ES6+):
const greet = (name) => `Hello, ${name}!`;
// Multiple parameters
const add = (a, b) => a + b;
// Single parameter (parentheses optional)
const double = x => x * 2;
// Multiple statements (need braces and explicit return)
const complexFunction = (x, y) => {
const result = x + y;
return result * 2;
};
Key Differences:
- Arrow functions don't have their own
thisbinding - Arrow functions cannot be used as constructors
- Arrow functions don't have
argumentsobject - Function declarations are hoisted, expressions are not
Objects and Arrays
Object Creation:
// Object literal
const person = {
name: 'John',
age: 30,
greet() {
return `Hello, I'm ${this.name}`;
}
};
// Accessing properties
person.name // Dot notation
person['name'] // Bracket notation (dynamic keys)
// Adding/modifying properties
person.email = 'john@example.com';
person.age = 31;
// Deleting properties
delete person.email;
Array Methods:
const numbers = [1, 2, 3, 4, 5];
// Transformation
numbers.map(n => n * 2) // [2, 4, 6, 8, 10]
numbers.filter(n => n > 2) // [3, 4, 5]
numbers.reduce((sum, n) => sum + n, 0) // 15
// Iteration
numbers.forEach(n => console.log(n));
// Search
numbers.find(n => n > 3) // 4
numbers.findIndex(n => n > 3) // 3
numbers.includes(3) // true
// Mutation (modify original)
numbers.push(6) // Add to end
numbers.pop() // Remove from end
numbers.unshift(0) // Add to start
numbers.shift() // Remove from start
numbers.splice(2, 1) // Remove 1 item at index 2
// Non-mutating
numbers.slice(1, 3) // [2, 3]
numbers.concat([6, 7]) // [1, 2, 3, 4, 5, 6, 7]
[...numbers, 6, 7] // Spread operator
Closures
A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned.
function createCounter() {
let count = 0; // Private variable
return {
increment() {
return ++count;
},
decrement() {
return --count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// count is not directly accessible
Use Cases:
- Data privacy/encapsulation
- Factory functions
- Event handlers
- Callbacks maintaining state
- Module pattern implementation
Prototypes and Inheritance
JavaScript uses prototypal inheritance. Every object has an internal [[Prototype]] link to another object.
// Constructor function
function Animal(name) {
this.name = name;
}
// Add method to prototype
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
const dog = new Animal('Dog');
console.log(dog.speak()); // "Dog makes a sound"
// Inheritance
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up prototype chain
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
return `${this.name} barks!`;
};
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.speak()); // "Buddy makes a sound" (inherited)
console.log(myDog.bark()); // "Buddy barks!" (own method)
Classes (ES6+)
Classes provide syntactic sugar over prototypal inheritance:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
// Static method
static create(name) {
return new Animal(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
return `${super.speak()} - Woof!`;
}
// Getter
get info() {
return `${this.name} is a ${this.breed}`;
}
// Setter
set nickname(value) {
this._nickname = value;
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.speak()); // "Buddy makes a sound - Woof!"
console.log(myDog.info); // "Buddy is a Golden Retriever"
myDog.nickname = 'Bud';
Modern JavaScript (ES6+)
Destructuring
Extract values from arrays or properties from objects:
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// Object destructuring
const user = { name: 'John', age: 30, city: 'NYC' };
const { name, age } = user;
console.log(name); // "John"
// Renaming
const { name: userName, age: userAge } = user;
// Default values
const { country = 'USA' } = user;
// Nested destructuring
const data = {
user: { name: 'John', address: { city: 'NYC' } }
};
const { user: { address: { city } } } = data;
// Function parameters
function greet({ name, age = 0 }) {
return `${name} is ${age} years old`;
}
greet({ name: 'John', age: 30 }); // "John is 30 years old"
Spread and Rest Operators
Spread (...) expands elements:
// Arrays
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const combined = [...arr1, ...arr2];
// Objects
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// Function arguments
Math.max(...arr1); // 3
// Shallow copy
const copy = [...arr1];
const objCopy = { ...obj1 };
Rest (...) collects elements:
// Function parameters
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4); // 10
// With other parameters
function multiply(multiplier, ...numbers) {
return numbers.map(n => n * multiplier);
}
multiply(2, 1, 2, 3); // [2, 4, 6]
Template Literals
Multi-line strings and string interpolation:
const name = 'John';
const age = 30;
// String interpolation
const greeting = `Hello, ${name}!`;
// Expressions
const message = `In 5 years, you'll be ${age + 5}`;
// Multi-line
const multiline = `
This is a
multi-line
string
`;
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
}, '');
}
const html = highlight`Hello, ${name}! You are ${age} years old.`;
// "Hello, <mark>John</mark>! You are <mark>30</mark> years old."
Optional Chaining and Nullish Coalescing
Optional Chaining (?.): Safely access nested properties:
const user = {
name: 'John',
address: {
city: 'NYC'
}
};
// Without optional chaining
const city = user && user.address && user.address.city;
// With optional chaining
const city = user?.address?.city; // "NYC"
const zip = user?.address?.zip; // undefined (no error!)
// With methods
user.getName?.(); // Only calls if method exists
// With arrays
const firstItem = arr?.[0];
Nullish Coalescing (??): Default values for null/undefined:
// || returns right side for ANY falsy value
const value1 = 0 || 'default'; // "default"
const value2 = '' || 'default'; // "default"
const value3 = false || 'default'; // "default"
// ?? only for null/undefined
const value4 = 0 ?? 'default'; // 0
const value5 = '' ?? 'default'; // ""
const value6 = false ?? 'default'; // false
const value7 = null ?? 'default'; // "default"
const value8 = undefined ?? 'default'; // "default"
Modules
Exporting:
// Named exports
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
// ...
}
// Export multiple
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };
// Default export (one per file)
export default class App {
// ...
}
// Or
class App { }
export default App;
Importing:
// Named imports
import { PI, add } from './math.js';
import { multiply as mult } from './math.js'; // Rename
// Default import
import App from './App.js';
// Mix default and named
import App, { PI, add } from './App.js';
// Import all
import * as Math from './math.js';
Math.PI;
Math.add(1, 2);
// Import for side effects only
import './polyfills.js';
Asynchronous Patterns
The Event Loop
JavaScript is single-threaded but handles async operations through the event loop:
- Call Stack: Executes synchronous code
- Web APIs: Browser/Node APIs (setTimeout, fetch, etc.)
- Callback Queue: Completed async operations wait here
- Event Loop: Moves callbacks from queue to stack when stack is empty
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
// Output: 1, 4, 3, 2
// Microtasks (Promises) have priority over macrotasks (setTimeout)
Callbacks
Traditional async pattern (can lead to "callback hell"):
function fetchUser(userId, callback) {
setTimeout(() => {
callback(null, { id: userId, name: 'John' });
}, 1000);
}
function fetchPosts(userId, callback) {
setTimeout(() => {
callback(null, [{ id: 1, title: 'Post 1' }]);
}, 1000);
}
// Callback hell
fetchUser(1, (error, user) => {
if (error) {
console.error(error);
return;
}
fetchPosts(user.id, (error, posts) => {
if (error) {
console.error(error);
return;
}
console.log(user, posts);
});
});
Promises
Promises represent eventual completion (or failure) of an async operation:
// Creating a promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('Success!');
} else {
reject(new Error('Failed!'));
}
}, 1000);
});
// Using a promise
promise
.then(result => {
console.log(result); // "Success!"
return result.toUpperCase();
})
.then(upperResult => {
console.log(upperResult); // "SUCCESS!"
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log('Cleanup');
});
// Promise utilities
Promise.all([promise1, promise2, promise3])
.then(results => {
// All resolved: [result1, result2, result3]
});
Promise.race([promise1, promise2])
.then(result => {
// First to resolve
});
Promise.allSettled([promise1, promise2])
.then(results => {
// All completed (resolved or rejected)
// [{ status: 'fulfilled', value: ... }, { status: 'rejected', reason: ... }]
});
Promise.any([promise1, promise2])
.then(result => {
// First to fulfill (resolve)
});
Async/Await
Modern syntax for handling promises (ES2017+):
async function fetchUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// Using the async function
fetchUserData(1)
.then(data => console.log(data))
.catch(error => console.error(error));
// Parallel execution
async function fetchAllData() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments()
]);
return { users, posts, comments };
}
// Sequential vs Parallel
async function sequential() {
const result1 = await operation1(); // Wait
const result2 = await operation2(); // Then wait
return [result1, result2];
}
async function parallel() {
const [result1, result2] = await Promise.all([
operation1(), // Start both
operation2() // simultaneously
]);
return [result1, result2];
}
Error Handling in Async Code
// Try-catch with async/await
async function robustFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
if (error.name === 'TypeError') {
console.error('Network error:', error);
} else {
console.error('Error:', error);
}
throw error; // Re-throw or handle
}
}
// Promise catch
fetchData()
.then(data => processData(data))
.catch(error => {
// Catches errors from fetchData AND processData
console.error(error);
});
// Global error handlers
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled promise rejection:', event.reason);
event.preventDefault();
});
Common Patterns
Module Pattern
Encapsulate private data and expose public API:
const Calculator = (function() {
// Private variables
let history = [];
// Private function
function log(operation) {
history.push(operation);
}
// Public API
return {
add(a, b) {
const result = a + b;
log(`${a} + ${b} = ${result}`);
return result;
},
subtract(a, b) {
const result = a - b;
log(`${a} - ${b} = ${result}`);
return result;
},
getHistory() {
return [...history]; // Return copy
},
clearHistory() {
history = [];
}
};
})();
Calculator.add(5, 3); // 8
console.log(Calculator.getHistory()); // ["5 + 3 = 8"]
Revealing Module Pattern
Cleaner variation of module pattern:
const UserManager = (function() {
// Private
let users = [];
function findUserById(id) {
return users.find(u => u.id === id);
}
function validateUser(user) {
return user && user.name && user.email;
}
// Public methods
function addUser(user) {
if (validateUser(user)) {
users.push(user);
return true;
}
return false;
}
function getUser(id) {
return findUserById(id);
}
function getAllUsers() {
return [...users];
}
// Reveal public API
return {
add: addUser,
get: getUser,
getAll: getAllUsers
};
})();
Factory Pattern
Create objects without specifying exact class:
function createUser(name, role) {
const roles = {
admin: {
permissions: ['read', 'write', 'delete'],
level: 3
},
editor: {
permissions: ['read', 'write'],
level: 2
},
viewer: {
permissions: ['read'],
level: 1
}
};
const roleConfig = roles[role] || roles.viewer;
return {
name,
role,
...roleConfig,
hasPermission(permission) {
return this.permissions.includes(permission);
},
toString() {
return `${this.name} (${this.role})`;
}
};
}
const admin = createUser('Alice', 'admin');
const editor = createUser('Bob', 'editor');
console.log(admin.hasPermission('delete')); // true
console.log(editor.hasPermission('delete')); // false
Singleton Pattern
Ensure only one instance exists:
const Config = (function() {
let instance;
function createInstance() {
return {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
get(key) {
return this[key];
},
set(key, value) {
this[key] = value;
}
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const config1 = Config.getInstance();
const config2 = Config.getInstance();
console.log(config1 === config2); // true
// Modern ES6 class version
class Database {
constructor() {
if (Database.instance) {
return Database.instance;
}
this.connection = null;
Database.instance = this;
}
connect() {
if (!this.connection) {
this.connection = 'Connected to DB';
}
return this.connection;
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
Observer Pattern
Subscribe to and publish events:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
// Return unsubscribe function
return () => this.off(event, listener);
}
off(event, listenerToRemove) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(
listener => listener !== listenerToRemove
);
}
emit(event, ...args) {
if (!this.events[event]) return;
this.events[event].forEach(listener => {
listener(...args);
});
}
once(event, listener) {
const onceWrapper = (...args) => {
listener(...args);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
}
// Usage
const emitter = new EventEmitter();
const unsubscribe = emitter.on('data', (data) => {
console.log('Received:', data);
});
emitter.emit('data', { id: 1 }); // "Received: { id: 1 }"
unsubscribe();
emitter.emit('data', { id: 2 }); // Nothing logged
Memoization
Cache function results for performance:
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cached result');
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// Expensive function
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(40)); // Slow first time
console.log(memoizedFib(40)); // Instant (cached)
// With object methods
const calculator = {
multiplier: 2,
calculate: memoize(function(n) {
return n * this.multiplier;
})
};
Best Practices
Naming Conventions
// Constants - UPPERCASE_SNAKE_CASE
const MAX_RETRIES = 3;
const API_BASE_URL = 'https://api.example.com';
// Variables and functions - camelCase
let userName = 'John';
function getUserData() { }
// Classes - PascalCase
class UserAccount { }
class HTTPHandler { }
// Private convention - prefix with underscore
class Widget {
constructor() {
this._privateProperty = 'internal';
}
_privateMethod() {
// Implementation
}
}
// Boolean variables - use is/has/can prefix
const isActive = true;
const hasPermission = false;
const canEdit = true;
// Functions - use verb prefix
function getUser() { }
function setConfig() { }
function validateInput() { }
function handleClick() { }
Error Handling
// Custom error classes
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
// Throw specific errors
function validateEmail(email) {
if (!email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
return true;
}
// Handle different error types
try {
validateEmail('invalid');
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Validation failed for ${error.field}: ${error.message}`);
} else {
console.error('Unexpected error:', error);
}
}
// Async error handling
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new NetworkError('Request failed', response.status);
}
return await response.json();
} catch (error) {
if (i === retries - 1) throw error; // Last retry
console.log(`Retry ${i + 1}/${retries}`);
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
Performance Tips
// 1. Avoid unnecessary operations in loops
// Bad
for (let i = 0; i < array.length; i++) { } // Length calculated each iteration
// Good
const len = array.length;
for (let i = 0; i < len; i++) { }
// Better - use built-in methods
array.forEach(item => { });
// 2. Debounce expensive operations
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
const expensiveSearch = debounce((query) => {
// API call
}, 300);
// 3. Throttle high-frequency events
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
window.addEventListener('scroll', throttle(() => {
// Handle scroll
}, 100));
// 4. Use object/map for lookups
// Bad - O(n)
const colors = ['red', 'blue', 'green'];
colors.includes('blue'); // Linear search
// Good - O(1)
const colorSet = new Set(['red', 'blue', 'green']);
colorSet.has('blue'); // Constant time
// 5. Avoid memory leaks
// Bad - event listener not removed
element.addEventListener('click', handler);
// Good
const handler = () => { };
element.addEventListener('click', handler);
// Later...
element.removeEventListener('click', handler);
// 6. Use WeakMap for private data
const privateData = new WeakMap();
class MyClass {
constructor() {
privateData.set(this, { secret: 'value' });
}
getSecret() {
return privateData.get(this).secret;
}
}
// When instance is garbage collected, WeakMap entry is too
Code Organization
// 1. Single Responsibility Principle
// Bad - function does too much
function processUserData(userData) {
// Validate
// Transform
// Save to database
// Send email
// Update UI
}
// Good - separate concerns
function validateUser(userData) { }
function transformUserData(userData) { }
function saveUser(userData) { }
function sendWelcomeEmail(user) { }
function updateUI(user) { }
// 2. Pure functions (no side effects)
// Bad - modifies input
function addToCart(cart, item) {
cart.items.push(item);
return cart;
}
// Good - returns new object
function addToCart(cart, item) {
return {
...cart,
items: [...cart.items, item]
};
}
// 3. Avoid magic numbers
// Bad
if (user.role === 2) { }
// Good
const ROLES = {
ADMIN: 1,
EDITOR: 2,
VIEWER: 3
};
if (user.role === ROLES.EDITOR) { }
// 4. Use default parameters
// Bad
function greet(name) {
name = name || 'Guest';
return `Hello, ${name}`;
}
// Good
function greet(name = 'Guest') {
return `Hello, ${name}`;
}
// 5. Guard clauses for early returns
// Bad
function processOrder(order) {
if (order) {
if (order.items.length > 0) {
if (order.total > 0) {
// Process order
}
}
}
}
// Good
function processOrder(order) {
if (!order) return;
if (order.items.length === 0) return;
if (order.total <= 0) return;
// Process order
}
Memory Management
// 1. Clear timers and intervals
const timerId = setTimeout(() => { }, 1000);
clearTimeout(timerId);
const intervalId = setInterval(() => { }, 1000);
clearInterval(intervalId);
// 2. Remove event listeners
const handler = () => { };
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
// 3. Set references to null when done
let largeData = fetchLargeDataset();
// Use data...
largeData = null; // Allow garbage collection
// 4. Use WeakMap/WeakSet for caches
const cache = new WeakMap();
let obj = { data: 'value' };
cache.set(obj, 'cached value');
obj = null; // Cache entry automatically removed
// 5. Avoid global variables
// Bad
var globalUser = { };
// Good - use modules/closures
(function() {
const user = { };
// Use user locally
})();
// 6. Be careful with closures
function createHeavyObject() {
const heavyData = new Array(1000000);
// Bad - closure keeps heavyData in memory
return function() {
console.log(heavyData.length);
};
// Good - only capture what you need
const length = heavyData.length;
return function() {
console.log(length);
};
}
Testing Considerations
// 1. Write testable code - pure functions
// Testable
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Hard to test - depends on external state
function calculateTotal() {
return cart.items.reduce((sum, item) => sum + item.price, 0);
}
// 2. Dependency injection
// Hard to test
class UserService {
constructor() {
this.api = new ApiClient();
}
}
// Easy to test - inject dependencies
class UserService {
constructor(apiClient) {
this.api = apiClient;
}
}
// 3. Avoid side effects
// Side effects
function processData(data) {
console.log('Processing...'); // Side effect
updateDatabase(data); // Side effect
return transform(data);
}
// Pure
function transformData(data) {
return transform(data);
}
// Separate side effects
function processData(data) {
const transformed = transformData(data);
console.log('Processing...');
updateDatabase(transformed);
return transformed;
}
Quick Reference
Array Methods Cheat Sheet
const arr = [1, 2, 3, 4, 5];
// Transform
arr.map(x => x * 2) // [2, 4, 6, 8, 10]
arr.filter(x => x > 2) // [3, 4, 5]
arr.reduce((sum, x) => sum + x) // 15
// Search
arr.find(x => x > 3) // 4
arr.findIndex(x => x > 3) // 3
arr.indexOf(3) // 2
arr.includes(3) // true
arr.some(x => x > 3) // true
arr.every(x => x > 0) // true
// Mutation
arr.push(6) // Add to end
arr.pop() // Remove from end
arr.unshift(0) // Add to start
arr.shift() // Remove from start
arr.splice(2, 1, 99) // Remove/add at index
// Non-mutating
arr.slice(1, 3) // [2, 3]
arr.concat([6, 7]) // [1, 2, 3, 4, 5, 6, 7]
[...arr] // Copy
arr.join(', ') // "1, 2, 3, 4, 5"
arr.reverse() // [5, 4, 3, 2, 1] (mutates!)
arr.sort((a, b) => a - b) // Sort (mutates!)
Object Methods Cheat Sheet
const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj) // ['a', 'b', 'c']
Object.values(obj) // [1, 2, 3]
Object.entries(obj) // [['a', 1], ['b', 2], ['c', 3]]
Object.fromEntries([['a', 1]]) // { a: 1 }
Object.assign({}, obj, { d: 4 }) // { a: 1, b: 2, c: 3, d: 4 }
{ ...obj, d: 4 } // Same as above
Object.freeze(obj) // Make immutable
Object.seal(obj) // Prevent add/remove properties
Object.preventExtensions(obj) // Prevent adding properties
Object.hasOwnProperty.call(obj, 'a') // true
'a' in obj // true
obj.hasOwnProperty('a') // true (not recommended)
Common Gotchas
// 1. Equality
0 == '0' // true (type coercion)
0 === '0' // false (strict)
null == undefined // true
null === undefined // false
// 2. Type coercion
'5' + 3 // "53" (string concatenation)
'5' - 3 // 2 (numeric subtraction)
+'5' // 5 (unary plus converts to number)
!!'value' // true (double negation to boolean)
// 3. this binding
const obj = {
name: 'Object',
regular: function() { console.log(this.name); },
arrow: () => { console.log(this.name); }
};
obj.regular(); // "Object"
obj.arrow(); // undefined (arrow uses lexical this)
// 4. Falsy values
false, 0, -0, 0n, '', null, undefined, NaN
// 5. Array/Object comparison
[] === [] // false (different references)
{} === {} // false (different references)
// 6. Floating point
0.1 + 0.2 === 0.3 // false (0.30000000000000004)
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // true
// 7. Hoisting
console.log(x); // undefined (var hoisted)
var x = 5;
console.log(y); // ReferenceError (let not hoisted to usable state)
let y = 5;
This comprehensive guide covers the essential JavaScript fundamentals needed for modern development. Practice these concepts regularly and refer to the EXAMPLES.md file for detailed implementation scenarios.