DotNetCurry Logo

ECMAScript 6 – New Objects and Updates to Existing Objects

Posted by: Ravi Kiran , on 3/12/2015, in Category HTML5 & JavaScript
Views: 19019
Abstract: This article gives an overview of the API updates in EcmaScript 6 (ES6)

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)

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
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


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Joseph N. Musser II on Saturday, March 14, 2015 8:10 AM
You said that Number.isFinite tries to convert the value to a number, but Number.isFinite("10") != Number.isFinite(10).
Comment posted by Ravi on Monday, March 16, 2015 3:42 AM
Joseph, You are correct, it doesn't try to convert the value to number. I think, I was confused between isFinite and Number.isFinite. The global isFinite function tries to convert before comparing. I will get it corrected.