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
javascript
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
javascript
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
javascript
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.
javascript
// 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:
javascript
function greet(name) {
return `Hello, ${name}!`;
}
Function Expression:
javascript
const greet = function(name) {
return `Hello, ${name}!`;
};
Arrow Function (ES6+):
javascript
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
this binding - Arrow functions cannot be used as constructors
- Arrow functions don't have
arguments object - Function declarations are hoisted, expressions are not
Objects and Arrays
Object Creation:
javascript
// 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:
javascript
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.
javascript
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.
javascript
// 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:
javascript
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:
javascript
// 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:
javascript
// 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:
javascript
// 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:
javascript
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:
javascript
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:
javascript
// || 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:
javascript
// 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:
javascript
// 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: