Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/493aa628b9e6c073d963ac4fd6580ab3 to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/493aa628b9e6c073d963ac4fd6580ab3 to your computer and use it in GitHub Desktop.
JavaScript Advanced Features: Map, Set, WeakMap, WeakSet, Symbol & Generators

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', '[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

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(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

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++) {
  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'

Summary Comparison

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment