avatarDr. Ashish Bamania

Summary

The web content provides an in-depth exploration of three advanced JavaScript concepts—WeakMaps, Symbols, and Proxies—emphasizing their practical applications and benefits for developers looking to enhance their programming skills.

Abstract

The article titled "3 Unpopular JavaScript Concepts To Learn Today That Will Make You A Better Programmer Forever" delves into the nuances of WeakMaps, Symbols, and Proxies, which are lesser-known but powerful features in JavaScript. It explains how WeakMaps can be used for memory-efficient caching and private data storage due to their weak referencing of keys, which allows for automatic garbage collection. Symbols are introduced as a means to create unique identifiers and non-enumerable object properties, ensuring data privacy and preventing accidental type coercion. Lastly, Proxies are presented as a tool for creating objects that can intercept and redefine core object operations, enabling advanced behaviors such as input validation and debugging. The article underscores the importance of understanding these concepts for developers aiming to write more robust and maintainable code.

Opinions

  • The author suggests that mastering WeakMaps, Symbols, and Proxies can significantly improve a developer's proficiency in JavaScript, making them a better programmer.
  • WeakMaps are portrayed as particularly useful for caching and managing private data in a memory-efficient manner, which can prevent memory leaks.
  • Symbols are highlighted for their ability to provide a level of privacy and uniqueness to object properties, which is not possible with traditional keys.
  • Proxies are presented as a versatile feature that can add layers of validation and logging to objects, which can be invaluable for debugging and maintaining code integrity.
  • The article encourages active engagement with the content, suggesting readers clap, comment, and subscribe to the author's newsletters for more insights.
  • The author promotes their Substack newsletters as a resource for further learning and engagement with the software engineering community.
  • There is an implicit opinion that the JavaScript community should be more aware of and familiar with these advanced concepts to fully leverage the language's capabilities.

3 Unpopular JavaScript Concepts To Learn Today That Will Make You A Better Programmer Forever

Generated with DALL-E 3

1. WeakMaps

WeakMaps are stores of key-value pairs, where keys are objects and values are of any arbitrary JavaScript type.

A WeakMap’s keys are weakly referenced. This makes sure that if there are no other references to the key object, it can be garbage-collected.

Confused? Let’s understand this better.

In JavaScript, memory management is largely handled by a process called Garbage Collection which automatically frees up memory that is not in use by the program.

As an example, when you create a Map, JavaScript allocates memory for it. The JavaScript engine then keeps track of how many references exist to this piece of allocated memory.

As long as there’s at least one reference to that memory (a variable pointing to an object), the memory cannot be freed and reused.

When these references are removed and the reference count reaches zero, (in other words when there are no more references to the object), the garbage collector frees up this memory.

In a regular Map, both keys and values are strongly referenced.

This means that as long as the Map exists and has a reference to the key-value pairs, they won’t be garbage collected.

This is not true for WeakMaps.

In a WeakMap, the keys are weakly referenced. This means that if a key exists in a WeakMap, this does not prevent it from being garbage collected.

If we have a WeakMap, the key objects can be garbage collected if there are no other references to them outside the WeakMap. After garbage collection, these key-value pairs will automatically disappear from the WeakMap.

It is to be noted that the newest version of JS now allows Symbols (discussed next) to be used as keys for WeakMaps.

Where To Use Them?

1. Caching

WeakMaps can be used for caching results of computations associated with objects. If this key object is garbage collected, its cache entry disappears automatically and this prevents memory leaks.

function computeExpensiveData(obj) {
    // Say that this is an expensive operation
    return Object.keys(obj).length;
}

let cache = new WeakMap();

function getCachedData(obj) {
    if (cache.has(obj)) {
        console.log('Retrieving from cache');
        return cache.get(obj);
    } else {
        console.log('Computing result');
        let result = computeExpensiveData(obj);
        cache.set(obj, result);
        return result;
    }
}

let myObj = { a: 1, b: 2 };

console.log(getCachedData(myObj)); // Computing result (returns 2)
console.log(getCachedData(myObj)); // Getting the result from cache (returns 2)

