Table of Contents
- Introduction
- ECMAScript Evolution
- ES2024 New Features
- Core JavaScript Concepts
- Asynchronous Programming
- Module Systems
- Error Handling
- Memory Management
- Performance Optimization
- Best Practices
- Jargon Reference Tables
- References
Introduction
JavaScript has evolved significantly since its creation in 1995. ECMAScript 2024 (ES2024 or ES15) represents the latest iteration of the language specification, bringing powerful new features that enhance developer productivity, code safety, and application performance. This guide provides comprehensive coverage of modern JavaScript, with particular focus on ES2024 features and industry best practices.
ECMAScript Evolution
What is ECMAScript?
ECMAScript is the standardized specification for JavaScript, maintained by Technical Committee 39 (TC39) under ECMA International. The relationship between these terms:
- JavaScript: The implementation of the ECMAScript specification (primarily in browsers and Node.js)
- ECMAScript: The formal standard defining the language syntax and behavior
- TC39: The committee responsible for evolving the JavaScript language
TC39 Proposal Process
New JavaScript features progress through five stages:
| Stage | Name | Description | Stability |
|---|
| Stage 0 | Strawperson | Initial idea or proposal | Experimental |
| Stage 1 | Proposal | Problem defined, solution proposed | Under consideration |
| Stage 2 | Draft | Precise syntax described | Expected to be developed |
| Stage 3 | Candidate | Specification complete, awaiting implementation feedback | Likely to be included |
| Stage 4 | Finished | Ready for inclusion in formal ECMAScript standard | Will be in next release |
ES2024 New Features
ECMAScript 2024 was officially approved on June 26, 2024, introducing several powerful capabilities:
1. Object.groupBy() and Map.groupBy()
These methods provide elegant solutions for grouping array elements based on callback function results.
Object.groupBy()
Groups elements into a plain JavaScript object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| const products = [
{ name: 'Laptop', category: 'Electronics', price: 1200 },
{ name: 'Shirt', category: 'Clothing', price: 30 },
{ name: 'Phone', category: 'Electronics', price: 800 },
{ name: 'Jeans', category: 'Clothing', price: 50 }
];
const grouped = Object.groupBy(products, item => item.category);
console.log(grouped);
// Output:
// {
// Electronics: [
// { name: 'Laptop', category: 'Electronics', price: 1200 },
// { name: 'Phone', category: 'Electronics', price: 800 }
// ],
// Clothing: [
// { name: 'Shirt', category: 'Clothing', price: 30 },
// { name: 'Jeans', category: 'Clothing', price: 50 }
// ]
// }
|
Map.groupBy()
Groups elements into a Map structure, useful when keys might not be strings:
1
2
3
4
5
6
7
8
9
10
| const numbers = [0, -5, 3, -4, 8, 9];
const signGroups = Map.groupBy(numbers, x => Math.sign(x));
console.log(signGroups);
// Output: Map(3) {
// 0 => [0],
// -1 => [-5, -4],
// 1 => [3, 8, 9]
// }
|
Use Cases:
- Data aggregation in analytics dashboards
- Organizing API responses
- Simplifying complex reduce operations
2. Promise.withResolvers()
Provides direct access to promise resolution controls, simplifying scenarios where promises need external resolution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| function createTimeout(ms) {
const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve(`Completed after ${ms}ms`), ms);
return { promise, cancel: () => reject(new Error('Cancelled')) };
}
const operation = createTimeout(2000);
// Use the promise
operation.promise
.then(result => console.log(result))
.catch(error => console.error(error));
// Can cancel if needed
// operation.cancel();
|
Use Cases:
- Event-driven programming
- WebSocket handling
- Manual promise orchestration
- Complex async state machines
3. Regular Expression /v Flag (unicodeSets)
Enables advanced Unicode operations and set operations in regular expressions:
1
2
3
4
5
6
7
8
| // Unicode string properties
const emojiRegex = /^\p{RGI_Emoji}$/v;
console.log(emojiRegex.test('π΅βπ«')); // true (multi-codepoint emoji)
// Set operations
const lettersAndNumbers = /[\p{Letter}&&\p{ASCII}]/v;
console.log(lettersAndNumbers.test('a')); // true
console.log(lettersAndNumbers.test('5')); // false
|
Features:
- Set intersection (
&&) - Set subtraction (
--) - Set union (implicit)
- Enhanced Unicode property support
These methods handle Unicode validation and sanitization:
1
2
3
4
5
6
7
8
9
10
11
| // Checking for well-formed strings
const validString = "Hello, world!";
console.log(validString.isWellFormed()); // true
const invalidString = "Hello, \uD800world!"; // Lone surrogate
console.log(invalidString.isWellFormed()); // false
// Sanitizing strings
const sanitized = invalidString.toWellFormed();
console.log(sanitized); // "Hello, οΏ½world!"
console.log(sanitized.isWellFormed()); // true
|
Use Cases:
- User input validation
- Network data sanitization
- Preventing Unicode-related security issues
5. Resizable ArrayBuffer and SharedArrayBuffer
Dynamic memory management for binary data:
1
2
3
4
5
6
7
8
9
10
11
| // Resizable ArrayBuffer
const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
console.log(buffer.byteLength); // 8
buffer.resize(12);
console.log(buffer.byteLength); // 12
// Transferring ownership
const newBuffer = buffer.transfer();
console.log(buffer.detached); // true
console.log(newBuffer.byteLength); // 12
|
Use Cases:
- WebAssembly integration
- Streaming data processing
- Dynamic buffer management in games
6. Atomics.waitAsync()
Non-blocking synchronization for shared memory in multi-threaded environments:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // Note: Requires SharedArrayBuffer (disabled by default in browsers)
const sharedBuffer = new SharedArrayBuffer(4);
const view = new Int32Array(sharedBuffer);
Atomics.store(view, 0, 0);
// Wait asynchronously for value change
Atomics.waitAsync(view, 0, 0).value.then(() => {
console.log("Value has changed!");
});
// Simulate change from another thread
setTimeout(() => {
Atomics.store(view, 0, 1);
Atomics.notify(view, 0);
}, 1000);
|
Use Cases:
- Web Workers coordination
- High-performance multi-threaded applications
- Shared memory synchronization
Core JavaScript Concepts
Data Types
JavaScript has eight data types divided into two categories:
Primitive Types
- Number: Represents both integers and floating-point numbers
1
2
3
4
| const integer = 42;
const float = 3.14;
const infinity = Infinity;
const notANumber = NaN;
|
- String: Represents textual data
1
2
3
| const single = 'Hello';
const double = "World";
const template = `Hello ${name}`;
|
- Boolean: Represents logical values
1
2
| const isActive = true;
const isCompleted = false;
|
- Undefined: Variable declared but not assigned
1
2
| let value;
console.log(value); // undefined
|
- Null: Intentional absence of value
- BigInt: Arbitrary precision integers
1
2
| const huge = 9007199254740991n;
const fromConstructor = BigInt("9007199254740991");
|
- Symbol: Unique and immutable identifier
1
2
3
| const sym1 = Symbol('description');
const sym2 = Symbol('description');
console.log(sym1 === sym2); // false (always unique)
|
Reference Type
- Object: Collections of properties and methods
1
2
3
| const obj = { key: 'value' };
const arr = [1, 2, 3];
const func = function() { };
|
Variable Declarations
| Declaration | Scope | Reassignable | Hoisting | Temporal Dead Zone |
|---|
var | Function | Yes | Initialized to undefined | No |
let | Block | Yes | Not initialized | Yes |
const | Block | No | Not initialized | Yes |
Best Practices for Declarations
1
2
3
4
5
6
7
8
9
10
11
12
| // β
Prefer const for values that won't change
const MAX_SIZE = 100;
const config = { api: 'https://api.example.com' };
// β
Use let for reassignable variables
let counter = 0;
for (let i = 0; i < 10; i++) {
counter += i;
}
// β Avoid var (function-scoped, can lead to bugs)
var globalVar = 'accessible everywhere in function';
|
Scope and Closures
Lexical Scope
JavaScript uses lexical (static) scoping, where variable accessibility is determined by code structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| const global = 'global';
function outer() {
const outerVar = 'outer';
function inner() {
const innerVar = 'inner';
console.log(global); // β
accessible
console.log(outerVar); // β
accessible
console.log(innerVar); // β
accessible
}
inner();
// console.log(innerVar); // β not accessible
}
|
Closures
A closure allows a function to access variables from its outer scope even after the outer function has returned:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| 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
|
Memory-Efficient Closures
Closures can inadvertently retain large objects. Follow these patterns:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // β Potential memory leak
function createLeak() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log(largeData.length); // Retains entire array
};
}
// β
Better approach
function avoidLeak() {
const length = new Array(1000000).length; // Only store what's needed
return function() {
console.log(length);
};
}
|
The this Keyword
The value of this depends on how a function is called:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| const obj = {
name: 'Object',
// Regular function: this = calling object
regularMethod() {
console.log(this.name);
},
// Arrow function: this = lexical scope
arrowMethod: () => {
console.log(this.name); // 'this' from outer scope
},
// Method with callback
withCallback() {
// β Lost context
setTimeout(function() {
console.log(this.name); // undefined
}, 100);
// β
Preserved with arrow function
setTimeout(() => {
console.log(this.name); // 'Object'
}, 100);
}
};
obj.regularMethod(); // 'Object'
|
Binding Context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| const person = {
name: 'Alice',
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
const greet = person.greet;
greet(); // Error: this.name is undefined
// β
Solution 1: bind()
const boundGreet = person.greet.bind(person);
boundGreet(); // "Hello, I'm Alice"
// β
Solution 2: call()
person.greet.call({ name: 'Bob' }); // "Hello, I'm Bob"
// β
Solution 3: apply()
person.greet.apply({ name: 'Charlie' }); // "Hello, I'm Charlie"
|
Asynchronous Programming
The Event Loop
JavaScript is single-threaded but handles asynchronous operations through the event loop:
1
2
3
4
5
6
7
8
9
10
11
| βββββββββββββββββββββββββββββ
β Call Stack β
βββββββββββββββββββββββββββββ€
β Web APIs β
βββββββββββββββββββββββββββββ€
β Microtask Queue β
β (Promises) β
βββββββββββββββββββββββββββββ€
β Macrotask Queue β
β (setTimeout, I/O) β
βββββββββββββββββββββββββββββ
|
Execution Order:
- Execute synchronous code on call stack
- Process all microtasks (Promises, queueMicrotask)
- Process one macrotask (setTimeout, setInterval, I/O)
- Render if needed
- Repeat from step 2
Callbacks
Traditional approach to handling async operations:
1
2
3
4
5
6
7
8
9
10
| // β Callback hell (pyramid of doom)
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
getYetMoreData(c, function(d) {
console.log('Final result:', d);
});
});
});
});
|
Promises
Modern abstraction for async operations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // Promise states: pending, fulfilled, rejected
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve('Operation successful');
} else {
reject(new Error('Operation failed'));
}
});
// Chaining
promise
.then(result => {
console.log(result);
return 'Next step';
})
.then(next => {
console.log(next);
})
.catch(error => {
console.error('Error:', error);
})
.finally(() => {
console.log('Cleanup');
});
|
Promise Combinators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve) => {
setTimeout(resolve, 100, 'foo');
});
// Wait for all promises (fails if any rejects)
Promise.all([promise1, promise2, promise3])
.then(values => console.log(values)); // [3, 42, 'foo']
// Wait for all promises (never rejects)
Promise.allSettled([promise1, promise2, promise3])
.then(results => console.log(results));
// [
// { status: 'fulfilled', value: 3 },
// { status: 'fulfilled', value: 42 },
// { status: 'fulfilled', value: 'foo' }
// ]
// First fulfilled promise
Promise.race([promise1, promise2, promise3])
.then(value => console.log(value)); // 3
// First settled promise (fulfilled or rejected)
Promise.any([promise1, promise2, promise3])
.then(value => console.log(value)); // 3
|
Async/Await
Syntactic sugar for working with Promises:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // β
Clean, readable async code
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
return { user, posts };
} catch (error) {
console.error('Failed to fetch user data:', error);
throw error;
}
}
// Using the async function
fetchUserData(123)
.then(data => console.log(data))
.catch(error => console.error(error));
|
Parallel Execution with Async/Await
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // β Sequential (slower)
async function sequential() {
const user = await fetchUser(); // 2s
const posts = await fetchPosts(); // 2s
return { user, posts }; // Total: 4s
}
// β
Parallel (faster)
async function parallel() {
const [user, posts] = await Promise.all([
fetchUser(), // Both start simultaneously
fetchPosts()
]);
return { user, posts }; // Total: ~2s
}
// β
Sequential with conditional logic
async function conditionalSequential(userId) {
const user = await fetchUser(userId);
// Only fetch posts if user is premium
if (user.isPremium) {
const posts = await fetchPosts(userId);
return { user, posts };
}
return { user };
}
|
Error Handling Patterns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| // Pattern 1: Try-catch within async function
async function handleErrors1() {
try {
const data = await riskyOperation();
return data;
} catch (error) {
console.error('Operation failed:', error);
return null; // Provide fallback
}
}
// Pattern 2: Catch at call site
async function handleErrors2() {
return await riskyOperation(); // Let error propagate
}
handleErrors2()
.catch(error => {
console.error('Handled at call site:', error);
});
// Pattern 3: Multiple operations with specific error handling
async function handleErrors3() {
try {
const user = await fetchUser().catch(error => {
console.error('User fetch failed:', error);
return null; // Provide default
});
const posts = await fetchPosts().catch(error => {
console.error('Posts fetch failed:', error);
return []; // Provide default
});
return { user, posts };
} catch (error) {
// This catches errors not handled by individual catches
console.error('Unexpected error:', error);
throw error;
}
}
|
Module Systems
ES6 Modules (ESM)
Modern standard for JavaScript modules:
Named Exports
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // utils.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
multiply(a, b) {
return a * b;
}
}
// Alternative syntax
const subtract = (a, b) => a - b;
const divide = (a, b) => a / b;
export { subtract, divide };
|
Default Exports
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // logger.js
export default class Logger {
log(message) {
console.log(`[LOG] ${message}`);
}
}
// Or
class Logger {
log(message) {
console.log(`[LOG] ${message}`);
}
}
export default Logger;
|
Importing Modules
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // Named imports
import { add, Calculator } from './utils.js';
import { subtract as minus } from './utils.js'; // Renaming
// Import all as namespace
import * as Utils from './utils.js';
console.log(Utils.PI);
// Default import
import Logger from './logger.js';
// Mixed imports
import Logger, { someUtil } from './mixed.js';
// Import for side effects only
import './initialize.js';
|
Dynamic Imports
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // Load modules conditionally or on-demand
async function loadModule(condition) {
if (condition) {
const module = await import('./heavy-module.js');
module.initialize();
}
}
// With error handling
button.addEventListener('click', async () => {
try {
const { processData } = await import('./data-processor.js');
processData();
} catch (error) {
console.error('Failed to load module:', error);
}
});
|
CommonJS (Node.js)
Traditional Node.js module system:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // exporting.js
const config = { api: 'https://api.example.com' };
function fetchData() {
// implementation
}
module.exports = {
config,
fetchData
};
// Alternative single export
module.exports = class Service {
// implementation
};
// importing.js
const { config, fetchData } = require('./exporting');
const Service = require('./service');
|
Module Best Practices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| // β
Organize exports logically
// api/index.js
export { fetchUser, fetchPosts } from './users.js';
export { authenticate, logout } from './auth.js';
// β
Use barrel exports for cleaner imports
// components/index.js
export { Button } from './Button.js';
export { Input } from './Input.js';
export { Modal } from './Modal.js';
// Usage
import { Button, Input, Modal } from './components';
// β
Avoid circular dependencies
// file-a.js
import { funcB } from './file-b.js';
export const funcA = () => { /* uses funcB */ };
// file-b.js (circular!)
import { funcA } from './file-a.js'; // β Circular dependency
export const funcB = () => { /* uses funcA */ };
// β
Solution: Extract shared code
// shared.js
export const sharedLogic = () => { };
// file-a.js
import { sharedLogic } from './shared.js';
export const funcA = () => { /* uses sharedLogic */ };
// file-b.js
import { sharedLogic } from './shared.js';
export const funcB = () => { /* uses sharedLogic */ };
|
Error Handling
Try-Catch-Finally
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| function processData(data) {
try {
if (!data) {
throw new TypeError('Data is required');
}
const parsed = JSON.parse(data);
return parsed;
} catch (error) {
if (error instanceof SyntaxError) {
console.error('Invalid JSON:', error.message);
} else if (error instanceof TypeError) {
console.error('Type error:', error.message);
} else {
console.error('Unexpected error:', error);
}
return null;
} finally {
// Always executes, useful for cleanup
console.log('Processing complete');
}
}
|
Custom Error Classes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
// Maintains proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ValidationError);
}
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError('Email is required', 'email');
}
if (!user.email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
}
try {
validateUser({ name: 'John' });
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Validation failed for ${error.field}: ${error.message}`);
}
}
|
Async Error Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| // β
Proper async error handling
async function fetchWithErrorHandling(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new NetworkError(
`Request failed: ${response.statusText}`,
response.status
);
}
return await response.json();
} catch (error) {
if (error instanceof NetworkError) {
console.error(`Network error (${error.statusCode}):`, error.message);
// Implement retry logic for certain status codes
if (error.statusCode === 429) {
console.log('Rate limited, retrying...');
await new Promise(resolve => setTimeout(resolve, 1000));
return fetchWithErrorHandling(url); // Retry
}
} else if (error instanceof TypeError) {
console.error('Network failure:', error.message);
}
throw error; // Re-throw if unhandled
}
}
// Global unhandled rejection handler
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Promise Rejection:', reason);
// Log to error tracking service
});
// Browser equivalent
window.addEventListener('unhandledrejection', event => {
console.error('Unhandled Promise Rejection:', event.reason);
event.preventDefault(); // Prevents default error logging
});
|
Error Handling Best Practices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| // β
Fail fast with meaningful errors
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Both arguments must be numbers');
}
if (b === 0) {
throw new RangeError('Division by zero');
}
return a / b;
}
// β
Use error boundaries in appropriate contexts
class ErrorBoundary {
constructor() {
this.errors = [];
}
wrap(fn) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
this.errors.push({
error,
timestamp: new Date(),
context: { fn: fn.name, args }
});
throw error;
}
};
}
getErrors() {
return this.errors;
}
}
// β
Provide context in errors
function processUserData(userId) {
try {
// ... processing
} catch (error) {
throw new Error(`Failed to process user ${userId}: ${error.message}`);
}
}
|
Memory Management
JavaScript uses automatic memory management through garbage collection, but understanding memory patterns helps prevent leaks and optimize performance.
Memory Lifecycle
- Allocation: Memory is allocated when values are created
- Usage: Reading and writing to allocated memory
- Release: Memory is freed when no longer needed (garbage collection)
Garbage Collection
JavaScript engines use mark-and-sweep algorithms:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Example: Reachability
let user = { name: 'John' }; // Object is reachable
user = null; // Object becomes unreachable and eligible for GC
// Example: Circular references (handled automatically)
function createCircular() {
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1; // Circular reference
return obj1;
}
let circular = createCircular();
circular = null; // Both objects become unreachable and will be GC'd
|
Common Memory Leaks
1. Forgotten Event Listeners
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| // β Memory leak
class Component {
constructor() {
document.addEventListener('click', this.handleClick);
}
handleClick() {
console.log('Clicked');
}
// Missing cleanup!
}
// β
Proper cleanup
class Component {
constructor() {
this.handleClick = this.handleClick.bind(this);
document.addEventListener('click', this.handleClick);
}
handleClick() {
console.log('Clicked');
}
destroy() {
document.removeEventListener('click', this.handleClick);
}
}
// β
Alternative: Use AbortController (modern approach)
class ModernComponent {
constructor() {
this.controller = new AbortController();
document.addEventListener('click', this.handleClick, {
signal: this.controller.signal
});
}
handleClick() {
console.log('Clicked');
}
destroy() {
this.controller.abort(); // Removes all listeners
}
}
|
2. Detached DOM Elements
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // β Memory leak
const elements = [];
function addElements() {
const div = document.createElement('div');
document.body.appendChild(div);
elements.push(div); // Reference kept after removal
}
function removeElements() {
document.body.innerHTML = ''; // Removes from DOM but elements array still references
}
// β
Proper cleanup
function cleanupElement(element) {
// Remove event listeners
const clone = element.cloneNode(true);
element.parentNode.replaceChild(clone, element);
// Clear reference
element = null;
}
|
3. Unmanaged Caches
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
| // β Unbounded cache growth
const cache = {};
function cacheData(key, value) {
cache[key] = value;
}
// β
Bounded cache with LRU
class LRUCache {
constructor(maxSize = 100) {
this.maxSize = maxSize;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return undefined;
// Move to end (most recently used)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
// Remove if exists (will re-add at end)
if (this.cache.has(key)) {
this.cache.delete(key);
}
// Remove oldest if at capacity
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
// β
Use WeakMap for object keys (auto garbage collection)
const weakCache = new WeakMap();
function cacheObjectData(obj, data) {
weakCache.set(obj, data);
// When obj is garbage collected, entry is automatically removed
}
|
4. Timers and Intervals
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // β Memory leak
function startTimer() {
const largeData = new Array(1000000);
setInterval(() => {
console.log(largeData.length); // Retains largeData forever
}, 1000);
}
// β
Clear timers and avoid closures over large data
function startTimerProperly() {
const dataSize = new Array(1000000).length; // Only store size
const timerId = setInterval(() => {
console.log(dataSize);
}, 1000);
// Return cleanup function
return () => clearInterval(timerId);
}
const cleanup = startTimerProperly();
// Later:
cleanup();
|
Memory Profiling Tips
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Check memory usage (Node.js)
if (typeof process !== 'undefined' && process.memoryUsage) {
console.log(process.memoryUsage());
// {
// rss: resident set size (total memory)
// heapTotal: total heap allocated
// heapUsed: actual memory used
// external: C++ objects bound to JS
// }
}
// Browser memory info (Chrome)
if (performance.memory) {
console.log({
usedJSHeapSize: performance.memory.usedJSHeapSize,
totalJSHeapSize: performance.memory.totalJSHeapSize,
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
});
}
|
Code-Level Optimizations
1. Avoid Unnecessary Computations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // β Repeated computation
function processItems(items) {
for (let i = 0; i < items.length; i++) {
console.log(items[i]);
}
}
// β
Cache length
function processItemsOptimized(items) {
const length = items.length;
for (let i = 0; i < length; i++) {
console.log(items[i]);
}
}
// β
Use modern iterators (often optimized by engines)
function processItemsModern(items) {
for (const item of items) {
console.log(item);
}
}
|
2. Debouncing and Throttling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| // Debouncing: Execute after inactivity period
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage
const searchInput = document.querySelector('#search');
const debouncedSearch = debounce((value) => {
console.log('Searching for:', value);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// Throttling: Execute at most once per interval
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Usage
const throttledScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', throttledScroll);
|
3. Lazy Loading and Code Splitting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // β
Dynamic import for code splitting
async function loadFeature() {
const module = await import('./heavy-feature.js');
module.initialize();
}
// β
Lazy initialization
class ExpensiveService {
constructor() {
this._connection = null;
}
get connection() {
if (!this._connection) {
this._connection = this.initializeConnection();
}
return this._connection;
}
initializeConnection() {
// Expensive initialization
return { /* connection object */ };
}
}
|
4. Efficient DOM Manipulation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| // β Multiple reflows
function inefficientDOMUpdate(items) {
const list = document.querySelector('#list');
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
list.appendChild(li); // Reflow for each append!
});
}
// β
Batch updates using DocumentFragment
function efficientDOMUpdate(items) {
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
document.querySelector('#list').appendChild(fragment); // Single reflow
}
// β
Use innerHTML for large batches (faster parsing)
function veryEfficientDOMUpdate(items) {
const html = items.map(item => `<li>${item}</li>`).join('');
document.querySelector('#list').innerHTML = html;
}
|
5. Optimize Loops
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // β Inefficient
const arr = [1, 2, 3, 4, 5];
const doubled = [];
for (let i = 0; i < arr.length; i++) {
doubled.push(arr[i] * 2);
}
// β
More efficient with built-in methods
const doubled = arr.map(x => x * 2);
// β
For filtering + mapping, use reduce
const evenDoubled = arr.reduce((acc, x) => {
if (x % 2 === 0) {
acc.push(x * 2);
}
return acc;
}, []);
// Or chain (might be less efficient for large arrays)
const evenDoubled2 = arr
.filter(x => x % 2 === 0)
.map(x => x * 2);
|
6. Use Appropriate Data Structures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // β Array for lookups (O(n))
const userArray = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
function findUser(id) {
return userArray.find(u => u.id === id); // O(n)
}
// β
Map for lookups (O(1))
const userMap = new Map([
[1, { id: 1, name: 'Alice' }],
[2, { id: 2, name: 'Bob' }]
]);
function findUserFast(id) {
return userMap.get(id); // O(1)
}
// β
Set for uniqueness checks (O(1))
const seenIds = new Set();
function hasSeen(id) {
if (seenIds.has(id)) return true; // O(1)
seenIds.add(id);
return false;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // Performance timing
const t0 = performance.now();
expensiveOperation();
const t1 = performance.now();
console.log(`Operation took ${t1 - t0}ms`);
// Performance markers
performance.mark('start-operation');
await fetchData();
performance.mark('end-operation');
performance.measure('operation', 'start-operation', 'end-operation');
// Get measurements
const measures = performance.getEntriesByType('measure');
console.log(measures[0].duration);
// Resource timing
performance.getEntriesByType('resource').forEach(resource => {
console.log(`${resource.name}: ${resource.duration}ms`);
});
|
Best Practices
Code Organization
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // β
Single Responsibility Principle
class UserService {
async getUser(id) {
return await fetch(`/api/users/${id}`).then(r => r.json());
}
}
class UserValidator {
validate(user) {
if (!user.email) throw new Error('Email required');
if (!user.name) throw new Error('Name required');
return true;
}
}
// β God object (avoid)
class UserEverything {
async getUser(id) { /* ... */ }
validate(user) { /* ... */ }
sendEmail(user) { /* ... */ }
generateReport(users) { /* ... */ }
// Too many responsibilities!
}
|
Naming Conventions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // β
Descriptive variable names
const maxRetryAttempts = 3;
const userAuthenticationToken = 'abc123';
const isUserAuthenticated = true;
// β Unclear abbreviations
const mra = 3;
const uat = 'abc123';
const iua = true;
// β
Functions: verb + noun
function calculateTotalPrice(items) { }
function fetchUserData(userId) { }
function validateEmailFormat(email) { }
// β
Boolean variables: is/has/can
const isLoading = false;
const hasPermission = true;
const canEdit = false;
// β
Constants: UPPER_SNAKE_CASE
const MAX_UPLOAD_SIZE = 5 * 1024 * 1024; // 5MB
const API_BASE_URL = 'https://api.example.com';
|
Immutability Patterns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| // β
Avoid mutations
const originalArray = [1, 2, 3];
// β Mutating
originalArray.push(4);
// β
Creating new array
const newArray = [...originalArray, 4];
// β
Object immutability
const originalObj = { name: 'John', age: 30 };
// β Mutating
originalObj.age = 31;
// β
Creating new object
const updatedObj = { ...originalObj, age: 31 };
// β
Deep updates with spread
const user = {
name: 'John',
address: { city: 'NYC', zip: '10001' }
};
const updatedUser = {
...user,
address: {
...user.address,
zip: '10002'
}
};
|
Functional Programming Patterns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // β
Pure functions (no side effects)
function add(a, b) {
return a + b;
}
// β Impure function (side effect)
let total = 0;
function addToTotal(value) {
total += value; // Modifies external state
return total;
}
// β
Composition
const double = x => x * 2;
const increment = x => x + 1;
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const doubleAndIncrement = compose(increment, double);
console.log(doubleAndIncrement(5)); // 11
// β
Higher-order functions
function withLogging(fn) {
return function(...args) {
console.log(`Calling ${fn.name} with`, args);
const result = fn(...args);
console.log(`Result:`, result);
return result;
};
}
const loggedAdd = withLogging(add);
loggedAdd(2, 3); // Logs: Calling add with [2, 3], Result: 5
|
Error Handling Strategy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // β
Centralized error handling
class ApplicationError extends Error {
constructor(message, statusCode = 500, isOperational = true) {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
}
class ErrorHandler {
static handle(error) {
if (error.isOperational) {
// Expected errors: log and notify user
console.error(error.message);
this.notifyUser(error);
} else {
// Programming errors: log, alert team, possibly crash
console.error('Critical error:', error);
this.alertTeam(error);
// process.exit(1); // In Node.js
}
}
static notifyUser(error) {
// Show user-friendly message
}
static alertTeam(error) {
// Send to error tracking service
}
}
// Usage
async function handleRequest() {
try {
const data = await fetchData();
return processData(data);
} catch (error) {
ErrorHandler.handle(error);
}
}
|
Testing Considerations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| // β
Write testable code
class DataProcessor {
constructor(validator, formatter) {
this.validator = validator;
this.formatter = formatter;
}
process(data) {
if (!this.validator.isValid(data)) {
throw new Error('Invalid data');
}
return this.formatter.format(data);
}
}
// Easy to test with mocks
const mockValidator = { isValid: () => true };
const mockFormatter = { format: (d) => d.toUpperCase() };
const processor = new DataProcessor(mockValidator, mockFormatter);
// β Hard to test (tightly coupled)
class DataProcessorBad {
process(data) {
// Directly instantiates dependencies
const validator = new RealValidator();
const formatter = new RealFormatter();
if (!validator.isValid(data)) {
throw new Error('Invalid data');
}
return formatter.format(data);
}
}
|
Security Best Practices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // β
Sanitize user input
function sanitizeHTML(input) {
const div = document.createElement('div');
div.textContent = input; // Automatically escapes
return div.innerHTML;
}
// β
Use Content Security Policy headers
// Set in server or meta tag:
// <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
// β
Avoid eval and similar constructs
// β Never do this
const userCode = getUserInput();
eval(userCode); // Dangerous!
// β
Use safe alternatives
const allowedFunctions = {
add: (a, b) => a + b,
multiply: (a, b) => a * b
};
function executeSafely(functionName, ...args) {
const fn = allowedFunctions[functionName];
if (fn) {
return fn(...args);
}
throw new Error('Function not allowed');
}
// β
Validate and sanitize data
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
// β
Use secure random values
// β Math.random() is not cryptographically secure
const insecureRandom = Math.random();
// β
Use crypto API
const secureRandom = crypto.getRandomValues(new Uint32Array(1))[0];
|
Jargon Reference Tables
Table 1: JavaScript Lifecycle Terminology
Different communities and contexts use various terms for similar concepts in JavaScript development:
| Concept | Alternative Terms | Description | Context |
|---|
| Runtime Execution | Event Loop Cycle, Execution Context | The process of JavaScript code execution | Engine internals |
| Asynchronous Resolution | Promise Settlement, Async Completion | When an async operation completes | Promises/Async |
| Memory Cleanup | Garbage Collection, GC Cycle, Mark-and-Sweep | Automatic memory deallocation | Memory management |
| Module Loading | Import Resolution, Dependency Loading | Process of loading external code | Module systems |
| Function Invocation | Function Call, Function Execution | Running a function | General programming |
| Scope Creation | Execution Context Creation, Lexical Environment Setup | Creating variable accessibility regions | Scoping |
| Prototype Chain Lookup | Inheritance Resolution, Prototype Traversal | Finding properties through inheritance | OOP in JS |
| Hoisting | Declaration Lifting, Variable Raising | Moving declarations to top of scope | Compilation phase |
| Closure Creation | Lexical Binding, Scope Capture | Function retaining outer scope access | Functions |
| Event Propagation | Event Bubbling/Capturing, Event Flow | How events traverse DOM | DOM events |
Table 2: Hierarchical Terminology Structure
This table organizes JavaScript terminology by abstraction level and domain:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| JavaScript Execution Hierarchy
β
βββ Language Specification Level
β βββ ECMAScript (formal specification)
β βββ TC39 Proposals (feature proposals)
β βββ Language Grammar (syntax rules)
β
βββ Engine Implementation Level
β βββ Parser (code β AST)
β βββ Interpreter (bytecode execution)
β βββ JIT Compiler (optimization)
β βββ Garbage Collector (memory management)
β
βββ Runtime Environment Level
β βββ Call Stack (synchronous execution)
β βββ Event Loop (async coordination)
β βββ Task Queues
β β βββ Microtask Queue (Promises, queueMicrotask)
β β βββ Macrotask Queue (setTimeout, I/O, UI)
β βββ Web APIs / Node APIs (browser/server features)
β
βββ Memory Management Level
β βββ Stack Memory (primitives, references)
β βββ Heap Memory (objects, closures)
β βββ Garbage Collection
β β βββ Mark Phase (identify reachable)
β β βββ Sweep Phase (reclaim unreachable)
β βββ Memory Leaks (unintentional retention)
β
βββ Code Organization Level
β βββ Modules
β β βββ ES Modules (import/export)
β β βββ CommonJS (require/module.exports)
β βββ Scopes
β β βββ Global Scope
β β βββ Function Scope (var)
β β βββ Block Scope (let/const)
β β βββ Module Scope
β βββ Closures (lexical scope retention)
β
βββ Asynchronous Patterns Level
β βββ Callbacks (basic async)
β βββ Promises (chainable async)
β β βββ Pending State
β β βββ Fulfilled State
β β βββ Rejected State
β βββ Async/Await (synchronous-style async)
β βββ Generators/Iterators (pauseable functions)
β
βββ Object Model Level
β βββ Prototype Chain (inheritance)
β βββ Constructor Functions (object creation)
β βββ Classes (syntactic sugar)
β βββ Property Descriptors (property metadata)
β
βββ Type System Level
βββ Primitive Types
β βββ Number, String, Boolean
β βββ Null, Undefined
β βββ Symbol
β βββ BigInt
βββ Reference Types
βββ Object
βββ Array
βββ Function
βββ Built-in Objects (Date, RegExp, etc.)
|
Table 3: Async Terminology Equivalents
| JavaScript Term | Equivalent in Other Languages | Description |
|---|
| Promise | Future (Scala, Java), Task (C#), Deferred (jQuery) | Represents eventual completion of async operation |
| async/await | async/await (C#, Python), suspend (Kotlin) | Syntactic sugar for promises |
| Callback | Completion Handler (Swift), Callback (C++), Lambda (Java) | Function passed to be called later |
| Event Loop | Run Loop (iOS), Message Loop (Windows) | Mechanism for handling async operations |
| Microtask | Job (Promise spec), Next Tick (Node.js) | High-priority async task |
| Macrotask | Task (WHATWG), Timeout (general) | Standard async task |
| Promise.all | Task.WhenAll (C#), Future.sequence (Scala) | Wait for multiple async operations |
| Promise.race | Task.WhenAny (C#), Future.firstCompletedOf (Scala) | First completed async operation |
Table 4: Module System Terminology
| Concept | CommonJS | ES Modules | Description |
|---|
| Import | require() | import | Loading external code |
| Export | module.exports, exports | export, export default | Exposing code |
| Loading | Synchronous | Asynchronous | When module code executes |
| Binding | Copy (value) | Live (reference) | How exported values behave |
| Circular Deps | Partial execution | Error-prone | Handling circular references |
| File Extension | .js, .cjs | .js, .mjs | File type indicators |
| Environment | Node.js primary | Browser + modern Node.js | Where primarily used |
Table 5: Error Handling Terminology
| General Term | JavaScript | Description | Usage |
|---|
| Exception | Error | Object representing problem | throw new Error() |
| Try Block | try | Code that might fail | try { } |
| Catch Block | catch | Error handler | catch (error) { } |
| Finally Block | finally | Always-executed cleanup | finally { } |
| Stack Trace | Error.stack | Call chain at error | Debugging |
| Error Boundary | try-catch wrapper | Isolates error impact | Error containment |
| Rejection | Promise rejection | Async error | .catch(), try-await |
| Unhandled Rejection | Uncaught promise error | Missed async error | unhandledrejection event |
References
The following sources were consulted and validated for this comprehensive guide:
- ECMAScript 2024 Language Specification
- MDN Web Docs - JavaScript
- TC39 GitHub - Proposals
- V8 JavaScript Engine Blog
- Node.js Documentation
- JavaScript.info - Modern JavaScript Tutorial
- Web.dev - JavaScript Performance
- ECMA International - ECMAScript Standards
- Chrome DevTools Documentation
- Can I Use - Browser Compatibility Tables
Last Updated: November 29, 2024