Skip to main content

JavaScript Advanced Features: Map, Set, WeakMap, WeakSet, Symbol & Generators

Table of Contentsโ€‹

  1. Map
  2. Set
  3. WeakMap
  4. WeakSet
  5. Symbol
  6. Generator Functions

Mapโ€‹

What is Map?โ€‹

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

Map Methodsโ€‹

Constructor:

  • new Map() - Creates a new Map
  • new 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 itself
  • map.get(key) - Returns the value by the key, undefined if key doesn't exist
  • map.has(key) - Returns true if the key exists, false otherwise
  • map.delete(key) - Removes the element by the key, returns true if existed
  • map.clear() - Removes all elements from the map
  • map.keys() - Returns an iterable for keys
  • map.values() - Returns an iterable for values
  • map.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

Map Examplesโ€‹

// Creating a Map
const userMap = new Map();

// Setting values
userMap.set('name', 'Alice');
userMap.set('age', 30);
userMap.set('email', 'alice@example.com');

// 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((val, key) => {
console.log(`${key}: ${val}`);
});

// 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โ€‹

What is Set?โ€‹

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

Set Methodsโ€‹

Constructor:

  • new Set() - Creates a new Set
  • new Set(iterable) - Creates a Set from an iterable

Instance Methods:

  • set.add(value) - Adds a value, returns the set itself
  • set.has(value) - Returns true if the value exists
  • set.delete(value) - Removes the value, returns true if existed
  • set.clear() - Removes all values
  • set.keys() - Returns an iterable for values (same as values())
  • set.values() - Returns an iterable for values
  • set.entries() - Returns an iterable for entries [value, value]
  • set.forEach(callback) - Calls callback for each value

Properties:

  • set.size - Returns the number of elements

Set Examplesโ€‹

// 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(val => {
console.log(val);
});

// 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โ€‹

What is WeakMap?โ€‹

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

WeakMap Methodsโ€‹

Constructor:

  • new WeakMap() - Creates a new WeakMap
  • new 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 key
  • weakMap.has(key) - Returns true if the key exists
  • weakMap.delete(key) - Removes the element by the key

WeakMap Examplesโ€‹

// 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 .size

WeakSetโ€‹

What is WeakSet?โ€‹

WeakSet 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

WeakSet Methodsโ€‹

Constructor:

  • new WeakSet() - Creates a new WeakSet
  • new WeakSet(iterable) - Creates from an iterable of objects

Instance Methods:

  • weakSet.add(object) - Adds an object
  • weakSet.has(object) - Returns true if the object exists
  • weakSet.delete(object) - Removes the object

WeakSet Examplesโ€‹

// 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 .size

Symbolโ€‹

What is Symbol?โ€‹

Symbol 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

Symbol Methodsโ€‹

Static Methods:

  • Symbol(description) - Creates a new unique symbol
  • Symbol.for(key) - Searches for/creates a global symbol
  • Symbol.keyFor(symbol) - Returns the key for a global symbol
  • Symbol.iterator - Well-known symbol for default iterator
  • Symbol.toStringTag - Symbol for object string description
  • Symbol.hasInstance - Symbol for instanceof behavior
  • Symbol.toPrimitive - Symbol for type conversion
  • Symbol.species - Symbol for derived object constructor

Instance Methods:

  • symbol.toString() - Returns string representation
  • symbol.valueOf() - Returns the symbol value
  • symbol.description - Returns the description (property, not method)

Symbol Examplesโ€‹

// 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โ€‹

What are Generators?โ€‹

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 yield to 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 Methodsโ€‹

Generator Function:

  • function* name() {} - Defines a generator function
  • yield value - Pauses and returns a value
  • yield* iterable - Delegates to another generator or iterable
  • return 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 generator
  • generator.throw(error) - Throws an error into the generator

Generator Examplesโ€‹

// 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++) {
const result = iterator.next();
first5Even.push(result.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'

Summary Comparisonโ€‹

FeatureMapSetWeakMapWeakSetSymbolGenerator
PurposeKey-value pairsUnique valuesWeak key-value pairsWeak object setUnique identifiersPausable functions
Keys/ValuesAny typeAny typeObjects onlyObjects onlyPrimitive typeYields values
IterableYesYesNoNoN/AYes
Size propertyYesYesNoNoN/AN/A
Garbage collectionNoNoYesYesNoNo
Main use caseFlexible key-value storageUnique collectionsPrivate dataObject trackingProperty keysLazy 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.