The use of WeakMap allows myObj to be garbage collected if there are no other references to it.

2. To Store Private Data

WeakMaps can be used to store private data as they are not enumerable.

This makes sure that the stored data cannot be directly accessed from outside the context in which the WeakMap was defined.

let privateData = new WeakMap();

class User {
    constructor(name, age) {
        // Store private data in the WeakMap
        privateData.set(this, { name, age });
    }

    getName() {
        // Access private data from the WeakMap
        return privateData.get(this).name;
    }

    getAge() {
        // Access private data from the WeakMap
        return privateData.get(this).age;
    }
}

let user = new User('Alice', 30);

console.log(user.getName()); // Output: Alice
console.log(user.getAge()); // Output: 30

The data stored in privateData is not accessible directly from the instance of the class and can only be accessed through the class methods.

Read More

2. Symbols

These are new primitive types in ES6 that help you create unique identifiers.

const symbol = Symbol('description')

Each time we create a Symbol, it’s guaranteed to be unique (even when their description is the same).

let sym1 = Symbol("mySymbol");
let sym2 = Symbol("mySymbol");

console.log(sym1 === sym2); // false

Symbols cannot be used with for...in loops or withObject.keys().

This makes them suitable for creating non-enumerable properties on objects.

They also cannot be automatically converted to strings. This is highly useful when avoiding accidental type coercion.

Where To Use Them?

1. For Defining Unique Constants

Symbols can be used for defining constants that represent unique values as shown in the example below.

const COLOR_RED = Symbol('Red');
const COLOR_BLUE = Symbol('Blue');

2. For Creating Unique Property Keys

Symbols can be used as keys for object properties where we want these keys to be unique and non-colliding.

const uniqueKey = Symbol();

let obj = {};

obj[uniqueKey] = 'value';

Read More

3. Proxies

These are special objects that wrap over other objects.

This creates an object that can be used in place of the original object, but which may redefine fundamental Object operations like getting, setting, and defining properties.

When we create a Proxy, we provide a handler object that defines “traps” or methods for various operations. These traps are functions that will be called when certain operations are performed on the proxy.

The provided target object is the original object, we are creating a proxy for.

Let’s create a proxy for the target object.

const target = {
  message1: "hello",
  message2: "world",
};

const my_handler = {};

const my_proxy = new Proxy(target, my_handler);
console.log(my_proxy.message1); // hello
console.log(my_proxy.message2); // world

Where To Use Them?

1. Input Validation

Proxies can be used to validate inputs before assigning these as values to an object.

let user = {};

let validator = {
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value)) {
                throw new TypeError('Age must be an integer.');
            }
            if (value < 0) {
                throw new RangeError('Age must be a positive number.');
            }
        }
        obj[prop] = value;
        return true;
    }
};

let userProxy = new Proxy(user, validator);

userProxy.age = 25; // Correct usage
userProxy.age = '25'; // Throws TypeError: Age must be an integer.
userProxy.age = -1; // Throws RangeError: Age must be a positive number.

2. Debugging

Proxies can log accesses and modifications to objects, which is helpful for debugging.

let user = {
    name: 'Alice',
    age: 30
};

let loggingHandler = {
    get(target, property) {
        console.log(`Property '${property}' has been read.`);
        return target[property];
    },
    set(target, property, value) {
        console.log(`Property '${property}' changed from ${target[property]} to ${value}`);
        target[property] = value;
        return true; // indicates success
    }
};

let userProxy = new Proxy(user, loggingHandler);

console.log(userProxy.name); // Output:Property 'name' has been read. Alice
userProxy.age = 31; // Output: Property 'age' changed from 30 to 31

Read More

If you found the article valuable and wish to offer a gesture of encouragement:

  1. Clap 50 times for this article
  2. Leave a comment telling me what you think
  3. Highlight the parts in this article that you resonate with

Subscribe to my Substack newsletters below:

PlainEnglish.io 🚀

Thank you for being a part of the In Plain English community! Before you go:

Programming
JavaScript
Software Development
Technology
Coding
Recommended from ReadMedium