This article is a continuation of the ES6 article published in the January edition of the DNC Magazine. In the first part, we saw some improvements to the JavaScript language coming up in ES6. This article will focus on new object types as well as updated APIs of existing objects in the language.
As mentioned in the introduction of the first article ECMAScript 6 – New language improvements in JavaScript, ES6 is designed to fit JavaScript better for writing larger applications. To do so, the language designers added a number of new features that got inspired from typed substitutes of JavaScript and also from a number of other libraries, including some server-side libraries. Following are the new and updated objects at a glance:
- New data structures that make it easier to store unique values (Sets) or, key-value pairs with unique keys (Maps)
- Existing objects like Math and Number have got new capabilities to perform more operations as well as perform existing operations in a better way
- String got some new functions to make parsing easier
- The type Object got functions to assign an object and compare two objects
- New functions on Array now makes it handy to find an entry, an index and to copy items inside the array
- New Proxy object to extend functionality of an existing object or function.
Also Read: Modules in ECMAScript 6 (ES6)
Platform Support for ES6 APIs
These APIs are not currently supported completely by all platforms. Latest versions of Chrome and Opera don’t support some of the new functions on Strings and they don’t support Proxy objects at all. Compilers like traceur and 6to5 also don’t have polyfills for some of these APIs. I used Firefox nightly to test all the samples. You would still need to compile the scripts using traceur, as some of the scripts use the new syntax of ES6 for arrow functions and short hand functions.
Now that you got a brief idea on the API updates in ES6, let’s start exploring each of them.
Set and WeakSet
Set
Set is a collection of distinct values of any JavaScript type (viz Number, Boolean, String, Object, etc.). It ignores any attempt made to insert a duplicate value. Sets are iterable; meaning we can loop over sets using for…of loop.
We can create a new instance of Set by calling constructor of Set type as follows:
var mySet = new Set(listOfItems);
..where listOfItems is an optional parameter, containing an iterable list of items to be inserted into the set. If not passed, an empty set will be created.
Following is an example of a set object:
var setOfObjects = new Set([17, 19, 38, 82, 17]);
We can loop over the Set object using a for…of loop, as Set is an iterable object. Following loop prints the values stored inside the Set:
for(let item of setOfObjects){
console.log(item);
}
Check the output of this loop; it will not show duplicate entries added to the set. In our example, ‘17’ is stored only once. Internally Set uses SameValueZero(x,y) to ignore duplicate entries.
Now let’s explore some methods provided by the Set API.
Adding Items
Items can be added to the Set using set.add() method.
setOfObjects.add(4);
setOfObjects.add(45);
setOfObjects.add(18);
setOfObjects.add(45);
The second attempt to insert 45 into the Set would be unsuccessful, as the Set already contains the value.
Verifying Presence of an Object
The has() method on the Set checks if the Set contains the object passed. The object is compared by its reference and not by value. Following example illustrates this:
var obj={value:100};
setOfObjects.add(obj);
console.log(setOfObjects.has(obj)); //true
console.log(setOfObjects.has({prop:100})); //false
Removing Objects
Objects stored in the Set can be deleted either by their reference using the delete() method or, by clearing all values using clear() method. Following are some examples:
setOfObjects.delete(obj);
setOfObjects.clear();
Size of Set
The size property on Set holds the number of objects currently present in it.
console.log(setOfObjects.size);
Looping over Set
As mentioned earlier, a Set can be iterated using regular for…of loop. In addition to this, there are some other ways to iterate or, loop over the set. They are listed here: (* indicates that the methods return iterators)
*entries(): Returns an iterator containing key-value pair objects. Since they are same in case of Sets, each entry is an array with the corresponding value repeated in it
for(let item of setOfObjects.entries()){
console.log(item);
}
*values(): Returns an iterator to iterate over values in the Set
for(let item of setOfObjects.values()){
console.log(item);
}
*keys():Returns an iterator to iterate over keys in the Set. As keys and values are same in Sets, the keys method produces same result as values does
for(let item of setOfObjects.keys()){
console.log(item.);
}
forEach(callback): It is another way of looping over entries in the Set. Callback function is called for every entry in the Set
setOfObjects.forEach(item => console.log(item));
WeakSet
WeakSet is the weak counterpart of Set. A WeakSet doesn’t prevent a value inserted into it from being garbage collected. The way it works is similar to Set, with the following exceptions:
- Can contain objects only. Numbers, Strings, Booleans, nulls and undefined values cannot be added into a WeakSet
- There is no way to iterate or loop over the values in a WeakSet, which means, methods like values(), entries() and forEach() are not available in WeakSet
- The set of operations one can perform over a WeakSet are: add, has and delete. These methods work the same way as they do in Set
The reason behind the name WeakSet is, they don’t prevent a value stored in them from being garbage collected.
Because of the restrictive nature of WeakSet, there are very few use cases where it can be used.
Map and WeakMap
Map
Maps are objects that are key-value pairs; both key and value can be any JavaScript object or value. Keys have to be unique in a given Map. Like Sets, Maps are iterable.
A new Map object can be created using constructor of the Map type as follows:
var myDictionary = new Map(...arguments);
Passing arguments to the Map constructor is optional. If passed, Map will be created with the set of values passed in; otherwise the Map object will not have any entries in it.
Following snippet shows how to create a Map with a set of objects:
var myDictionary = new Map([["key1", "value1"], ["key2", "value2"]]);
As the dictionaries are iterable, we can loop over the items using for…of loop.
for(let dictionaryEntry of myDictionary){
console.log(dictionaryEntry);
}
Map provides a set of methods to interact with it. Let’s explore them.
Adding Items
New entries can be added to an existing Map using the set() method. This method checks if the key passed to it already exists in the Map. It adds the entry if the key is not found; otherwise discards the entry.
Following snippet adds some more entries to the Map created above:
myDictionary.set("key3", "value4");
myDictionary.set("key2", "value5");
var obj = {id: "1"};
myDictionary.set(obj, 1000);
myDictionary.set(obj, 1900);
An attempt to insert another entry with key assigned as key2 as well as a second attempt to add an entry with obj as the key will be discarded as they already exist in myDictionary.
The keys are checked by reference, not by value. So, the following snippet will add an entry to myDictionary.
myDictionary.set({id: "1"}, 826);
Getting a Value by Key
It is possible to extract a value from a Map using get() method, if the key is known. If the key is not found in the Map, the method returns undefined.
Checking if a Key Exists
We can check if a key is already added to the Map using the has() method. Like in the case of Sets, the has() method checks for occurrence of the key by reference.
console.log(myDictionary.has("key2")); //true
console.log(myDictionary.has(obj)); //true
console.log(myDictionary.has({id: "1"})); //false
Getting a Value by Key
It is possible to extract a value from a Map using get method, if the key is known. If the key is not found in the Map, the method returns undefined.
console.log(myDictionary.get("key2")); //value2
console.log(myDictionary.get("key2ii")); //undefined
Removing Objects
The entries in a Map object can be either removed one by one using delete method, or all entries can be removed at once using the clear method. The delete method takes key as the input and returns ‘true’ if the key is found and the entry is deleted; otherwise it returns ‘false’.
Following are some examples of calling delete and clear methods:
console.log(myDictionary.delete({prop: 2000})); //false
console.log(myDictionary.delete(obj)); //true
console.log(myDictionary.delete("key1")); //true
myDictionary.clear();
Size of Map
The size property on Map holds the number of objects currently present on it.
console.log(myDictionary.size);
Looping over Map
As mentioned earlier, Maps can be iterated using regular for…of loop. In addition to this, there are some other ways to iterate or, loop over the keys and values of the Map. They are listed below: (*indicates the methods return iterators)
*entries(): Returns an iterator containing key-value pair objects. Each entry is an array of length 2 with the first item being key and the second item being the value.
for(let item of myDictionary.entries()){
console.log(item);
}
*values(): Returns an iterator to iterate over values in the Map
for(let item of myDictionary.values()){
console.log(item);
}
*keys():Returns an iterator to iterate over keys in the Map
for(let item of myDictionary.keys()){
console.log(item);
}
forEach(callback): It is another way of looping over keys in the Map. Callback function is called for every key.
myDictionary.forEach(item => console.log(item));
WeakMap
WeakMap works similar to the way Map works, with a few exceptions. The exceptions are identical to the exceptions in case of WeakSet. WeakMaps don’t restrict the objects used as keys from being garbage collected. Following are the list of characteristics of a WeakMap:
- Keys can only be objects; they cannot be of value types. Values can be of any type
- They don’t support iteration over the items. So, for…of loop cannot be used over the items in a WeakMap and the methods entries, values and keys are not supported
The set of operations supported are: set, get, has and delete. Behaviour of these operations is same as their behaviour in case of Maps.
Numbers
Some of the global number functions like parseInt, parseFloat have been moved to the Number object and now the language has also got better support for dealing with different number systems. Let’s see these changes in action.
Number Systems
ES6 defines an explicit way to work with Octal and Binary number systems. Now it is easier to represent these numbers and also to convert the values between these number systems and Decimal number system.
All octal numbers have to be prefixed with “0o”. It is also possible to convert a string following this format, to a number using the Number object. Following are some examples:
var octal = 0o16;
console.log(octal); //output: 14
var octalFromString = Number("0o20");
console.log(octalFromString); //output: 16
Similarly, any binary number has to be prefixed with “0b”. They can also be converted from string using the Number object.
var binary = 0b1100;
console.log(binary); //output: 12
var binaryFromString = Number("0b11010");
console.log(binaryFromString); //output: 26
parseInt and parseFloat
Now the parseInt and parseFloat functions are made available through Number object. Using these functions through the Number object is more explicit. They work the same way as they used to earlier.
console.log(Number.parseInt("182"));
console.log(Number.parseFloat("817.12"));
isNaN
Now we can check if an expression is a valid number using isNaN method of Number. The difference between the global isNaN function and the Number.isNaN is, this method converts value to number before checking if it is a number. Following are some examples:
console.log(Number.isNaN("10")); //false as “10” is converted to the number 10 which is not NaN
console.log(Number.isNaN(10)); //false
"NaN" means "this value is a numeric Not-a-Number value according to IEEE-754"
Fyi, another way to determine if the value is of the number type is by using typeof().
isFinite
This method finds out if a value is a finite number. It tries to convert the value to number before checking if it is finite. Following are some examples of using this method:
console.log(Number.isFinite("10")); //false
console.log(Number.isFinite("x19")); //false
isInteger
This method checks out if a value is a valid integer. It doesn’t convert the value to number before checking if it is an integer. Following are some examples of using this method:
console.log(Number.isInteger("10")); //false
console.log(Number.isInteger(19)); //true
Constants
The Number API now includes 2 constant values:
- EPSILON (Smallest possible fractional number). Its value is 2.220446049250313e-16
- MAX_INTEGER (Largest possible number). Its value is 1.7976931348623157e+308
Math
The new methods added to Math object finds logarithmic values, contains hyperbolic trigonometric functions and a few other utility functions. Following is the list of methods added to Math.
Logarithmic Functions
- log10: Calculates logarithm of the value passed with base 10
- log2: Calculates logarithm of the value passed with base 2
- log1p: Calculates natural logarithms of the value passed in after incrementing the number by 1
- expm1: It does the reverse of the previous function. Raises the value of input with exponent of natural logarithm and subtracts the result by 1
Hyperbolic Trigonometric Functions
- sinh, cosh, tanh: Hyperbolic sine, cosine and tangent functions respectively
- asinh, acosh, atanh: Inverse hyperbolic sine, cosine and tangent functions respectively
Miscellaneous Functions
- hypot: Accepts two numbers and finds value of hypotenuse of a right angle triangle of which the values passed are adjacent sides of right angle
- trunc: Truncates fractional part of the value passed in
- sign: Returns sign of the value passed in. if NaN is passed, results NaN, -0 for -0, +0 for +0, -1 for any negative number and +1 for any positive number
- cbrt: Finds cubic root of the value
String
String Templating
In every JavaScript application, we use strings heavily and in many cases, we have to deal with appending values of variables with strings. Till now, we used to append variables using the plus (+) operator. At times, it is quite frustrating to deal with such cases. ES6 brings support for String Templating to address this difficulty.
If we use templates, we won’t have to deal with breaking the strings and attaching variables to them. We can continue writing the strings without breaking the quotes. To use this feature, we cannot use single or double quotes; we need to use back quotes (`). Following example shows the syntax of using templates. It appends value of a variable to a string to form path of a REST API:
var employeeId = 'E1001';
var getDepartmentApiPath = `/api/department/${employeeId}`;
console.log(getDepartmentApiPath);
You can use any number of variables in templates. Following example forms another API path using two variables:
var projectId = 'P2001';
var employeeProjectDetailsApiPath = `/api/project/${projectId}/${employeeId}`;
console.log(employeeProjectDetailsApiPath);
We can perform some simple arithmetic operations inside the templates. Following snippet shows them:
var x=20, y=10;
console.log(`${x} + ${y} = ${x+y}`);
console.log(`${x} - ${y} = ${x-y}`);
console.log(`${x} * ${y} = ${x*y}`);
console.log(`${x} / ${y} = ${x/y}`);
Utility Functions
String adds one utility function, repeat in ES6. This function repeats the string for a specified number of times and returns it. It can be called using any string.
var thisIsCool = "Cool! ";
var repeatedString = thisIsCool.repeat(4);
console.log(repeatedString);
Substring Matching Functions
ES6 adds the functions startsWith, endsWith and includes to the String’s prototype to check for occurrence of a substring inside a given string at the beginning, towards end or at any position respectively. All these functions return Boolean values. The function includes can be used to check for occurrence at a given index in the string as well. Following are examples showing usage of these functions:
startsWith():
console.log(repeatedString.startsWith("Cool! "));
console.log(repeatedString.startsWith("cool! "));
endsWith():
console.log(repeatedString.endsWith("Cool! "));
console.log(repeatedString.endsWith("Cool!"));
includes():
console.log(repeatedString.includes("Cool! "));
console.log(repeatedString.includes("Cool! ", 6));
console.log(repeatedString.includes("Cool! ", 10));
Unicode Functions
ES6 adds the function to find Unicode equivalent of characters, to convert characters to Unicode and to normalize a Unicode string using different compositions.
codePointAt(): Returns Unicode character of character at the specified position in the string
console.log(repeatedString.codePointAt(0));
fromCodePoint(): It is a static function on string. It returns character equivalent of the Unicode passed as an argument to it.
console.log(String.fromCodePoint(200));
normalize(): Returns Unicode normalization form of the string. It accepts the format to be used for normalization. If format is not passed, it uses NFC. Check documentation on MDN for more details on this function.
"c\u067e".normalize("NFKC"); //"cıe"
Array
Arrays are the most commonly used data structures in any language. ES6 brings some new utility functions to objects of Array type and also adds some static methods to Array to make searching elements, copying elements in the same Array, iterating over the elements and converting non-Array types to Array types.
Iterating Over Array
Like in case of Maps, Arrays now have the methods entries() and keys() to iterate over the values.
*entries(): Each entry returned from the entries function is an array of two elements containing a key and its corresponding value. For Arrays, keys are same as the indices.
var citiesList = ["Delhi", "Mumbai", "Kolkata", "Chennai", "Hyderabad", "Bangalore"];
for(let entry of citiesList.entries()){
console.log(entry);
}
*keys(): Keys are same as indices; so this function returns index of every item in the array.
for(let key of citiesList.keys()){
console.log(key);
}
Finding Occurrence
Arrays now have two methods, find and findIndex, that take a predicate and return the item in the Array that matches the condition checked by the predicate. For the predicate, we can pass an arrow function.
find(): Accepts a predicate and returns the first item in the array that satisfies the condition checked by the predicate.
console.log(citiesList.find( city => city.startsWith("M") ));
findIndex():Accepts a predicate and returns index of the first item in the array that satisfies the condition checked by the predicate.
console.log(citiesList.findIndex( city => city.startsWith("M") ));
Filling and Copying
It is now easy to fill the entire Array or, a part of the Array with an item and also copy a portion of the Array into rest of it.
fill(): Following is the syntax of the fill function:
arrayObject.fill(objectToFill, startIndex, endIndex);
Only first argument is mandatory. When it is called with just one argument, it fills the entire array with the value passed in.
citiesList.fill("Pune");
citiesList.fill("Hyderabad", 2);
citiesList.fill("Bangalore", 3, 5);
copyWithin(): Copies one or more elements inside the array to other positions in the array.
citiesList.copyWithin(0, 3); //elements at 0 to 2 into elements from 3 onwards
citiesList.copyWithin(0, 3, 5); //elements at 0 to 2 into elements from 3 to 5
citiesList.copyWithin(0, -3); //negative index starts from end of the array
Converting to Array
ES6 adds two static methods to Array that convert collections and stream of data into Arrays.
of(): This function takes a list of objects and returns an Array with these objects as items in it.
var citiesInUS= Array.of("New York", "Chicago", "Los Angeles", "Seattle");
From(): Used to convert Array-like data (viz., arguments of functions) into arrays.
function convertToArray(){
return Array.from(arguments);
}
var numbers = convertToArray(19, 72, 18, 71, 37, 91);
Object
Object got two new static functions in ES6 - to compare two objects and assign enumerable properties of a number of objects into one object.
is(): Accepts two objects and returns a Boolean value representing if the objects are equal
var obj = {employeeId: 100};
var obj2 = obj;
console.log(Object.is(obj, {employeeId: 100})); //false
console.log(Object.is(obj, obj2)); //true
assign(): Following is the syntax of this function:
Object.assign(target, source1, source2, …)
Assigns enumerable properties of all source objects into the target object.
var obj3 = {departmentName: "Accounts"};
var obj4 = {};
Object.assign(obj4, obj, obj3);
//contents of obj4: {employeeId: 100, departmentName: “Accounts”}
Proxy
As name of the object itself suggests, the Proxy object is used to create proxies around objects and methods. The Proxy object is very useful to perform tasks like validations before calling a function and format data of a property when its value is accessed. In my opinion, Proxies define a new way to decorate the objects in JavaScript. Let’s see it in action.
Consider the following object:
var employee={
employeeId: 'E10101',
name:"Hari",
city:"Hyderabad",
age: 28,
salary: 10000,
calculateBonus(){
return this.salary * 0.1;
}
};
Proxying Getters
Let’s format the value of this employees salary when it is accessed. For this, we need to define a proxy around getter of the object properties and format the data. Let’s define a Proxy object to do this task:
var employeeProxy = new Proxy(employee, {
get(target, property){
if(property === "salary"){
return `$ ${target[property]}`;
}
return target[property];
}
});
console.log(employeeProxy.salary);
As you can see, the get method of the proxy object takes two arguments:
· target: Object on which the getter is being redefined
· property: Name of the property to be accessed
Take another look at the snippet. I am using two ES6 features to compose it: short-hand way of defining function and string templates.
Proxying Setters
EmployeeId of an employee should be assigned only once. Any further attempt to assign a value to it should be prevented. We can do it by creating a proxy around setter on the object. Following snippet does this:
var employeeProxy = new Proxy(employee, {
set(target, property, value){
if(property === "employeeId"){
console.error("employeeId cannot be modified");
}
else{
target[property] = value;
}
}
});
employeeProxy.employeeId = "E0102"; //Logs an error in the console
Proxying Function Calls
Let us assume bonus of the employee has to be calculated only if salary of the employee is greater than $16,000. But the calculateBonus method in the above object doesn’t check this condition. Let’s define a proxy to check this condition.
employee.calculateBonus = new Proxy(employee.calculateBonus, {
apply(target, context, args){
if(context.salary < 15000){
return 0;
}
return target.apply(context, args);
}
});
console.log(employee.calculateBonus()); //Output: 0
employee.salary=16000;
console.log(employee.calculateBonus()); //Output: 1600
Conclusion
As we saw, ES6 brings a number of new APIs on existing objects and also a set of new objects and data structures that ease a lot of work. As already mentioned, some of these APIs are not supported by all platforms, as at the time of writing this article. Let’s hope to see them supported in near future. In future articles, we will explore promises and modules in ES6.
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active
blogger, an author at
SitePoint and at
DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community