3 Unpopular JavaScript Concepts To Learn Today That Will Make You A Better Programmer Forever
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: 30The 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); // falseSymbols 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); // worldWhere 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 31Read More
If you found the article valuable and wish to offer a gesture of encouragement:
- Clap 50 times for this article
- Leave a comment telling me what you think
- 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:
- Be sure to clap and follow the writer️
- Learn how you can also write for In Plain English️
- Follow us: X | LinkedIn | YouTube | Discord | Newsletter
- Visit our other platforms: Stackademic | CoFeed | Venture






