ES6 is more than just a Syntactic Sugar WeakMap, WeakSet, Type Conversation, Proxy

Ashok Vishwakarma
May 28 · 7 min read

ES6 as Syntactic Sugar

In computer science, syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express. It makes the language “sweeter” for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer.

— wikipedia

Which simply means its the same features in a more accessible way to developers instead of computers, which seems a good thing as in programming languages we as developers want the syntax friendly, accessible, concise and expressive by itself.

For example, if we compare two versions of a function from ES5 and ES6

// ES5 way
function test(param) {
param = param || {};
var foo = param.foo || 42;
return 'The test result is ' + foo + '.';
}
// ES6 way
// using arrow function
// destructuring of the param
// default values
// template string
const test = ({foo = 42} = {}) =>
`The test result is ${foo}.`;

If you look closely the ES6 example you immediately see its much more sorter but beyond that, it's more declarative. The default value of parameter foo is a part of the function signature rather than being written as part of the code. So if somebody looking at the function signature can immediately know what the defaults are.

The ES6 implementation is also correct where the ES5 contains a hidden bug. Can you find the bug in ES5 version of the function test? Let me know in comments.

There are more features which are no obvious as a Syntactic Sugar but they have a completely different implementation using the existing features.

ES6 classes are implemented completely on top of the prototypical mechanism that enables inheritance in JavaScript. So when you take ES6 classes and use babel to compile it and you get ES5 code which uses the prototype. Similarly, ES6 has completely new implementation for Iterators, Generators, Rest and Spread and etc.

Things harder to implement in ES5

Let’s look into the ES6 things which are harder to implement in ES5 before going into the ES6 things which you cannot implement in ES5, the very first example is the ES6 Map, check the example below:

var map = {};
var k1 = {},
k2 = {};
map[k1] = 10;
console.log(map[k1]); // 10
console.log(map[k2]); // 10
console.log(map['[object Object]']); // 10

To implement a dictionary or map in ES5 the easiest way to create an object and assign key and value to that object, but you see the above example that will have some serious issues if you want an object as a key.

The issue with using objects as the map is that all the keys are string by default so when you pass an object as a key it will be converted to its default string (‘[object Object]’) using toString method.

Now let's have a look at the code of ES6 map

const map = new Map();
const k1 = {},
k2 = {};
map.set(k1, 10);
console.log(map.get(k1)); // 10
console.log(map.get(k2)); // undefined

The ES6 Map can use string and object references as a key which solves the issue of the keys which are not strings, converted into strings. So when you set the map value with key k1, ES6 map uses its reference as a key to store the value and when you try to access the value on the key k2 it returns undefined as its not the same reference as k1

So the question is can we implement Map in ES5? The simple answer is yes

function MyMap() {
this.keys = [];
this.values = [];
}
Map.prototype = {
get: function(key) {
var index = this.keys.indexOf(key);
if(index !== -1) {
return this.values[index];
}
},
set: function(key, value) {
var index = this.keys.indexOf(key);
if(index !== -1) {
this.values[index] = value;
} else {
this.keys.push(key);
this.values.push(value);
}
},
size: function() {
return this.keys.length;
}
};

The way its implemented is using two arrays one is for keys and one for the values. So we eas time the set method is called we simply push the key and value into those arrays, as we are pushing the key and value on the same array index so when get method is called we simply find the index of the key from the keys array and return the value from the values array.

The problem with this implementation is the performance, to get any simple value on a key from the map we have iterate over the keys array to find the index which tends to have O(N) time complexity where N is the length of the keys array.

So you can implement Map using ES5 but over a significant performance tradeoff.

Things cannot be done in ES5

Let’s take another example of ES6 Map with a different scenario.

WeakMap or WeakSet

Suppose you want to add data on top of the existing object without modifying the existing ones, for example, some additional data on a dom element

var map = new MyMap();
var ele = document.getElementById('ele');
map.set(ele, {x: 10, y: 'hey'});
console.log(map.get(ele).x); // 10

Similar to the .data method from jQuery.

The problem with the above scenario is a memory leak which can be seen once we remove the element from the DOM

console.log(map.size()); // 1
ele.remove();
console.log(map.size()); // 1 not removed

In the matter of fact, the element is deleted from the DOM but its reference is present in the map which cannot be garbage collected even its value also not garbage collected. So checking the size of the map remains the same.

In order to implement a fix for this problem we have implemented a watcher on every object, variables, DOM elements, and whats not so if they get removed we can their reference in the map and remove if exists. Which clearly not a very nice implementation to have.

In ES6 this is solved using WeakMap, the WeakMap uses weak references or weakly held references and do not prevent Garbage collection in case there are no other references left for the key objects. The WeakMaps are also smart enough to know when the key object is garbage collected and no longer exists in the memory, they simply remove the values from the map and if that is the only reference for the value they also get garbage collected.

WeakSet also works similar to the WeakMap on sets instead of maps.

So WeakMap or WeakSet cannot be implemented in ES5 as JavaScript does not provide direct control over Garbage Collector.

Check out below links to read more about WeakMap and WeakSet.

Proxy

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

— MDN

Using ES6 Proxy we can manipulate the fundamental behavior of the object such as its property lookup, property or value assignments and etc. If the object is a function we can also manipulate its invocation and etc. Which means all the operation you can perform on an object ES6 gives you full control to manipulate them as per your need, also known as Metaprogramming.

const pobj = {
foo: 'hey',
'1': 'one'
};
const double = new Proxy(pobj, {
get(target, key) {
return isNaN(key) ? target[key] : 2 * key;
}
});
console.log(doble[3]); // 6
console.log(doble['1']); // one
console.log(doble['foo']); // hey

In the above example, the Proxy is being used to modify the property getter behavior for the original object pobj. The Proxy method takes two parameters the first one is the original object and the second one a function definitions for its behavior. In which I have just defined get to take control of how the property can be accessed from the object.

The proxy cannot be implemented in ES5 as JavaScriot does not support operator overloading which restricts us to overload the Square Brackets.

To read more about ES6 Proxy

Type Conversation

When it comes to converting an object to a string and etc JavaScript does not provide proper methods to do that, let's take an example

const obj = {
valueOf() { return 10;},
toString() { return 'hello';}
};
console.log('' + obj); // 10
console.log(String(obj)); // hello

So in the above example, we are trying to convert an object into a string and there are two common ways we can do that by contacting the object with a string or using global String object constructor as a function. Which looks interchangeable and many developers think that either of them will produce the same result. But they do not. Concating an object to the string will call valueOf method of the object and using String constructor will call toString method of the object which can result in two different values, similar to the above example.

Which means the object loses control for its conversation and behave differently based on the way of conversion. To accomplish the same ES6 expose the method which was internal in the JavaScript engine which is called primitive mechanism and uses Symbol.toPremitive method to access.

const obj = {
[Symbol.toPrimitive](hint){
// hint: 'default' | 'string' | 'number'

if(hint === 'number') {
return 10;
}
return null;
}
};
console.log(new Number(obj)); // [Number: 10]

Read more about Symbol.toPrimitive

There are few other ES6 features which cannot be implemented in ES5 like Tail Call Optimization and etc.


Let me know your thoughts in comments and also share with your friends, don’t forget to calp :)

Ashok Vishwakarma

Written by

@GoogleDevExpert — #WebTechnologies & @angular | #Principal #Architect at @Naukri | #Entrepreneur | #TechEnthusiast | #Speaker

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade