Map is a collection of keyed data items, similar to an Object. However, the main difference is that Map allows keys of any type, including objects, functions, and primitives. Maps maintain the insertion order of keys and provide better performance for frequent additions and removals.
Key Characteristics:
- Keys can be of any type (objects, functions, primitives)
- Maintains insertion order
- Has a size property
- Directly iterable
- Better performance for frequent additions/deletions
Constructor:
new Map()- Creates a new Mapnew Map(iterable)- Creates a Map from an iterable (array of [key, value] pairs)
Instance Methods:
map.set(key, value)- Stores the value by the key, returns the map itselfmap.get(key)- Returns the value by the key, undefined if key doesn't existmap.has(key)- Returns true if the key exists, false otherwisemap.delete(key)- Removes the element by the key, returns true if existedmap.clear()- Removes all elements from the mapmap.keys()- Returns an iterable for keysmap.values()- Returns an iterable for valuesmap.entries()- Returns an iterable for entries [key, value]map.forEach(callback)- Calls callback for each key-value pair
Properties:
map.size- Returns the number of elements
// Creating a Map
const userMap = new Map();
// Setting values
userMap.set('name', 'Alice');
userMap.set('age', 30);
userMap.set('email', '[email protected]');
// Using objects as keys
const user1 = { id: 1 };
const user2 = { id: 2 };
const roles = new Map();
roles.set(user1, 'admin');
roles.set(user2, 'user');
// Getting values
console.log(userMap.get('name')); // 'Alice'
console.log(roles.get(user1)); // 'admin'
// Checking existence
console.log(userMap.has('age')); // true
// Size
console.log(userMap.size); // 3
// Iterating
for (let [key, value] of userMap) {
console.log(`${key}: ${value}`);
}
// Using forEach
userMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Converting from array
const map = new Map([
['apple', 500],
['banana', 300],
['orange', 200]
]);
// Converting to array
const entries = [...map.entries()];
const keys = [...map.keys()];
const values = [...map.values()];
// Deleting
userMap.delete('age');
// Clearing
userMap.clear();Set is a collection of unique values. Each value may occur only once in a Set. It's useful for storing unique items and performing set operations like union, intersection, and difference.
Key Characteristics:
- Stores only unique values
- Values can be of any type
- Maintains insertion order
- Directly iterable
- Fast lookup and deletion
Constructor:
new Set()- Creates a new Setnew Set(iterable)- Creates a Set from an iterable
Instance Methods:
set.add(value)- Adds a value, returns the set itselfset.has(value)- Returns true if the value existsset.delete(value)- Removes the value, returns true if existedset.clear()- Removes all valuesset.keys()- Returns an iterable for values (same as values())set.values()- Returns an iterable for valuesset.entries()- Returns an iterable for entries [value, value]set.forEach(callback)- Calls callback for each value
Properties:
set.size- Returns the number of elements
// Creating a Set
const numbers = new Set();
// Adding values
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(2); // Duplicate, won't be added
console.log(numbers.size); // 3
// Creating from array
const uniqueNumbers = new Set([1, 2, 2, 3, 4, 4, 5]);
console.log(uniqueNumbers); // Set { 1, 2, 3, 4, 5 }
// Checking existence
console.log(numbers.has(2)); // true
// Removing duplicates from array
const array = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(array)];
console.log(unique); // [1, 2, 3, 4, 5]
// Iterating
for (let value of numbers) {
console.log(value);
}
// forEach
numbers.forEach(value => {
console.log(value);
});
// Set operations
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// Union
const union = new Set([...setA, ...setB]);
console.log(union); // Set { 1, 2, 3, 4, 5, 6 }
// Intersection
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log(intersection); // Set { 3, 4 }
// Difference
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log(difference); // Set { 1, 2 }
// Deleting
numbers.delete(2);
// Clearing
numbers.clear();WeakMap is a collection of key-value pairs where keys must be objects and are held weakly. This means if there are no other references to the key object, it can be garbage collected. WeakMap is useful for storing metadata about objects without preventing garbage collection.
Key Characteristics:
- Keys must be objects (not primitives)
- Keys are held weakly (can be garbage collected)
- Not iterable
- No size property
- No clear method
- Useful for storing private data or metadata
Constructor:
new WeakMap()- Creates a new WeakMapnew WeakMap(iterable)- Creates from an iterable of [key, value] pairs
Instance Methods:
weakMap.set(key, value)- Stores the value by the key (key must be an object)weakMap.get(key)- Returns the value by the keyweakMap.has(key)- Returns true if the key existsweakMap.delete(key)- Removes the element by the key
// Creating a WeakMap
const privateData = new WeakMap();
// Example: Storing private data for objects
class User {
constructor(name) {
this.name = name;
// Store private data in WeakMap
privateData.set(this, {
password: 'secret123',
ssn: '123-45-6789'
});
}
getPassword() {
return privateData.get(this).password;
}
}
const user = new User('Alice');
console.log(user.name); // 'Alice'
console.log(user.getPassword()); // 'secret123'
// Private data is not directly accessible
// DOM element metadata example
const elementMetadata = new WeakMap();
const button = document.createElement('button');
elementMetadata.set(button, { clicks: 0, created: Date.now() });
// When button is removed and no other references exist,
// the metadata will be garbage collected automatically
// Checking existence
console.log(elementMetadata.has(button)); // true
// Getting value
const metadata = elementMetadata.get(button);
// Deleting
elementMetadata.delete(button);
// Note: Cannot iterate over WeakMap
// No .keys(), .values(), .entries(), or .sizeWeakSet is a collection of objects held weakly. Like WeakMap, if there are no other references to an object in the WeakSet, it can be garbage collected. WeakSet is useful for tracking object membership without preventing garbage collection.
Key Characteristics:
- Can only contain objects (not primitives)
- Objects are held weakly
- Not iterable
- No size property
- No clear method
- Useful for tracking object sets without memory leaks
Constructor:
new WeakSet()- Creates a new WeakSetnew WeakSet(iterable)- Creates from an iterable of objects
Instance Methods:
weakSet.add(object)- Adds an objectweakSet.has(object)- Returns true if the object existsweakSet.delete(object)- Removes the object
// Creating a WeakSet
const visitedNodes = new WeakSet();
// Example: Marking objects without preventing garbage collection
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
function traverseTree(node) {
if (!node || visitedNodes.has(node)) {
return;
}
visitedNodes.add(node);
console.log(node.value);
traverseTree(node.left);
traverseTree(node.right);
}
const root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
traverseTree(root);
// Tracking DOM elements
const disabledElements = new WeakSet();
function disableElement(element) {
element.disabled = true;
disabledElements.add(element);
}
function isDisabled(element) {
return disabledElements.has(element);
}
const input = document.createElement('input');
disableElement(input);
console.log(isDisabled(input)); // true
// Deleting
disabledElements.delete(input);
// Note: Cannot iterate over WeakSet
// No .keys(), .values(), .entries(), or .sizeSymbol is a primitive data type that represents a unique identifier. Every Symbol value is unique, even if they have the same description. Symbols are often used as object property keys to avoid naming collisions and to create private properties.
Key Characteristics:
- Always unique (even with same description)
- Immutable primitive type
- Can be used as object property keys
- Not enumerable in for...in loops
- Not shown in Object.keys()
- Useful for creating private properties and avoiding collisions
Static Methods:
Symbol(description)- Creates a new unique symbolSymbol.for(key)- Searches for/creates a global symbolSymbol.keyFor(symbol)- Returns the key for a global symbolSymbol.iterator- Well-known symbol for default iteratorSymbol.toStringTag- Symbol for object string descriptionSymbol.hasInstance- Symbol for instanceof behaviorSymbol.toPrimitive- Symbol for type conversionSymbol.species- Symbol for derived object constructor
Instance Methods:
symbol.toString()- Returns string representationsymbol.valueOf()- Returns the symbol valuesymbol.description- Returns the description (property, not method)
// Creating symbols
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description');
console.log(sym2 === sym3); // false (each symbol is unique)
// Using symbols as object keys
const id = Symbol('id');
const user = {
name: 'Alice',
[id]: 12345
};
console.log(user[id]); // 12345
console.log(user.id); // undefined
// Symbols are not enumerable
for (let key in user) {
console.log(key); // Only logs 'name', not the symbol
}
console.log(Object.keys(user)); // ['name']
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
// Global symbol registry
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');
console.log(globalSym1 === globalSym2); // true (same global symbol)
console.log(Symbol.keyFor(globalSym1)); // 'app.id'
// Well-known symbols
const collection = {
items: [1, 2, 3],
[Symbol.iterator]: function* () {
for (let item of this.items) {
yield item;
}
}
};
for (let item of collection) {
console.log(item); // 1, 2, 3
}
// Symbol.toStringTag
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
const obj = new MyClass();
console.log(obj.toString()); // '[object MyClass]'
// Private properties pattern
const _private = Symbol('private');
class SecureClass {
constructor() {
this[_private] = 'secret data';
this.public = 'public data';
}
getPrivate() {
return this[_private];
}
}
const secure = new SecureClass();
console.log(secure.public); // 'public data'
console.log(secure[_private]); // undefined (if you don't have the symbol)
console.log(secure.getPrivate()); // 'secret data'
// Description property
const sym = Symbol('mySymbol');
console.log(sym.description); // 'mySymbol'Generator functions are special functions that can pause their execution and resume later, maintaining their context. They are defined using the function* syntax and use the yield keyword to pause execution. Generators return an iterator object that can be used to control the function's execution.
Key Characteristics:
- Defined with
function*syntax - Use
yieldto pause execution - Return an iterator object
- Maintain state between calls
- Can receive values via
next(value) - Support lazy evaluation
- Useful for creating iterators and managing asynchronous operations
Generator Function:
function* name() {}- Defines a generator functionyield value- Pauses and returns a valueyield* iterable- Delegates to another generator or iterablereturn value- Ends the generator and returns a value
Generator Object Methods:
generator.next(value)- Resumes execution and returns {value, done}generator.return(value)- Terminates the generatorgenerator.throw(error)- Throws an error into the generator
// Basic generator
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// Using in for...of loop
for (let value of simpleGenerator()) {
console.log(value); // 1, 2, 3
}
// Generator with infinite sequence
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
const infinite = infiniteSequence();
console.log(infinite.next().value); // 0
console.log(infinite.next().value); // 1
console.log(infinite.next().value); // 2
// Generator with parameters
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const numbers = [...range(1, 5)];
console.log(numbers); // [1, 2, 3, 4, 5]
// Receiving values with next()
function* conversation() {
const name = yield 'What is your name?';
const age = yield `Nice to meet you, ${name}! How old are you?`;
yield `So you are ${age} years old!`;
}
const chat = conversation();
console.log(chat.next().value); // 'What is your name?'
console.log(chat.next('Alice').value); // 'Nice to meet you, Alice! How old are you?'
console.log(chat.next(30).value); // 'So you are 30 years old!'
// Delegating with yield*
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield* generator1();
yield 3;
yield 4;
}
console.log([...generator2()]); // [1, 2, 3, 4]
// Early return
function* withReturn() {
yield 1;
yield 2;
return 3;
yield 4; // Never reached
}
const ret = withReturn();
console.log(ret.next()); // { value: 1, done: false }
console.log(ret.next()); // { value: 2, done: false }
console.log(ret.next()); // { value: 3, done: true }
// Error handling
function* errorGenerator() {
try {
yield 1;
yield 2;
yield 3;
} catch (error) {
yield `Error caught: ${error.message}`;
}
}
const errGen = errorGenerator();
console.log(errGen.next().value); // 1
console.log(errGen.throw(new Error('Something went wrong')).value);
// 'Error caught: Something went wrong'
// Practical example: ID generator
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const ids = idGenerator();
console.log(ids.next().value); // 1
console.log(ids.next().value); // 2
// Lazy evaluation for performance
function* lazyMap(iterable, fn) {
for (let item of iterable) {
yield fn(item);
}
}
function* lazyFilter(iterable, predicate) {
for (let item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
const largeArray = Array.from({ length: 1000000 }, (_, i) => i);
// This doesn't process all items immediately
const processed = lazyMap(
lazyFilter(largeArray, x => x % 2 === 0),
x => x * 2
);
// Only processes items as needed
const first5Even = [];
const iterator = processed[Symbol.iterator]();
for (let i = 0; i < 5; i++) {
first5Even.push(iterator.next().value);
}
console.log(first5Even); // [0, 4, 8, 12, 16]
// Async generator (ES2018)
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
(async () => {
for await (let value of asyncGenerator()) {
console.log(value); // 1, 2, 3
}
})();
// State machine example
function* stateMachine() {
while (true) {
const action = yield 'idle';
if (action === 'start') {
yield 'running';
yield 'finished';
} else if (action === 'pause') {
yield 'paused';
}
}
}
const machine = stateMachine();
console.log(machine.next().value); // 'idle'
console.log(machine.next('start').value); // 'running'
console.log(machine.next().value); // 'finished'
console.log(machine.next().value); // 'idle'
console.log(machine.next('pause').value); // 'paused'| Feature | Map | Set | WeakMap | WeakSet | Symbol | Generator |
|---|---|---|---|---|---|---|
| Purpose | Key-value pairs | Unique values | Weak key-value pairs | Weak object set | Unique identifiers | Pausable functions |
| Keys/Values | Any type | Any type | Objects only | Objects only | Primitive type | Yields values |
| Iterable | Yes | Yes | No | No | N/A | Yes |
| Size property | Yes | Yes | No | No | N/A | N/A |
| Garbage collection | No | No | Yes | Yes | No | No |
| Main use case | Flexible key-value storage | Unique collections | Private data | Object tracking | Property keys | Lazy evaluation |
Each of these features adds powerful capabilities to JavaScript for different use cases, from managing collections to creating unique identifiers and implementing complex control flows.