×

Understanding ECMAScript 6

Introduction

The JavaScript core language features are defined in a standard called ECMA-262. The language defined in this standard is called ECMAScript, of which the JavaScript in the browser and Node.js environments are a superset. While browsers and Node.js may add more capabilities through additional objects and methods, the core of the language remains as defined in ECMAScript, which is why the ongoing development of ECMA-262 is vital to the success of JavaScript as a whole.

In 2007, JavaScript was at a crossroads. The popularity of Ajax was ushering in a new age of dynamic web applications while JavaScript hadn’t changed since the third edition of ECMA-262 was published in 1999. TC-39, the committee responsible for driving the ECMAScript process, put together a large draft specification for ECMAScript 4. ECMAScript 4 was massive in scope, introducing changes both small and large to the language. Languages features included new syntax, modules, classes, classical inheritance, private object members, optional type annotations, and more.

The scope of the ECMAScript 4 changes caused a rift to form in TC-39, with some members feeling that the fourth edition was trying to accomplish too much. A group of leaders from Yahoo, Google, and Microsoft came up with an alternate proposal for the next version of ECMAScript that they initially called ECMAScript 3.1. The “3.1” was intended to show that this was an incremental change to the existing standard.

ECMAScript 3.1 introduced very few syntax changes, instead focusing on property attributes, native JSON support, and adding methods to already-existing objects. Although there was an early attempt to reconcile ECMAScript 3.1 and ECMAScript 4, this ultimately failed as the two camps had difficulty with the very different perspectives on how the language should grow.

In 2008, Brendan Eich, the creator of JavaScript, announced that TC-39 would focus its efforts on standardizing ECMAScript 3.1. They would table the major syntax and feature changes of ECMAScript 4 until after the next version of ECMAScript was standardized, and all members of the committee would work to bring the best pieces of ECMAScript 3.1 and 4 together after that point into an effort initially nicknamed ECMAScript Harmony.

ECMAScript 3.1 was eventually standardized as the fifth edition of ECMA-262, also described as ECMAScript 5. The committee never released an ECMAScript 4 standard to avoid confusion with the now-defunct effort of the same name. Work then began on ECMAScript Harmony, with ECMAScript 6 being the first standard released in this new “harmonious” spirit.

ECMAScript 6 reached feature complete status in 2014. The features vary widely from completely new objects and patterns to syntax changes to new methods on existing objects. The exciting thing about ECMAScript 6 is that all of these changes are geared towards problems that developers are actually facing. And while it will still take time for adoption and implementation to reach the point where ECMAScript 6 is the minimum that developers can expect, there’s a lot to be gained from a good understanding of what the future of JavaScript looks like.

Browser and Node.js Compatibility

Many JavaScript environments, such as web browsers and Node.js, are actively working on implementing ECMAScript 6. This book does not attempt to address to inconsistencies between implementations and instead focuses on what the specification defines as the correct behavior. As such, it’s possible that your JavaScript environment may not conform to the behavior described in this book.

Who This Book is For

This book is intended as a guide for those who are already familiar with JavaScript and ECMAScript 5. While a deep understanding of the language isn’t necessary to use this book, it is helpful in understanding the differences between ECMAScript 5 and 6. In particular, this book is aimed at intermediate-to-advanced JavaScript developers (both browser and Node.js environments) who want to learn about the future of the language.

This book is not for beginners who have never written JavaScript. You will need to have a good basic understanding of the language to make use of this book.

Overview

Chapter 1: The Basics introduces the smallest changes in the language. These are the new features that don’t necessarily introduce syntax changes, but rather are incremental changes on top of ECMAScript 5.

Chapter 2: Functions discusses the various changes to functions. This includes the arrow function form, default parameters, rest parameters, and more.

Chapter 3: Objects explains the changes to how objects are created, modified, and used. Topics include changes to object literal syntax, and new reflection methods.

Chapter 4: Classes introduces the first formal concept of classes in JavaScript. Often a point of confusion for those coming from other languages, the addition of class syntax in JavaScript makes the language more approachable to others and more concise for enthusiasts.

Chapter 5: Arrays details the changes to native arrays and the interesting new ways they can be used in JavaScript.

Chapter 6: Iterators and Generators discusses the addition of iterators and generators to the language. These features allow you to work with collections of data in powerful ways that were not possible in previous versions of JavaScript.

Chapter 7: Collections details the new collection types of Set, WeakSet, Map, and WeakMap. These types expand on the usefulness of arrays by adding semantics, de-duping, and memory management designed specifically for JavaScript.

Chapter 8: Symbols introduces the concept of symbols, a new way to define properties. Symbols are a new primitive type that can be used to obscure (but not hide) object properties and methods.

Chapter 9: Proxies discusses the new proxy object that allows you to intercept every operation performed on an object. Proxies give developers unprecedented control over objects and, as such, unlimited possibilities for defining new interaction patterns.

Chapter 10: Promises introduces promises as a new part of the language. Promises were a grassroots effort that eventually took off and gained in popularity due to extensive library support. ECMAScript 6 formalizes promises and makes them available by default.

Chapter 11: Modules details the official module format for JavaScript. The intent is that these modules can replace the numerous ad-hoc module definition formats that have appeared over the years.

Chapter 12: Template Strings discusses the new built-in templating functionality. Template strings are designed to easily create DSLs in a secure way.

Chapter 13: Reflection introduces the formalized reflection API for JavaScript. Similar to other languages, ECMAScript 6 reflection allows you to inspect objects at a granular level, even if you didn’t create the object.

Help and Support

You can file issues, suggest changes, and open pull requests against this book by visiting: https://github.com/nzakas/understandinges6

For anything else, please send a message to the mailing list: http://groups.google.com/group/zakasbooks.

The Basics

ECMAScript 6 makes a large number of changes on top of ECMAScript 5. Some of the changes are larger, such as adding new types or syntax, while others are quite small, providing incremental improvements on top of the language. This chapter covers those incremental improvements that likely won’t gain a lot of attention but provide some important functionality that may make certain types of problems easier to solve.

Better Unicode Support

Prior to ECMAScript 6, JavaScript strings were based solely on the idea of 16-bit character encodings. All string properties and methods, such as length and charAt(), were based around the idea that every 16-bit sequence represented a single character. ECMAScript 5 allowed JavaScript engines to decide which of two encodings to use, either UCS-2 or UTF-16 (both encoding using 16-bit code units, making all observable operations the same). While it’s true that all of the world’s characters used to fit into 16 bits at one point in time, that is no longer the case.

Keeping within 16 bits wasn’t possible for Unicode’s stated goal of providing a globally unique identifier to every character in the world. These globally unique identifiers, called code points, are simply numbers starting at 0 (you might think of these as character codes, but there is subtle difference). A character encoding is responsible for encoding a code point into code units that are internally consistent. While UCS-2 had a one-to-one mapping of code point to code unit, UTF-16 is more variable.

The first 2^16 code points are represented as single 16-bit code units in UTF-16. This is called the Basic Multilingual Plane (BMP). Everything beyond that range is considered to be in a supplementary plane, where the code points can no longer be represented in just 16-bits. UTF-16 solves this problem by introducing surrogate pairs in which a single code point is represented by two 16-bit code units. That means any single character in a string can be either one code unit (for BMP, total of 16 bits) or two (for supplementary plane characters, total of 32 bits).

ECMAScript 5 kept all operations as working on 16-bit code units, meaning that you could get unexpected results from strings containing surrogate pairs. For example:

var text = "𠮷";

console.log(text.length);           // 2
console.log(/^.$/.test(text));      // false
console.log(text.charAt(0));        // ""
console.log(text.charAt(1));        // ""
console.log(text.charCodeAt(0));    // 55362
console.log(text.charCodeAt(1));    // 57271

In this example, a single Unicode character is represented using surrogate pairs, and as such, the JavaScript string operations treat the string as having two 16-bit characters. That means length is 2, a regular expression trying to match a single character fails, and charAt() is unable to return a valid character string. The charCodeAt() method returns the appropriate 16-bit number for each code unit, but that is the closest you could get to the real value in ECMAScript 5.

ECMAScript 6 enforces encoding of strings in UTF-16. Standardizing on this character encoding means that the language can now support functionality designed to work specifically with surrogate pairs.

The codePointAt() Method

The first example of fully supporting UTF-16 is the codePointAt() method, which can be used to retrieve the Unicode code point that maps to a given character. This method accepts the character position (not the code unit position) and returns an integer value:

var text = "𠮷a";

console.log(text.codePointAt(0));   // 134071
console.log(text.codePointAt(1));   // 97

The value returned is the Unicode code point value. For BMP characters, this will be the same result as using charCodeAt(), so the "a" returns 97. This method is the easiest way to determine if a given character is represented by one or two code points:

function is32Bit(c) {
    return c.codePointAt(0) > 0xFFFF;
}

console.log(is32Bit("𠮷"));         // true
console.log(is32Bit("a"));          // false

The upper bound of 16-bit characters is represented in hexadecimal as FFFF, so any code point above that number must be represented by two code units.

String.fromCodePoint()

When ECMAScript provides a way to do something, it also tends to provide a way to do the reverse. You can use codePointAt() to retrieve the code point for a character in a string while String.fromCodePoint() produces a single-character string for the given code point. For example:

console.log(String.fromCodePoint(134071));  // "𠮷"

You can think of String.fromCodePoint() as a more complete version of String.fromCharCode(). Each method has the same result for all characters in the BMP; the only difference is with characters outside of that range.

Escaping Non-BMP Characters

ECMAScript 5 allows strings to contain 16-bit Unicode characters represented by an escape sequence. The escape sequence is the \u followed by four hexadecimal values. For example, the escape sequence \u0061 represents the letter "a":

console.log("\u0061");      // "a"

If you try to use an escape sequence with a number past FFFF, the upper bound of the BMP, then you can get some surprising results:

console.log("\u20BB7");     // "7"

Since Unicode escape sequences were defined as always having exactly four hexadecimal characters, ECMAScript evaluates \u20BB7 as two characters: \u20BB and "7". The first character is unprintable and the second is the number 7.

ECMAScript 6 solves this problem by introducing an extended Unicode escape sequence where the hexadecimal numbers are contained within curly braces. This allows up to 8 hexadecimal characters to specify a single character:

console.log("\u{20BB7}");     // "𠮷"

Using the extended escape sequence, the correct character is contained in the string.

Make sure that you use this new escape sequence only in an ECMAScript 6 environment. In all other environments, doing so causes a syntax error. You may want to check and see if the environment supports the extended escape sequence using a function such as:

function supportsExtendedEscape() {
    try {
        eval("'\\u{00FF1}'");
        return true;
    } catch (ex) {
        return false;
    }
}

The normalize() Method

Another interesting aspect of Unicode is that different characters may be considered equivalent for the purposes of sorting or other comparison-based operations. There are two ways to define these relationships. First, canonical equivalence means that two sequences of code points are considered interchangeable in all respects. That even means that a combination of two characters can be canonically equivalent to one character. The second relationship is compatibility, meaning that two sequences of code points having different appearances but can be used interchangeably in certain situations.

The important thing to understand is that due to these relationships, it’s possible to have two strings that represent fundamentally the same text and yet have them contain different code point sequences. For example, the character “æ” and the string “ae” may be used interchangeably even though they are different code points. These two strings would therefore be unequal in JavaScript unless they are normalized in some way.

ECMAScript 6 supports the four Unicode normalization forms through a new normalize() method on strings. This method optionally accepts a single parameter, one of "NFC" (default), "NFD", "NFKC", or "NFKD". It’s beyond the scope of this book to explain the differences between these four forms. Just keep in mind that, in order to be used, you must normalize both strings that are being compared to the same form. For example:

var normalized = values.map(function(text) {
    return text.normalize();
});
normalized.sort(function(first, second) {
    if (first < second) {
        return -1;
    } else if (first === second) {
        return 0;
    } else {
        return 1;
    }
});

In this code, the strings in a values array are converted into a normalized form so that the array can be sorted appropriately. You can accomplish the sort on the original array by calling normalize() as part of the comparator:

values.sort(function(first, second) {
    var firstNormalized = first.normalize(),
        secondNormalized = second.normalize();

    if (firstNormalized < secondNormalized) {
        return -1;
    } else if (firstNormalized === secondNormalized) {
        return 0;
    } else {
        return 1;
    }
});

Once again, the most important thing to remember is that both values must be normalized in the same way. These examples have used the default, NFC, but you can just as easily specify one of the others:

values.sort(function(first, second) {
    var firstNormalized = first.normalize("NFD"),
        secondNormalized = second.normalize("NFD");

    if (firstNormalized < secondNormalized) {
        return -1;
    } else if (firstNormalized === secondNormalized) {
        return 0;
    } else {
        return 1;
    }
});

If you’ve never worried about Unicode normalization before, then you probably won’t have much use for this method. However, knowing that it is available will help should you ever end up working on an internationalized application.

The Regular Expression u Flag

Many common string operations are accomplished by using regular expressions. However, as noted earlier, regular expressions also work on the basis of 16-bit code units each representing a single character. That’s why the single character match in the earlier example didn’t work. To address this problem, ECMAScript 6 defines a new flag for regular expressions: u for “Unicode”.

When a regular expression has the u flag set, it switches modes to work on characters and not code units. That means the regular expression will no longer get confused about surrogate pairs in strings and can behave as expected. For example:

var text = "𠮷";

console.log(text.length);           // 2
console.log(/^.$/.test(text));      // false
console.log(/^.$/u.test(text));     // true

Adding the u flag allows the regular expression to correctly match the string by characters. Unfortunately, ECMAScript 6 does not have a way of determining how many code points are present in a string; fortunately, regular expressions can be used to figure it out:

function codePointLength(text) {
    var result = text.match(/[\s\S]/gu);
    return result ? result.length : 0;
}

console.log(codePointLength("abc"));    // 3
console.log(codePointLength("𠮷bc"));   // 3

The regular expression in this example matches both whitespace and non-whitespace characters, and is applied globally with Unicode enabled. The result contains an array of matches when there’s at least one match, so the array length ends up being the number of code points in the string.

Although this approach works, it’s not very fast, especially when applied to long strings. Try to minimize counting code points whenever possible. Hopefully ECMAScript 7 will bring a more performant means by which to count code points.

Since the u flag is a syntax change, attempting to use it in non-compliant JavaScript engines means a syntax error is thrown. The safest way to determine if the u flag is supported is with a function:

function hasRegExpU() {
    try {
        var pattern = new RegExp(".", "u");
        return true;
    } catch (ex) {
        return false;
    }
}

This function uses the RegExp constructor to pass in the u flag as an argument. This is valid syntax even in older JavaScript engines, however, the constructor will throw an error if u isn’t supported.

If your code needs to still work in older JavaScript engines, it’s best to use the RegExp constructor exclusively when using the u flag. This will prevent syntax errors and allow you to optionally detect and use the u flag without aborting execution.

Other String Changes

JavaScript strings have always lagged behind similar features of other languages. It was only in ECMAScript 5 that strings finally gained a trim() method, and ECMAScript 6 continues extending strings with new functionality.

includes(), startsWith(), endsWith()

Developers have used indexOf() as a way to identify strings inside of other strings since JavaScript was first introduced. ECMAScript 6 adds three new methods whose purpose is to identify strings inside of other strings:

  • includes() - returns true if the given text is found anywhere within the string or false if not.
  • startsWith() - returns true if the given text is found at the beginning of the string or false if not.
  • endsWith() - returns true if the given text is found at the end of the string or false if not.

Each of these methods accepts two arguments: the text to search for and an optional location from which to start the search. When the second argument is omitted, includes() and startsWith() start search from the beginning of the string while endsWith() starts from the end. In effect, the second argument results in less of the string being searched. Here are some examples:

var msg = "Hello world!";

console.log(msg.startsWith("Hello"));       // true
console.log(msg.endsWith("!"));             // true
console.log(msg.includes("o"));             // true

console.log(msg.startsWith("o"));           // false
console.log(msg.endsWith("world!"));        // true
console.log(msg.includes("x"));             // false

console.log(msg.startsWith("o", 4));        // true
console.log(msg.endsWith("o", 8));          // true
console.log(msg.includes("o", 8));          // false

These three methods make it much easier to identify substrings without needing to worry about identifying their exact position.

All of these methods return a boolean value. If you need to find the position of a string within another, use indexOf() or lastIndexOf().

The startsWith(), endsWith(), and includes() methods will throw an error if you pass a regular expression instead of a string. This stands in contrast to indexOf() and lastIndexOf(), which both convert a regular expression argument into a string and then search for that string.

repeat()

ECMAScript 6 also adds a repeat() method to strings. This method accepts a single argument, which is the number of times to repeat the string, and returns a new string that has the original string repeated the specified number of times. For example:

console.log("x".repeat(3));         // "xxx"
console.log("hello".repeat(2));     // "hellohello"
console.log("abc".repeat(4));       // "abcabcabcabc"

This method is really a convenience function above all else, which can be especially useful when dealing with text manipulation. One example where this functionality comes in useful is with code formatting utilities where you need to create indentation levels:

// indent using a specified number of spaces
var indent = " ".repeat(size),
    indentLevel = 0;

// whenever you increase the indent
var newIndent = indent.repeat(++indentLevel);

Other Regular Expression Changes

Regular expressions are an important part of working with strings in JavaScript, and like many parts of the language, haven’t really changed very much in recent versions. ECMAScript 6, however, made several improvements to regular expressions to go along with the updates to strings.

The Regular Expression y Flag

ECMAScript 6 standardized the y flag after it had been implemented in Firefox as a proprietary extension to regular expressions. The y (sticky) flag indicates that the next match should be made starting with the value of lastIndex on the regular expression.

The lastIndex property indicates the position at which to start the match of a string and is set to 0 by default, meaning matches always start at the beginning of a string. You can, however, overwrite lastIndex to have it start from somewhere else:

var pattern = /hello\d\s?/g,
    text = "hello1 hello2 hello3",
    result = pattern.exec(text);

console.log(result[0]);     // "hello1 "

pattern.lastIndex = 7;
result = pattern.exec(text);

console.log(result[0]);     // "hello2 "

In this example, the regular expression matches the string "hello" followed by a number and optionally a whitespace character. The g flag is important as it allows the regular expression to use lastIndex when set (without it, matches always start at 0 regardless of the lastIndex value). The first call to exec() results in matching “hello1” first while the second call, with a lastIndex of 7, matches “hello2” first.

The sticky flag tells the regular expression to save the index of the next character after the last match in lastIndex whenever an operation is performed (in the previous example, 7 is the location of next character after “hello1 “). If an operation results in no match then lastIndex is set back to 0.

var pattern = /hello\d\s?/y,
    text = "hello1 hello2 hello3",
    result = pattern.exec(text);

console.log(result[0]);             // "hello1 "
console.log(pattern.lastIndex);     // 7

result = pattern.exec(text);

console.log(result[0]);             // "hello2 "
console.log(pattern.lastIndex);     // 14

Here, the same pattern is used but with the sticky flag instead of the global flag. The value of lastIndex changed to 7 after the first call to exec() and to 14 after the second call. Since the sticky flag is updating lastIndex for you, there’s no need to keep track and manually update it yourself.

Perhaps the most important thing to understand about the sticky flag is that sticky regular expressions have an implied ^ at the beginning, indicating that the pattern should match from the beginning of the input. For example, if the previous example is changed to not match the whitespace character, there are different results:

var pattern = /hello\d/y,
    text = "hello1 hello2 hello3",
    result = pattern.exec(text);

console.log(result[0]);             // "hello1"
console.log(pattern.lastIndex);     // 6

result = pattern.exec(text);

console.log(result);                // null
console.log(pattern.lastIndex);     // 0

Without matching the whitespace character, the lastIndex is set to 6 after the first call to exec(). That means the regular expression will be evaluating the string as if it were this:

" hello2 hello3"

Since there is an implied ^ at the beginning of the regular expression pattern, the pattern starts by matching "h" against the space and sees that they are not equivalent. The matching stops there and null is returned. The lastIndex property is reset to 0.

As with other regular expression flags, you can detect the presence of y by using a property. The sticky property is set to true with the sticky flag is present and false if not:

var pattern = /hello\d/y;

console.log(pattern.sticky);    // true

The sticky property is read-only based on the presence of the flag and so cannot be changed in code.

The lastIndex property is only honored when calling methods on the regular expression object such as exec() and test(). Passing the regular expression to a string method, such as match(), will not result in the sticky behavior.

Similar to the u flag, the y flag is a syntax change, so it will cause a syntax error in older JavaScript engines. You can use the same approach to detect support:

function hasRegExpY() {
    try {
        var pattern = new RegExp(".", "y");
        return true;
    } catch (ex) {
        return false;
    }
}

Also similar to u, if you need to use y in code that runs in older JavaScript engines, be sure to use the RegExp constructor when defining those regular expressions to avoid a syntax error.

Duplicating Regular Expressions

In ECMAScript 5, you can duplicate regular expressions by passing them into the RegExp constructor, such as:

js
var re1 = /ab/i,
    re2 = new RegExp(re1);

However, if you provide the second argument to RegExp, which specifies the flags for the regular expression, then an error is thrown:

js
var re1 = /ab/i,

    // throws an error in ES5, okay in ES6
    re2 = new RegExp(re1, "g");

If you execute this code in an ECMAScript 5 environment, you’ll get an error stating that the second argument cannot be used when the first argument is a regular expression. ECMAScript 6 changed this behavior such that the second argument is allowed and will override whichever flags are present on the first argument. For example:

js
var re1 = /ab/i,

    // throws an error in ES5, okay in ES6
    re2 = new RegExp(re1, "g");


console.log(re1.toString());            // "/ab/i"
console.log(re2.toString());            // "/ab/g"

console.log(re1.test("ab"));            // true
console.log(re2.test("ab"));            // true

console.log(re1.test("AB"));            // true
console.log(re2.test("AB"));            // false

In this code, re1 has the case-insensitive i flag present while re2 has only the global g flag. The RegExp constructor duplicated the pattern from re1 and then substituted g for i. If the second argument was missing then re2 would have the same flags as re1.

The flags Property

In ECMAScript 5, it’s possible to get the text of the regular expression by using the source property, but to get the flag string requires parsing the output of toString(), such as:

function getFlags(re) {
    var text = re.toString();
    return text.substring(text.lastIndexOf("/") + 1, text.length);
}

// toString() is "/ab/g"
var re = /ab/g;

console.log(getFlags(re));          // "g"

ECMAScript 6 adds a flags property to go along with source. Both properties are prototype accessor properties with only a getter assigned (making them read-only). The addition of flags makes it easier to inspect regular expressions for both debugging and inheritance purposes.

A late addition to ECMAScript 6, the flags property returns the string representation of any flags applied to a regular expression.

Object.is()

When you want to compare two values, you’re probably used to using either the equals operator (==) or the identically equals operator (===). Many prefer to use the latter to avoid type coercion during the comparison. However, even the identically equals operator isn’t entirely accurate. For example, the values +0 and -0 are considered equal by === even though they are represented differently in the JavaScript engine. Also NaN === NaN returns false, which necessitates using isNaN() to detect NaN properly.

ECMAScript 6 introduces Object.is() to make up for the remaining quirks of the identically equals operator. This method accepts two arguments and returns true if the values are equivalent. Two values are considered equivalent when they are of the same type and have the same value. In many cases, Object.is() works the same as ===. The only differences are that +0 and -0 are considered not equivalent and NaN is considered equivalent to NaN. Here are some examples:

console.log(+0 == -0);              // true
console.log(+0 === -0);             // true
console.log(Object.is(+0, -0));     // false

console.log(NaN == NaN);            // false
console.log(NaN === NaN);           // false
console.log(Object.is(NaN, NaN));   // true

console.log(5 == 5);                // true
console.log(5 == "5");              // true
console.log(5 === 5);               // true
console.log(5 === "5");             // false
console.log(Object.is(5, 5));       // true
console.log(Object.is(5, "5"));     // false

In most cases you will probably still want to use == or === for comparing values, as special cases covered by Object.is() may not affect you directly.

Block bindings

Traditionally, one of the tricky parts of JavaScript has been the way that var declarations work. In most C-based languages, variables are created at the spot where the declaration occurs. In JavaScript, however, this is not the case. Variables declared using var are hoisted to the top of the function (or global scope) regardless of where the actual declaration occurs. For example:

function getValue(condition) {

    if (condition) {
        var value = "blue";

        // other code

        return value;
    } else {

        // value exists here with a value of undefined

        return null;
    }

    // value exists here with a value of undefined
}

If you are unfamiliar with JavaScript, you might expect that the variable value is only defined if condition evaluates to true. In fact, the variable value is declared regardless. The JavaScript engine changes the function to look like this:

function getValue(condition) {

    var value;

    if (condition) {
        value = "blue";

        // other code

        return value;
    } else {

        return null;
    }
}

The declaration of value is moved to the top (hoisted) while the initialization remains in the same spot. That means the variable value is actually still accessible from within the else clause, it just has a value of undefined because it hasn’t been initialized.

It often takes new JavaScript developers some time to get used to declaration hoisting and this unique behavior can end up causing bugs. For this reason, ECMAScript 6 introduces block level scoping options to make the control of variable lifecycle a little more powerful.

Let declarations

The let declaration syntax is the same as for var. You can basically replace var with let to declare a variable but keep its scope to the current code block. For example:

function getValue(condition) {

    if (condition) {
        let value = "blue";

        // other code

        return value;
    } else {

        // value doesn't exist here

        return null;
    }

    // value doesn't exist here
}

This function now behaves much closer to other C-based languages. The variable value is declared using let instead of var. That means the declaration is not hoisted to the top, and the variable value is destroyed once execution has flowed out of the if block. If condition evaluates to false, then value is never declared or initialized.

Perhaps one of the areas where developers most want block level scoping of variables is with for loops. It’s not uncommon to see code such as this:

for (var i=0; i < items.length; i++) {
    process(items[i]);
}

// i is still accessible here and is equal to items.length

In other languages, where block level scoping is the default, code like this works as intended. In JavaScript, the variable i is still accessible after the loop is completed because the var declaration was hoisted. Using let allows you to get the intended behavior:

for (let i=0; i < items.length; i++) {
    process(items[i]);
}

// i is not accessible here

In this example, the variable i only exists within the for loop. Once the loop is complete, the variable is destroyed and is no longer accessible elsewhere.

Using let in loops

The behavior of let inside of loops is slightly different than with other blocks. Instead of creating a variable that is used with each iteration of the loop, each iteration actually gets its own variable to use. This is to solve an old problem with JavaScript loops. Consider the following:

 var funcs = [];

 for (var i=0; i < 10; i++) {
     funcs.push(function() { console.log(i); });
 }

 funcs.forEach(function(func) {
     func();     // outputs the number "10" ten times
 });

This code will output the number 10 ten times in a row. That’s because the variable i is shared across each iteration of the loop, meaning the closures created inside the loop all hold a reference to the same variable. The variable i has a value of 10 once the loop completes, and so that’s the value each function outputs.

To fix this problem, developers use immediately-invoked function expressions (IIFEs) inside of loops to force a new copy of the variable to be created:

 var funcs = [];

 for (var i=0; i < 10; i++) {}
     funcs.push((function(value) {
         return function() {
             console.log(value);
         }
     }(i)));
 }

 funcs.forEach(function(func) {
     func();     // outputs 0, then 1, then 2, up to 9
 });

This version of the example uses an IIFE inside of the loop. The i variable is passed to the IIFE, which creates it’s own copy and stores it as value. This is the value used of the function for that iteration, so calling each function returns the expected value.

A let declaration does this for you without the IIFE. Each iteration through the loop results in a new variable being created and initialized to the value of the variable with the same name from the previous iteration. That means you can simplify the process by using this code:

 var funcs = [];

 for (let i=0; i < 10; i++) {}
     funcs.push(function() { console.log(i); });
 }

 funcs.forEach(function(func) {
     func();     // outputs 0, then 1, then 2, up to 9
 })

This code works exactly like the code that used var and an IIFE but is, arguably, cleaner.

Unlike var, let has no hoisting characteristics. A variable declared with let cannot be accessed until after the let statement. Attempting to do so results in a reference error:

if (condition) {
    console.log(value);     // ReferenceError!
    let value = "blue";
}

In this code, the variable value is defined and initialized using let, but that statement is never executed because the previous line throws an error. The same is true anytime you attempt to use a let variable inside of the same block prior to it being defined. Even the normally safe-to-use typeof operator isn’t safe:

if (condition) {
    console.log(typeof value);     // ReferenceError!
    let value = "blue";
}

Here, typeof value throws the same error as the previous example. You cannot use a let variable before its declaration within the same block. However, you can use typeof outside of the block:

console.log(typeof value);     // "undefined"

if (condition) {
    let value = "blue";
}

This example has the typeof operator applied outside of the block in which value is declared. That means there is no value binding and typeof simply returns "undefined".

If an identifier has already been defined in the block, then using the identifier in a let declaration causes an error to be thrown. For example:

var count = 30;

// Syntax error
let count = 40;

In this example, count is declared twice, once with var and once with let. Because let will not redefine an identifier that already exists in the same scope, the declaration throws an error. No error is thrown if a let declaration creates a new variable in a scope with the same name as a variable in the containing scope, such as:

var count = 30;

// Does not throw an error
if (condition) {

    let count = 40;

    // more code
}

Here, the let declaration will not throw an error because it is creating a new variable called count within the if statement. This new variable shadows the global count, preventing access to it from within the if block.

The intent of let is to replace var long term, as the former behaves more like variable declarations in other languages. If you are writing JavaScript that will execute only in an ECMAScript 6 or higher environment, you may want to try using let exclusively and leaving var for other scripts that require backwards compatibility.

Since let declarations are not hoisted to the top of the enclosing block, you may want to always place let declarations first in the block so that they are available to the entire block.

Constant declarations

Another new way to define variables is to use the const declaration syntax. Variables declared using const are considered to be constants, so the value cannot be changed once set. For this reason, every const variable must be initialized. For example:

// Valid constant
const MAX_ITEMS = 30;

// Syntax error: missing initialization
const NAME;

Constants are also block-level declarations, similar to let. That means constants are destroyed once execution flows out of the block in which they were declared and declarations are hoisted to the top of the block. For example:

if (condition) {
    const MAX_ITEMS = 5;

    // more code
}

// MAX_ITEMS isn't accessible here

In this code, the constant MAX_ITEMS is declared within and if statement. Once the statement finishes executing, MAX_ITEMS is destroyed and is not accessible outside of that block.

Also similar to let, an error is thrown whenever a const declaration is made with an identifier for an already-defined variable in the same scope. It doesn’t matter if that variable was declared using var (for global or function scope) or let (for block scope). For example:

var message = "Hello!";
let age = 25;

// Each of these would cause an error given the previous declarations
const message = "Goodbye!";
const age = 30;

Several browsers implement pre-ECMAScript 6 versions of const. Implementations range from being simply a synonym for var (allowing the value to be overwritten) to actually defining constants but only in the global or function scope. For this reason, be especially careful with using const in a production system. It may not be providing you with the functionality you expect.

Destructuring Assignment

JavaScript developers spend a lot of time pulling data out of objects and arrays. It’s not uncommon to see code such as this:

var options = {
        repeat: true,
        save: false
    };

// later

var localRepeat = options.repeat,
    localSave = options.save;

Frequently, object properties are stored into local variables for more succinct code and easier access. ECMAScript 6 makes this easy by introducing destructuring assignment, which systematically goes through an object or array and stores specified pieces of data into local variables.

If the right side value of a destructuring assignment evaluates to null or undefined, an error is thrown.

Object Destructuring

Object destructuring assignment syntax uses an object literal on the left side of an assignment operation. For example:

var options = {
        repeat: true,
        save: false
    };

// later

var { repeat: localRepeat, save: localSave } = options;

console.log(localRepeat);       // true
console.log(localSave);         // false

In this code, the value of options.repeat is stored in a variable called localRepeat and the value of options.save is stored in a variable called localSave. These are both specified using the object literal syntax where the key is the property to find on options and the value is the variable in which to store the property value.

If the property with the given name doesn’t exist on the object, then the local variable gets a value of undefined.

If you want to use the property name as the local variable name, you can omit the colon and the identifier, such as:

var options = {
        repeat: true,
        save: false
    };

// later

var { repeat, save } = options;

console.log(repeat);        // true
console.log(save);          // false

Here, two local variables called repeat and save are created. They are initialized with the value of options.repeat and options.save, respectively. This shorthand is helpful when there’s no need to have different variable names.

Destructuring can also handled nested objects, such as the following:

var options = {
        repeat: true,
        save: false,
        rules: {
            custom: 10,
        }
    };

// later

var { repeat, save, rules: { custom }} = options;

console.log(repeat);        // true
console.log(save);          // false
console.log(custom);        // 10

In this example, the custom property is embedded in another object. The extra set of curly braces allows you to descend into a nested an object and pull out its properties.

Syntax Gotcha

If you try use destructuring assignment without a var, let, or const, you may be surprised by the result:

// syntax error
{ repeat, save, rules: { custom }} = options;

This causes a syntax error because the opening curly brace is normally the beginning of a block and blocks can’t be part of assignment expressions.

The solution is to wrap the left side literal in parentheses:

// no syntax error
({ repeat, save, rules: { custom }}) = options;

This now works without any problems.

Array Destructuring

Similarly, you can destructure arrays using array literal syntax on the left side of an assignment operation. For example:

var colors = [ "red", "green", "blue" ];

// later

var [ firstColor, secondColor ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

In this example, array destructuring pulls out the first and second values in the colors array. Keep in mind that the array itself isn’t changed in any way.

Similar to object destructuring, you can also nest array destructuring. Just use another set of square brackets to descend into a subarray:

var colors = [ "red", [ "green", "lightgreen" ], "blue" ];

// later

var [ firstColor, [ secondColor ] ] = colors;

console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

Here, the secondColor variable refers to the "green" value inside of the colors array. That item is contained within a second array, so the extra square brackets around secondColor in the destructuring assignment is necessary.

Mixed Destructuring

It’s possible to mix objects and arrays together in a destructuring assignment expression using a mix of object and array literals. For example:

var options = {
        repeat: true,
        save: false,
        colors: [ "red", "green", "blue" ]
    };

var { repeat, save, colors: [ firstColor, secondColor ]} = options;

console.log(repeat);            // true
console.log(save);              // false
console.log(firstColor);        // "red"
console.log(secondColor);       // "green"

This example extracts two property values, repeat and save, and then two items from the colors array, firstColor and secondColor. Of course, you could also choose to retrieve the entire array:

var options = {
        repeat: true,
        save: false,
        colors: [ "red", "green", "blue" ]
    };

var { repeat, save, colors } = options;

console.log(repeat);                        // true
console.log(save);                          // false
console.log(colors);                        // "red,green,blue"
console.log(colors === options.colors);     // true

This modified example retrieves options.colors and stores it in the colors variable. Notice that colors is a direct reference to options.colors and not a copy.

Mixed destructuring is very useful for pulling values out of JSON configuration structures without navigating the entire structure.

Numbers

JavaScript numbers can be particularly complex due to the dual usage of a single type for both integers and floats. Numbers are stored in the IEEE 754 double precision floating point format, and that same format is used to represent both types of numbers. As one of the foundational data types of JavaScript (along with strings and booleans), numbers are quite important to JavaScript developers. Given the new emphasis on gaming and graphics in JavaScript, ECMAScript 6 sought to make working with numbers easier and more powerful.

Octal and Binary Literals

ECMAScript 5 sought to simplify some common numerical errors by removing the previously-included octal integer literal notation in two places: parseInt() and strict mode. In ECMAScript 3 and earlier, octal numbers were represented with a leading 0 followed by any number of digits. For example:

// ECMAScript 3
var number = 071;       // 57 in decimal

var value1 = parseInt("71");    // 71
var value2 = parseInt("071");   // 57

Many developers were confused by this version of octal literal numbers, and many mistakes were made as a result of misunderstanding the effects of a leading zero in various places. The most egregious was in parseInt(), where a leading zero meant the value would be treated as an octal rather than a decimal. This led to one of Douglas Crockford’s first JSLint rules: always use the second argument of parseInt() to specify how the string should be interpreted.

ECMAScript 5 cut down on the use of octal numbers. First, parseInt() was changed so that it ignores leading zeros in the first argument when there is no second argument. This means a number cannot accidentally be treated as octal anymore. The second change was to eliminate octal literal notation in strict mode. Attempting to use an octal literal in strict mode results in a syntax error.

// ECMAScript 5
var number = 071;       // 57 in decimal

var value1 = parseInt("71");        // 71
var value2 = parseInt("071");       // 71
var value3 = parseInt("071", 8);    // 57

function getValue() {
    "use strict";
    return 071;     // syntax error
}

By making these two changes, ECMAScript 5 sought to eliminate a lot of the confusion and errors associated with octal literals.

ECMAScript 6 took things a step further by reintroducing an octal literal notation, along with a binary literal notation. Both of these notations take a hint for the hexadecimal literal notation of prepending 0xor 0X to a value. The new octal literal format begins with 0o or 0O while the new binary literal format begins with 0b or 0B. Each literal type must be followed by one or more digits, 0-7 for octal, 0-1 for binary. Here’s an example:

// ECMAScript 6
var value1 = 0o71;      // 57 in decimal
var value2 = 0b101;     // 5 in decimal

Adding these two literal types allows JavaScript developers to quickly and easily include numeric values in binary, octal, decimal, and hexadecimal formats, which is very important in certain types of mathematical operations.

The parseInt() method doesn’t handle strings that look like octal or binary literals:

console.log(parseInt("0o71"));      // 0
console.log(parseInt("0b101"));     // 0

However, the Number() function will convert a string containing octal or binary literals correctly:

console.log(Number("0o71"));      // 57
console.log(Number("0b101"));     // 5

When using octal or binary literal in strings, be sure to understand your use case and use the most appropriate method for converting them into numeric values.

isFinite() and isNaN()

JavaScript has long had a couple of global methods for identifying certain types of numbers:

  • isFinite() determines if a value represents a finite number (not Infinity or -Infinity)
  • isNaN() determines if a value is NaN (since NaN is the only value that is not equal to itself)

Although intended to work with numbers, these methods are capable of inferring a numeric value from and value that is passed in. That both methods can return incorrect results when passed a value that isn’t a number. For example:

console.log(isFinite(25));      // true
console.log(isFinite("25"));    // true

console.log(isNaN(NaN));        // true
console.log(isNaN("NaN"));      // true

Both isFinite() and isNaN() pass their arguments through Number() to get a numeric value and then perform their comparisons on that numeric value rather than the original. This confusing outcome can lead to errors when value types are not checked before being used with one of these methods.

ECMAScript 6 adds two new methods that perform the same comparison but only for number values: Number.isFinite() and Number.isNaN(). These methods always return false when passed a non-number value and return the same values as their global counterparts when passed a number value:

console.log(isFinite(25));              // true
console.log(isFinite("25"));            // true
console.log(Number.isFinite(25));       // true
console.log(Number.isFinite("25"));     // false

console.log(isNaN(NaN));                // true
console.log(isNaN("NaN"));              // true
console.log(Number.isNaN(NaN));         // true
console.log(Number.isNaN("NaN"));       // false

In this code, Number.isFinite("25") returns false even though isFinite("25") returns true; likewise Number.isNaN("NaN") returns false even though isNaN(“NaN”) returns true`.

These two new methods are aimed at eliminating certain types of errors that can be caused when non-number values are used with isFinite() and isNaN() without dramatically changing the language.

parseInt() and parseFloat()

The global functions parseInt() and parseFloat() now also reside at Number.parseInt() and Number.parseFloat(). These functions behave exactly the same as the global functions of the same name. The only purpose in making this move is to categorize purely global functions that clearly relate to a specific data type. Since these functions both create numbers from strings, they are now on Number along with the other functions that relate to numbers.

Working with Integers

A lot of confusion has been caused over the years related to JavaScript’s single number type that is used to represent both integers and floats. The language goes through great pains to ensure that developers don’t need to worry about the details, but problems still leak through from time to time. ECMAScript 6 seeks to address this by making it easier to identify and work with integers.

Identifying Integers

The first addition is Number.isInteger(), which allows you to determine if a value represents an integer in JavaScript. Since integers and floats are stored differently, the JavaScript engine looks at the underlying representation of the value to make this determination. That means numbers that look like floats might actually be stored as integers and therefore return true from Number.isInteger(). For example:

console.log(Number.isInteger(25));      // true
console.log(Number.isInteger(25.0));    // true
console.log(Number.isInteger(25.1));    // false

In this code, Number.isInteger() returns true for both 25 and 25.0 even though the latter looks like a float. Simply adding a decimal point to a number doesn’t automatically make it a float in JavaScript. Since 25.0 is really just 25, it is stored as an integer. The number 25.1, however, is stored as a float because there is a fraction value.

Safe Integers

However, all is not so simple with integers. JavaScript can only accurately represent integers between -253 and 253, and outside of this “safe” range, binary representations end up reused for multiple numeric values. For example:

console.log(Math.pow(2, 53));      // 9007199254740992
console.log(Math.pow(2, 53) + 1);  // 9007199254740992

This example doesn’t contain a typo, two different numbers end up represented by the same JavaScript integer. The effect becomes more prevalent the further the value is outside of the safe range.

ECMAScript 6 introduces Number.isSafeInteger() to better identify integers that can accurately be represented in the language. There is also Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER that represent the upper and lower bounds of the same range, respectively. The Number.isSafeInteger() method ensures that a value is an integer and falls within the safe range of integer values:

var inside = Number.MAX_SAFE_INTEGER,
    outside = inside + 1;

console.log(Number.isInteger(inside));          // true
console.log(Number.isSafeInteger(inside));      // true

console.log(Number.isInteger(outside));         // true
console.log(Number.isSafeInteger(outside));     // false

The number inside is the largest safe integer, so it returns true for both Number.isInteger() and Number.isSafeInteger(). The number outside is the first questionable integer value, so it is no longer considered safe even though it’s still an integer.

Most of the time, you only want to deal with safe integers when doing integer arithmetic or comparisons in JavaScript, so it’s a good idea to use Number.isSafeInteger() as part of input validation.

New Math Methods

The aforementioned new emphasis on gaming and graphics in JavaScript led to the realization that many mathematical calculations could be done more efficiently by a JavaScript engine than with pure JavaScript code. Optimization strategies like asm.js, which works on a subset of JavaScript to improve performance, need more information to perform calculations in the fastest way possible. It’s important, for instance, to know whether the numbers should be treated as 32-bit integers or as 64-bit floats.

As a result, ECMAScript 6 adds several new methods to the Math object. These new methods are important for improving the speed of common mathematical calculations, and therefore, improving the speed of applications that must perform many calculations (such as graphics programs). The new methods are listed below.

Method Description
Math.acosh(x) Returns the inverse hyperbolic cosine of x.
Math.asinh(x) Returns the inverse hyperbolic sine of x.
Math.atanh(x) Returns the inverse hyperbolic tangent of x
Math.cbrt(x) Returns the cubed root of x.
Math.clz32(x) Returns the number of leading zero bits in the 32-bit integer representation of x.
Math.cosh(x) Returns the hyperbolic cosine of x.
Math.expm1(x) Returns the result of subtracting 1 from the exponential function of x
Math.fround(x) Returns the nearest single-precision float of x.
Math.hypot(...values) Returns the square root of the sum of the squares of each argument.
Math.imul(x, y) Returns the result of performing true 32-bit multiplication of the two arguments.
Math.log1p(x) Returns the natural logarithm of 1 + x.
Math.log10(x) Returns the base 10 logarithm of x.
Math.log2(x) Returns the base 2 logarithm of x.
Math.sign(x) Returns -1 if the x is negative 0 if x is +0 or -0, or 1 if x is positive.
Math.sinh(x) Returns the hyperbolic sine of x.
Math.tanh(x) Returns the hyperbolic tangent of x.
Math.trunc(x) Removes fraction digits from a float and returns an integer.

It’s beyond the scope of this book to explain each new method and what it does in detail. However, if you are looking for a reasonably common calculation, be sure to check the new Math methods before implementing it yourself.

Summary

ECMAScript 6 makes a lot of changes, both large and small, to JavaScript. Some of the smaller changes detailed in this chapter will likely be overlooked by many but they are just as important to the evolution of the language as the big changes.

Full Unicode support allows JavaScript to start dealing with UTF-16 characters in logical ways. The ability to transfer between code point and character via codePointAt() and String.fromCodePoint() is an important step for string manipulation. The addition of the regular expression u flag makes it possible to operate on code points instead of 16-bit characters, and the normalize() method allows for more appropriate string comparisons.

Additional methods for working with strings were added, allowing you to more easily identify substrings no matter where they are found, and more functionality was added to regular expressions. The Object.is() method performs strict equality on any value, effectively becoming a safer version of === when dealing with special JavaScript values.

The let and const block bindings introduce lexical scoping to JavaScript. These declarations are not hoisted and only exist within the block in which they are declared. That means behavior that is more like other languages and less likely to cause unintentional errors, as variables can now be declared exactly where they are needed. It’s expected that the majority of JavaScript code going forward will use let and const exclusively, effectively making var a deprecated part of the language.

ECMAScript 6 makes it easier to work with numbers through the introduction of new syntax and new methods. The binary and octal literal forms allow you to embed numbers directly into source code while keeping the most appropriate representation visible. There are Number.isFinite() and Number.isNaN() that are safer versions of the global methods of the same names due to their lack of type coercion. You can more easily identify integers using Number.isInteger() and Number.isSafeInteger() as well as perform a lot more mathematical operations thanks to new methods on Math.

Though many of these changes are small, they will make a significant difference in the lives of JavaScript developers for years to come. Each change addresses a particular concern that can otherwise requires a lot of custom code to address. By building this functionality into the language, developers can focus on writing the code for their product rather than low-level utilities.

Functions

Functions are an important part of any programming language, and JavaScript functions hadn’t changed much since the language was first introduced. This left a backlog of problems and nuanced behavior that made it easy to make mistakes or require more code just to achieve a very common behavior.

ECMAScript 6 functions made a big leap forward, taking into account years of complaints and asks from JavaScript developers. The result is a number of incremental improvements on top of ECMAScript 5 functions that make programming in JavaScript less error-prone and more powerful than ever before.

Default Parameters

Functions in JavaScript are unique in that they allow any number of parameters to be passed regardless of the number of declared parameters in the function definition. This allows you to define functions that can handle different number of parameters, often by just filling in default values when ones are provided. In ECMAScript 5 and earlier, you would likely use the following pattern to accomplish this:

function makeRequest(url, timeout, callback) {

    timeout = timeout || 2000;
    callback = callback || function() {};

    // the rest of the function

}

In this example, both timeout and callback are actually optional because they are given a default value if not provided. The logical OR operator (||) always returns the second operand when the first is falsy. Since named function parameters that are not explicitly provided are set to undefined, the logical OR operator is frequently used to provide default values for missing parameters. There is a flaw with this approach, however, in that a valid value for timeout might actually be 0, but this could would replace it with 2000 because 0 is falsy.

Other ways of determining if any parameters are missing include checking arguments.length for the number of parameters that were passed or directly inspecting each parameter to see if it is not undefined.

ECMAScript 6 makes it easier to provide default values for parameters by providing initializations that are used when the parameter isn’t formally passed. For example:

function makeRequest(url, timeout = 2000, callback = function() {}) {

    // the rest of the function

}

Here, only the first parameter is expected to be passed all the time. The other two parameters have default values, which makes the body of the function much smaller because you don’t need to add any code to check for a missing value. When makeRequest() is called with all three parameters, then the defaults are not used. For example:

// uses default timeout and callback
makeRequest("/foo");

// uses default callback
makeRequest("/foo", 500);

// doesn't use defaults
makeRequest("/foo", 500, function(body) {
    doSomething(body);
});

Any parameters with a default value are considered to be optional parameters while those without default value are considered to be required parameters.

It’s possible to specify default values for any arguments, including those that appear before arguments without default values. For example, this is fine:

function makeRequest(url, timeout = 2000, callback) {

    // the rest of the function

}

In this case, the default value for timeout will only be used if there is no second argument passed in or if the second argument is explicitly passed in as undefined. For example:

// uses default timeout
makeRequest("/foo", undefined, function(body) {
    doSomething(body);
});

// uses default timeout
makeRequest("/foo");

// doesn't use default timeout
makeRequest("/foo", null, function(body) {
    doSomething(body);
});

In the case of default parameter values, the value of null is considered to be valid and the default value will not be used.

Perhaps the most interesting feature of default parameter arguments is that the default value need not be a primitive value. You can, for example, execute a function to retrieve the default parameter:

function getCallback() {
    return function() {
        // some code
    };
}

function makeRequest(url, timeout = 2000, callback = getCallback()) {

    // the rest of the function

}

Here, if the last argument isn’t provided, the function getCallback() is called to retrieve the correct default value. This opens up a lot of interesting possibilities to dynamically inject information into functions.

Rest Parameters

Since JavaScript functions can be passed any number of parameters, it’s not always necessary to define each parameter specifically. Early on, JavaScript provided the arguments object as a way of inspecting all function parameters that were passed without necessarily defining each one individually. While that worked fine in most cases, it can become a little cumbersome to work with. For example:

function sum(first) {
    let result = first,
        i = 1,
        len = arguments.length;

    while (i < len) {
        result += arguments[i];
        i++;
    }

    return result;
}

This function adds together all of the parameters that are passed to it so you can call sum(1) or sum(1,2,3,4) and it will still work. There are couple of things to notice about this function. First, it’s not at all obvious that the function is capable of handling more than one parameter. You could add in several more named parameters, but you would always fall short of indicating that this function can take any number of parameters. Second, because the first parameter is named and used directly, you have to start looking in the arguments object at index 1 instead of starting at index 0. Remembering to use the appropriate indices with arguments isn’t necessarily difficult, but it’s one more thing to keep track of. ECMAScript 6 introduces rest parameters to help with these issues.

Rest parameters are indicated by three dots (...) preceding a named parameter. That named parameter then becomes an Array containing the rest of the parameters (which is why these are called “rest” parameters). For example, sum() can be rewritten using rest parameters like this:

function sum(first, ...numbers) {
    let result = first,
        i = 0,
        len = numbers.length;

    while (i < len) {
        result += numbers[i];
        i++;
    }

    return result;
}

In this version of the function, numbers is a rest parameter that contains all parameters after the first one (unlike arguments, which contains all parameters including the first one). That means you can iterate over numbers from beginning to end without worry. As a bonus, you can tell by looking at the function that it is capable of handling any number of parameters.

The sum() method doesn’t actually need any named parameters. You could, in theory, use only rest parameters and have it continue to work exactly the same. However, in that case, the rest parameters would effectively be the same as arguments, so you’re not really getting any additional benefit.

The only restriction on rest parameters is that no other named arguments can follow in the function declaration. For example, this causes syntax error:

// Syntax error: Can't have a named parameter after rest parameters
function sum(first, ...numbers, last) {
    let result = first,
        i = 0,
        len = numbers.length;

    while (i < len) {
        result += numbers[i];
        i++;
    }

    return result;
}

Here, the parameter last follows the rest parameter numbers and causes a syntax error.

Rest parameters were designed to replace arguments in ECMAScript. Originally ECMAScript 4 did away with arguments and added rest parameters to allow for an unlimited number of arguments to be passed to functions. Even though ECMAScript 4 never came into being, the idea was kept around and reintroduced in ECMAScript 6 despite arguments not being removed from the language.

Destructured Parameters

In Chapter 1, you learned about destructuring assignment. Destructuring can also be used outside of the context of an assignment expression and perhaps the most interesting such case is with destructured parameters.

It’s common for functions that take a large number of optional parameters to use an options object as one or more parameters. For example:

function setCookie(name, value, options) {

    options = options || {};

    var secure = options.secure,
        path = options.path,
        domain = options.domain,
        expires = options.expires;

    // ...
}

setCookie("type", "js", {
    secure: true,
    expires: 60000
});

There are many setCookie() functions in JavaScript libraries that look similar to this. The name and value are required but everything else is not. And since there is no priority order for the other data, it makes sense to have an options object with named properties rather than extra named properties. This approach is okay, although it makes the expect input for the function a bit opaque.

Using destructured parameters, the previous function can be rewritten as follows:

function setCookie(name, value, { secure, path, domain, expires }) {

    // ...
}

setCookie("type", "js", {
    secure: true,
    expires: 60000
});

The behavior of this function is similar to the previous example, the biggest difference is the third argument uses destructuring to pull out the necessary data. Doing so makes it clear which parameters are really expected, and the destructured parameters also act like regular parameters in that they are set to undefined if they are not passed.

One quirk of this pattern is that the destructured parameters throw an error when the argument isn’t provided. If setCookie() is called with just two arguments, it results in a runtime error:

// Error!
setCookie("type", "js");

This code throws an error because the third argument is missing (undefined). To understand why this is an error, it helps to understand that destructured parameters are really just a shorthand for destructured assignment. The JavaScript engine is actually doing this:

function setCookie(name, value, options) {

    var { secure, path, domain, expires } = options;

    // ...
}

Since destructuring assignment throws an error when the right side expression evaluates to null or undefined, the same is true when the third argument isn’t passed.

You can work around this behavior by providing a default value for the destructured parameter:

function setCookie(name, value, { secure, path, domain, expires } = {}) {

    // ...
}

This example now works exactly the same as the first example in this section. Providing the default value for the destructured parameter means that secure, path, domain, and expires will all be undefined if the third argument to setCookie() isn’t provided.

It’s recommended to always provide the default value for destructured parameters to avoid all errors that are unique to their usage.

The Spread Operator

Closely related to rest parameters is the spread operator. Whereas rest parameters allow you to specify multiple independent arguments should be combined into an array, the spread operator allows you to specify an array that should be be split and have its items passed in as separate arguments to a function. Consider the Math.max() method, which accepts any number of arguments and returns the one with the highest value. It’s basic usage is as follows:

let value1 = 25,
    value2 = 50;

console.log(Math.max(value1, value2));      // 50

When you’re dealing with just two values, as in this example, Math.max() is very easy to use. The two values are passed in and the higher value is returned. But what if you have been tracking values in an array, and now you want to find the highest value? The Math.max() method doesn’t allow you to pass in an array, so in ECMAScript 5 and earlier, you’d be stuck either searching the array yourself or using apply():

let values = [25, 50, 75, 100]

console.log(Math.max.apply(Math, values));  // 100

While possible, using apply() in this manner is a bit confusing - it actually seems to obfuscate the true meaning of the code with additional syntax.

The ECMAScript 6 spread operator makes this case very simple. Instead of calling apply(), you can pass in the array and prefix it with the same ... pattern that is used with rest parameters. The JavaScript engine then splits up the array into individual arguments and passes them in:

let values = [25, 50, 75, 100]

// equivalent to
// console.log(Math.max(25, 50, 75, 100));
console.log(Math.max(...values));           // 100

Now the call to Math.max() looks a bit more conventional and avoids the complexity of specifying a this-binding for a simple mathematical operation.

You can mix and match the spread operator with other arguments as well. Suppose you want the smallest number returned from Math.max() to be 0 (just in case negative numbers sneak into the array). You can pass that argument separately and still use the spread operator for the other arguments:

let values = [-25, -50, -75, -100]

console.log(Math.max(...values, 0));        // 0

In this example, the last argument passed to Math.max() is 0, which comes after the other arguments are passed in using the spread operator.

The spread operator for argument passing makes using arrays for function arguments much easier. You’ll likely find it to be a suitable replacement for the apply() method in most circumstances.

The name Property

Identifying functions can be challenging in JavaScript given the various ways a function can be defined. Additionally, the prevalence of anonymous function expressions makes debugging a bit more difficult, often resulting in stack traces that are hard to read and decipher. For these reasons, ECMAScript 6 adds the name property to all functions.

All functions in an ECMAScript 6 program will have an appropriate value for their name property while all others will have an empty string. For example:

function doSomething() {
    // ...
}

var doAnotherThing = function() {
    // ...
};

console.log(doSomething.name);          // "doSomething"
console.log(doSomethingElse.name);      // "doSomethingElse"
console.log(doAnotherThing.name);       // "doAnotherThing"

In this code, doSomething() has a name property equal to "doSomething" because it’s a function declaration. The anonymous function expression doAnotherThing() has a name of "doAnotherThing" due to the variable to which it is assigned.

While function declarations and function expressions as in the last example are easy to find an appropiate name for, ECMAScript 6 goes further to ensure that all functions have appropriate names:

var doSomething = function doSomethingElse() {
    // ...
};

var person = {
    get firstName() {
        return "Nicholas"
    },
    sayName: function() {
        console.log(this.name);
    }
}

console.log(doSomething.name);      // "doSomethingElse"
console.log(person.sayName.name);   // "sayName"
console.log(person.firstName.name); // "get firstName"

In this example, doSomething.name is "doSomethingElse" because the function expression itself has a name and that name takes priority over the variable to which the function was assigned. The name property of person.sayName() is "sayName", as the value was interpreted from the object literal. Similarly, person.firstName is actually a getter function, so its name is "get firstName" to indicate this difference (setter functions are prefixed with "set" as well).

There are a couple of other special cases for function names. Functions created using bind() will have their name prefixed with "bound" and functions created using the Function constructor have a name of "anonymous":

var doSomething = function() {
    // ...
};

console.log(doSomething.bind().name);   // "bound doSomething"

console.log((new Function()).name);     // "anonymous"

The name of a bound function will always be the name of the function being bound prefixed with the "bound ", so the bound version of doSomething() is "bound doSomething".

Block-Level Functions

In ECMAScript 3 and earlier, a function declaration occurring inside of a block (a block-level function) was technically a syntax error, but many browsers still supported it. Unfortunately, each browser that allowed the syntax behaved in a slightly different way, so it is considered a best practice to avoid function declarations inside of blocks (the best alternative is to use a function expression).

In an attempt to reign in this incompatible behavior, ECMAScript 5 strict mode introduced an error whenever a function declaration was used inside of a block. For example:

"use strict";

if (true) {

    // Throws a syntax error in ES5, not so in ES6
    function doSomething() {
        // ...
    }
}

In ECMAScript 5, this code throws a syntax error. In ECMAScript 6, the doSomething() function is considered a block-level declaration and can be accessed and called within the same block in which it was defined. For example:

"use strict";

if (true) {

    console.log(typeof doSomething);        // "function"

    function doSomething() {
        // ...
    }

    doSomething();
}

console.log(typeof doSomething);            // "undefined"

Block level functions are hoisted to the top of the block in which they are defined, so typeof doSomething returns "function" even though it appears before the function declaration in the code. Once the if block is finished executing, doSomething() no longer exists.

Block level functions are a similar to let function expressions in that the function definition is removed once execution flows out of the block in which it’s defined. The key difference is that block level functions are hoisted to the top of the containing block while let function expressions are not hoisted. For example:

"use strict";

if (true) {

    console.log(typeof doSomething);        // throws error

    let doSomething = function () {
        // ...
    }

    doSomething();
}

console.log(typeof doSomething);

Here, code execution stops when typeof doSomething is executed because the let statement hasn’t been executed yet.

Whether you want to use block level functions or let expressions depends on whether or not you want the hoisting behavior.

ECMAScript 6 also allows block-level functions in nonstrict mode, but the behavior is slightly different. Instead of hoisting these declarations to the top of the block, they are hoisted all the way to the containing function or global environment. For example:

// ECMAScript 6 behavior
if (true) {

    console.log(typeof doSomething);        // "function"

    function doSomething() {
        // ...
    }

    doSomething();
}

console.log(typeof doSomething);            // "function"

In this example, doSomething() is hoisted into the global scope so that it still exists outside of the if block. ECMAScript 6 standardized this behavior to remove the incompatible browser behaviors that previously existed. ECMAScript 6 runtimes will all behave in the same way.

Arrow Functions

One of the most interesting new parts of ECMAScript 6 are arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an “arrow” (=>). However, arrow functions behave differently than traditional JavaScript functions in a number of important ways:

  • Lexical this binding - The value of this inside of the function is determined by where the arrow function is defined not where it is used.
  • Not newable - Arrow functions cannot be used a constructors and will throw an error when used with new.
  • Can’t change this - The value of this inside of the function can’t be changed, it remains the same value throughout the entire lifecycle of the function.
  • No arguments object - You can’t access arguments through the arguments object, you must use named arguments or other ES6 features such as rest arguments.

There are a few reasons why these differences exist. First and foremost, this binding is a common source of error in JavaScript. It’s very easy to lose track of the this value inside of a function and can easily result in unintended consequences. Second, by limiting arrow functions to simply executing code with a single this value, JavaScript engines can more easily optimize these operations (as opposed to regular functions, which might be used as a constructor or otherwise modified).

Arrow functions also have a name property that follows the same rule as other functions.

Syntax

The syntax for arrow functions comes in many flavors depending upon what you are trying to accomplish. All variations begin with function arguments, followed by the arrow, followed by the body of the function. Both the arguments and the body can take different forms depending on usage. For example, the following arrow function takes a single argument and simply returns it:

var reflect = value => value;

// effectively equivalent to:

var reflect = function(value) {
    return value;
};

When there is only one argument for an arrow function, that one argument can be used directly without any further syntax. The arrow comes next and the expression to the right of the arrow is evaluated and returned. Even though there is no explicit return statement, this arrow function will return the first argument that is passed in.

If you are passing in more than one argument, then you must include parentheses around those arguments. For example:

var sum = (num1, num2) => num1 + num2;

// effectively equivalent to:

var sum = function(num1, num2) {
    return num1 + num2;
};

The sum() function simply adds two arguments together and returns the result. The only difference is that the arguments are enclosed in parentheses with a comma separating them (same as traditional functions).

If there are no arguments to the function, then you must include an empty set of parentheses:

var getName = () => "Nicholas";

// effectively equivalent to:

var getName = function() {
    return "Nicholas";
};

When you want to provide a more traditional function body, perhaps consisting of more than one expression, then you need to wrap the function body in braces and explicitly define a return value, such as:

var sum = (num1, num2) => {
    return num1 + num2;
};

// effectively equivalent to:

var sum = function(num1, num2) {
    return num1 + num2;
};

You can more or less treat the inside of the curly braces as the same as in a traditional function with the exception that arguments is not available.

If you want to create a function that does nothing, then you need to include curly braces:

var doNothing = () => {};

// effectively equivalent to:

var doNothing = function() {};

Because curly braces are used to denote the function’s body, an arrow function that wants to return an object literal outside of a function body must wrap the literal in parentheses. For example:

var getTempItem = id => ({ id: id, name: "Temp" });

// effectively equivalent to:

var getTempItem = function(id) {

    return {
        id: id,
        name: "Temp"
    };
};

Wrapping the object literal in parentheses signals that the braces are an object literal instead of the function body.

Lexical this Binding

One of the most common areas of error in JavaScript is the binding of this inside of functions. Since the value of this can change inside of a single function depending on the context in which it’s called, it’s possible to mistakenly affect one object when you meant to affect another. Consider the following example:

var PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click", function(event) {
            this.doSomething(event.type);     // error
        }, false);
    },

    doSomething: function(type) {
        console.log("Handling " + type  + " for " + this.id);
    }
};

In this code, the object PageHandler is designed to handle interactions on the page. The init() method is called to set up the interactions and that method in turn assigns an event handler to call this.doSomething(). However, this code doesn’t work as intended. The call to this.doSomething() is broken because this is a reference to the element object (in this case document) that was the target of the event, instead of being bound to PageHandler. If you tried to run this code, you will get an error when the event handler fires because this.doSomething() doesn’t exist on the target document object.

You can bind the value of this to PageHandler explicitly using the bind() method on the function:

var PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click", (function(event) {
            this.doSomething(event.type);     // no error
        }).bind(this), false);
    },

    doSomething: function(type) {
        console.log("Handling " + type  + " for " + this.id);
    }
};

Now the code works as expected, but may look a little bit strange. By calling bind(this), you’re actually creating a new function whose this is bound to the current this, which is PageHandler. The code now works as you would expect even though you had to create an extra function to get the job done.

Arrow functions have implicit this binding, which means that the value of this inside of an arrow function is always the same as the value of this in the scope in which the arrow function was defined. For example:

var PageHandler = {

    id: "123456",

    init: function() {
        document.addEventListener("click",
                event => this.doSomething(event.type), false);
    },

    doSomething: function(type) {
        console.log("Handling " + type  + " for " + this.id);
    }
};

The event handler in this example is an arrow function that calls this.doSomething(). The value of this is the same as it is within init(), so this version of the example works similarly to the one using bind(). Even though the doSomething() method doesn’t return a value, it is still the only statement executed necessary for the function body and so there is no need to include braces.

Arrow functions are designed to be “throwaway” functions and so cannot be used to define new types. This is evident by the missing prototype property that regular functions have. If you try to use the new operator with an arrow function, you’ll get an error:

var MyType = () => {},
    object = new MyType();  // error - you can't use arrow functions with 'ne\
w'

Also, since the this value is statically bound to the arrow function, you cannot change the value of this using call(), apply(), or bind().

The concise syntax for arrow functions makes them ideal for use with array processing. For example, if you want to sort an array using a custom comparator, you typically write something like this:

var result = values.sort(function(a, b) {
    return a - b;
});

That’s a lot of syntax for a very simple procedure. Compare that to the more terse arrow function version:

var result = values.sort((a, b) => a - b);

The array methods that accept callback functions such as sort(), map(), and reduce() all can benefit from simpler syntax with arrow functions to change what would appear to be more complex processes into simpler code.

Generally speaking, arrow functions are designed to be used in places where anonymous functions have traditionally been used. They are not really designed to be kept around for long periods of time, hence the inability to use arrow functions as constructors. Arrow functions are best used for callbacks that are passed into other functions, as seen in the examples in this section.

Identifying Arrow Functions

Despite the different syntax, arrow functions are still functions and are identified as such:

var comparator = (a, b) => a - b;

console.log(typeof comparator);                 // "function"
console.log(comparator instanceof Function);    // true

Both typeof and instanceof behave the same with arrow functions as they do with other functions.

Also like other functions, you can still use call(), apply(), and bind(), although the this-binding of the function will not be affected. Here are some examples:

var sum = (num1, num2) => num1 + num2;

console.log(sum.call(null, 1, 2));      // 3
console.log(sum.apply(null, [1, 2]));   // 3

var boundSum = sum.bind(null, 1, 2);

console.log(boundSum());                // 3

In this example, the sum() function is called using call() and apply() to pass arguments as you would with any function. The bind() method is used to create boundSum(), which has its two arguments bound to 1 and 2 so that they don’t need to be passed directly.

Arrow functions are appropriate to use anywhere you’re currently using an anonymous function expression, such as with callbacks.

Summary

Functions haven’t undergone a huge change in ECMAScript 6, but rather, a series of incremental changes that make them easier to work with.

Default function parameters allow you to easily specify what value to use when a particular argument isn’t passed. Prior to ECMAScript 6, this would require some extra code inside of the function to both check for the presence of arguments and assign a different value.

Rest parameters allow you to specify an array into which all remaining parameters should be placed. Using a real array and letting you indicate which parameters to include makes rest parameters a much more flexible solution than arguments.

Destructured parameters use the destructuring syntax to make options objects more transparent when used as function parameters. The actual data you’re interested in can be listed out along with other named parameters.

The spread operator is a companion to rest parameters, allowing you to destructure an array into separate parameters when calling a function. Prior to ECMAScript 6, the only ways to pass individual parameters that were contained in an array were either manually specifying each parameter or using apply(). With the spread operator, you can easily pass an array to any function without worrying about the this binding of the function.

The addition of the name property helps to more easily identify functions for debugging and evaluation purposes. Additionally, ECMAScript 6 formally defines the behavior of block-level functions so they are no longer a syntax error in strict mode.

The biggest change to functions in ECMAScript 6 was the addition of arrow functions. Arrow functions are designed to be used in places where anonymous function expressions have traditionally been used. Arrow functions have a more concise syntax, lexical this binding, and no arguments object. Additionally, arrow functions can’t change their this binding and so can’t be used as constructors.

Objects

This chapter is a work-in-progress. As such, it may have more typos or content errors than others.

A lot of ECMAScript 6 focused on improving the utility of objects. The focus makes sense given that nearly every value in JavaScript is represented by some type of object. Additionally, the number of objects used in an average Javascript program continues to increase, meaning that developers are writing more objects all the time. With more objects comes the necessity to use them more effectively.

ECMAScript 6 improves objects in a number of ways, from simple syntax to new ways of manipulating and interacting with objects.

Object Categories

The ECMAScript 6 specification introduced some new terminology to help distinguish between categories of objects. JavaScript has long been littered with a mix of terminology used to describe objects found in the standard as opposed to those that are added by execution environments such as the browser. ECMAScript 6 takes the time to clearly define each category of object, and it’s important to understand this terminology to have a good understanding of the language as a whole. The object categories are:

  • Ordinary objects are objects that have all of the default internal behaviors for objects in JavaScript.
  • Exotic objects are objects whose internal behavior is different than the default in some way.
  • Standard objects are objects defined by ECMAScript 6, such as Array, Date, etc. Standard objects may be ordinary or exotic.
  • Built-in objects are objects that are present in a JavaScript execution environment when a script begins to execute. All standard objects are built-in objects.

These terms are used throughout the book to explain the various objects defined by ECMAScript 6.

Object Literal Extensions

One of the most popular patterns in JavaScript is the object literal. It’s the syntax upon which JSON is built and can be seen in nearly every JavaScript file on the Internet. The reason for the popularity is clear: a succinct syntax for creating objects that otherwise would take several lines of code to accomplish. ECMAScript 6 recognized the popularity of the object literal and extends the syntax in several ways to make object literals more powerful and even more succinct.

Property Initializer Shorthand

In ECMAScript 5 and earlier, object literals were simply collections of name-value pairs. That meant there could be some duplication when property values are being initialized. For example:

function createPerson(name, age) {
    return {
        name: name,
        age: age
    };
}

The createPerson() function creates an object whose property names are the same as the function parameter names. The result is what appears to be duplication of name and age even though each represents a different aspect of the process.

In ECMAScript 6, you can eliminate the duplication that exists around property names and local variables by using the property initializer shorthand. When the property name is going to be the same as the local variable name, you can simply include the name without a colon and value. For example, createPerson() can be rewritten as follows:

function createPerson(name, age) {
    return {
        name,
        age
    };
}

When a property in an object literal only has a name and no value, the JavaScript engine looks into the surrounding scope for a variable of the same name. If found, that value is assigned to the same name on the object literal. So in this example, the object literal property name is assigned the value of the local variable name.

The purpose of this extension is to make object literal initialization even more succinct than it already was. Assigning a property with the same name as a local variable is a very common pattern in JavaScript and so this extension is a welcome addition.

Method Initializer Shorthand

ECMAScript 6 also improves syntax for assigning methods to object literals. In ECMAScript 5 and earlier, you must specify a name and then the full function definition to add a method to an object. For example:

var person = {
    name: "Nicholas",
    sayName: function() {
        console.log(this.name);
    }
};

In ECMAScript 6, the syntax is made more succinct by eliminating the colon and the function keyword. You can then rewrite the previous example as:

var person = {
    name: "Nicholas",
    sayName() {
        console.log(this.name);
    }
};

This shorthand syntax creates a method on the person object just as the previous example did. There is no difference aside from saving you some keystrokes, so sayName() is assigned an anonymous function expression and has all of the same characteristics as the function defined in the previous example.

The name property of a method created using this shorthand is the name used before the parentheses. In the previous example, the name property for person.sayName() is "sayName".

Computed Property Names

JavaScript objects have long had computed property names through the use of square brackets instead of dot notation. The square brackets allow you to specify property names using variables and string literals that may contain characters that would be a syntax error if used in an identifier. For example:

var person = {},
    lastName = "last name";

person["first name"] = "Nicholas";
person[lastName] = "Zakas";

console.log(person["first name"]);      // "Nicholas"
console.log(person[lastName]);          // "Zakas"

Both of the property names in this example have a space, making it impossible to reference those names using dot notation. However, bracket notation allows any string value to be used as a property name.

In ECMAScript 5, you could use string literals as property names in object literals, such as:

var person = {
    "first name": "Nicholas"
};

console.log(person["first name"]);      // "Nicholas"

If you could provide the string literal inside of the object literal property definition then you were all set. If, however, the property name was contained in a variable or had to be calculated, then there was no way to define that property using an object literal.

ECMAScript 6 adds computed property names to object literal syntax by using the same square bracket notation that has been used to reference computed property names in object instances. For example:

var lastName = "last name";

var person = {
    "first name": "Nicholas",
    [lastName]: "Zakas"
};

console.log(person["first name"]);      // "Nicholas"
console.log(person[lastName]);          // "Zakas"

The square brackets inside of the object literal indicate that the property name is computed, so its contents are evaluated as a string. That means you can also include expressions such as:

var suffix = " name";

var person = {
    ["first" + suffix]: "Nicholas",
    ["last" + suffix]: "Zakas"
};

console.log(person["first name"]);      // "Nicholas"
console.log(person["last name"]);       // "Zakas"

Anything you would be inside of square brackets while using bracket notation on object instances will also work for computed property names inside of object literals.

Object.assign()

One of the most popular patterns for object composition is mixins, in which one object receives properties and methods from another object. Many JavaScript libraries have a mixin method similar to this:

function mixin(receiver, supplier) {
    Object.keys(supplier).forEach(function(key) {
        receiver[key] = supplier[key];
    });

    return receiver;
}

The mixin() function iterates over the own properties of supplier and copies them onto receiver. This allows the receiver to gain new behaviors without inheritance. For example:

function EventTarget() { /*...*/ }
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function() { /*...*/ },
    on: function() { /*...*/ }
}

var myObject = {}
mixin(myObject, EventTarget.prototype);

myObject.emit("somethingChanged");

In this example, myObject receives behavior from EventTarget.prototype. This gives myObject the ability to publish events and let others subscribe to them using emit() and on(), respectively.

This pattern became popular enough that ECMAScript 6 added Object.assign(), which behaves the same way. The difference in name is to reflect the actual operation that occurs. Since the mixin() method uses the assignment operator (=), it cannot copy accessor properties to the receiver as accessor properties. The name Object.assign() was chosen to reflect this distinction.

You can use Object.assign() anywhere the mixin() function would have been used:

function EventTarget() { /*...*/ }
EventTarget.prototype = {
    constructor: EventTarget,
    emit: function() { /*...*/ },
    on: function() { /*...*/ }
}

var myObject = {}
Object.assign(myObject, EventTarget.prototype);

myObject.emit("somethingChanged");

The Object.assign() method accepts any number of suppliers, and the receiver receives the properties in the order in which the suppliers are specified. That means the second supplier might overwrite a value from the first supplier on the receiver. For example:

var receiver = {};

Object.assign(receiver, {
        type: "js",
        name: "file.js"
    }, {
        type: "css"
    }
);

console.log(receiver.type);     // "css"
console.log(receiver.name);     // "file.js"

The value of receiver.type is "css" because the second supplier overwrote the value of the first.

The Object.assign() method isn’t a big addition to ECMAScript 6, but it does formalize a common function that is found in many JavaScript libraries.

Working with Accessor Properties

Keep in mind that you cannot create accessor properties on the receiver by using a supplier with accessor properties. Since Object.assign() uses the assignment operator, an accessor property on a supplier will become a data property on the receiver. For example:

var receiver = {},
    supplier = {
        get name() {
            return "file.js"
        }
    };

Object.assign(receiver, supplier);

var descriptor = Object.getOwnPropertyDescriptor(receiver, "name");

console.log(descriptor.value);      // "file.js"
console.log(descriptor.get);        // undefined

In this code, the supplier has an accessor property called name. After using Object.assign(), receiver.name exists as a data property with the value of "file.js". That’s because supplier.name returned “file.js” at the time Object.assign() was called.

Duplicate Object Literal Properties

ECMAScript 5 strict mode introduced a check for duplicate object literal properties that would throw an error if a duplicate was found. For example:

var person = {
    name: "Nicholas",
    name: "Greg"        // syntax error in ES5 strict mode
};

When running in ECMAScript 5 strict mode, this example results in a syntax error on the second name property.

In ECMAScript 6, the duplicate property check has been removed. This change was made due to the additional complexity of spread arguments in object destructuring. Both strict and nonstrict mode code no longer check for duplicate properties and instead take the last property of the given name as the actual value.

var person = {
    name: "Nicholas",
    name: "Greg"        // not an error in ES6
};

console.log(person.name);       // "Greg"

In this example, the value of person.name is "Greg" because that was the last value assigned to the property.

proto, Object.setPrototypeOf()

TODO

super

toMethod()

TODO

Reflection Methods

TODO

Object.getOwnPropertyDescriptors()

TODO

Object.getPropertyNames()

TODO

Object.getPropertyDescriptor()

TODO

Summary

TODO

Iterators and Generators

This chapter is a work-in-progress. As such, it may have more typos or content errors than others.

Iterators have been used in many programming languages as a way to more easily work with collections of data. In ECMAScript 6, JavaScript adds iterators as an important feature of the language. When coupled with new array methods and new types of collections (such as sets and maps), iterators become even more important for efficient processing of data.

What are Iterators?

Iterators are nothing more than objects with a certain interface. That interface consists of a method called next() that returns a result object. The result object has two properties, value, which is the next value, and done, which is a boolean value that’s true when there are no more values to return. The iterator keeps an internal pointer to a location within a collection of values and, with each call to next(), returns the next appropriate value.

If you call next() after the last value has been returned, the method returns done as true and value contains the return value for the iterator. The return value is not considered part of the data set, but rather a final piece of related data or undefined if no such data exists. (This concept will become clearer in the generators section later in this chapter.)

With that understanding, it’s fairly easy to create an iterator using ECMAScript 5, for example:

function createIterator(items) {

    let i = 0;

    return {
        next: function() {

            let done = (i >= items.length);
            let value = !done ? items[i++] : undefined;

            return {
                done: done,
                value: value
            };

        }
    };
}

let iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

The createIterator() function in this example returns an object with a next() method. Each time the method is called, the next value in the items array is returned as value. When i is 4, items[i++] returns undefined and done is true, which fulfills the special last case for iterators in ECMAScript 6.

ECMAScript 6 makes use of iterators in a number of places to make dealing with collections of data easier, so having a good basic understanding allows you to better understand the language as a whole.

for-of

The first place you’ll see iterators in ECMAScript 6 is with the new for-of loop. The for-of loop works with iterators to return each successive value. The loop itself calls next() behind the scenes and exits when the done property of the returned object is true. For example:

let iterator = createIterator([1, 2, 3]);

for (let i of iterator) {
    console.log(i);
}

This code outputs the following:

1
2
3

The for-of loop in this example is calling iterator.next() and assigning the variable i to the value returned on the value property. So i is first 1, then 2, and finally 3. When done is true, the loop exits, so i is never assigned the value of undefined.

The for-of statement will throw an error when used on a null or undefined iterator.

Generators

You might be thinking that iterators sound interesting but they look like a bunch of work. Indeed, writing iterators so that they adhere to the correct behavior is a bit difficult, which is why ECMAScript 6 provides generators. A generator is a special kind of function that returns an iterator. Generator functions are indicated by inserting a star character (*) after the function keyword (it doesn’t matter if the star is directly next to function or if there’s some whitespace between them). The yield keyword is used inside of generators to specify the values that the iterator should return when next() is called. So if you want to return three different values for each successive call to next(), you can do so as follows:

For example:

// generator
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}

// generators are called like regular functions but return an iterator
let iterator = createIterator();

for (let i of iterator) {
    console.log(i);
}

This code outputs the following:

1
2
3

In this example, the createIterator() function is a generator (as indicated by the * before the name) and it’s called like any other function. The value returned is an object that adheres to the iterator pattern. Multiple yield statements inside the generator indicate the progression of values that should be returned when next() is called on the iterator. First, next() should return 1, then 2, and then 3 before the iterator is finished.

Perhaps the most interesting aspect of generator functions is that they stop execution after each yield statement, so yield 1 executes and then the function doesn’t execute anything else until the iterator’s next() method is called. At that point, execution resumes with the next statement after yield 1, which in this case is yield 2. This ability to stop execution in the middle of a function is extremely powerful and lends to some interesting uses of generator functions (discussed later in this chapter).

The yield keyword can be used with any value or expression, so you can do interesting things like use yield inside of a for loop:

function *createIterator(items) {
    for (let i=0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);

for (let i of iterator) {
    console.log(i);
}

In this example, an array is iterated over, yielding each item as the loop progresses. Each time yield is encountered, the loop stops, and each time next() is called on iterator, the loop picks back up where it left off.

Of course, you can still call iterator.next() directly:

function *createIterator(items) {
    for (let i=0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

Of course, collections such as arrays naturally lend themselves to iteration, and that’s why ECMAScript 6 has a number of built-in iterators.

Built-in Iterators

Another way that ECMAScript 6 makes using iterators easier is by making iterators available on many objects by default. You don’t actually need to create your own iterators for many of the built-in types because the language has them already. You only need to create iterators when you find that the built-in ones don’t serve your purpose.

Collection Iterators

The ECMAScript 6 collection objects, arrays, maps, and sets, all have three default iterators to help you navigate data. You can retrieve an iterator for the array by calling one of these methods:

  • entries() - returns an iterator whose values are a key-value pair.
  • values() - returns an iterator whose values are the values of the collection.
  • keys() - returns an iterator whose values are the keys contained in the collection.

The entries() iterator actually returns a two-item array where the first item is the key and the second item is the value. For arrays, the first item is the numeric index; for sets, the first item is also the value (since values double as keys in sets). Here are some examples:

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

for (let entry of colors.entries()) {
    console.log(entry);
}

for (let entry of tracking.entries()) {
    console.log(entry);
}

for (let entry of data.entries()) {
    console.log(entry);
}

This example outputs the following:

[0, "red"]
[1, "green"]
[2, "blue"]
[1234, 1234]
[5678, 5678]
[9012, 9012]
["title", "Understanding ECMAScript 6"]
["format", "ebook"]

The values() iterator simply returns the values as they are stored in the collection. For example:

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

for (let value of colors.values()) {
    console.log(value);
}

for (let value of tracking.values()) {
    console.log(value);
}

for (let value of data.values()) {
    console.log(value);
}

This example outputs the following:

"red"
"green"
"blue"
1234
5678
9012
"Understanding ECMAScript 6"
"ebook"

In this case, using values() returns the exact data contained in the value property returned from next().

The keys() iterator returns each key present in the collection. For arrays, this is the numeric keys only (it never returns other own properties of the array); for sets, the keys are the same as the values and so keys() and values() return the same iterator.

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

for (let key of colors.keys()) {
    console.log(key);
}

for (let key of tracking.keys()) {
    console.log(key);
}

for (let key of data.keys()) {
    console.log(key);
}

This example outputs the following:

0
1
2
1234
5678
9012
"title"
"format"

Additionally, each collection type has a default iterator that is used by for-of whenever an iterator isn’t explicitly specified. The default iterator for array and set is values() while the default iterator for maps is entries(). This makes it a little bit easier to use collection objects in for-of:

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

// same as using colors.values()
for (let value of colors) {
    console.log(value);
}

// same as using tracking.values()
for (let num of tracking) {
    console.log(num);
}

// same as using data.entries()
for (let entry of data) {
    console.log(entry);
}

This example outputs the following:

"red"
"green"
"blue"
1234
5678
9012
["title", "Understanding ECMAScript 6"]
["format", "ebook"]

String Iterators

TODO

NodeList Iterators

TODO

Advanced Functionality

TODO

Passing Arguments to Iterators

TODO

Delegating Generators

TODO

Asynchronous Task Scheduling

TODO

Summary

TODO

Symbols

This chapter is a work-in-progress. As such, it may have more typos or content errors than others.

ECMAScript 6 symbols began as a way to create private object members, a feature JavaScript developers have long wanted. The focus was around creating properties that were not identified by string names. Any property with a string name was easy picking to access regardless of the obscurity of the name. The initial “private names” feature aimed to create non-string property names. That way, normal techniques for detecting these private names wouldn’t work.

The private names proposal eventually evolved into ECMAScript 6 symbols. While the implementation details remained the same (non-string values for property identifiers), TC-39 dropped the requirement that these properties be private. Instead, the properties would be categorized separately, being non-enumerable by default but still discoverable.

Symbols are actually a new kind of primitive value, joining strings, numbers, booleans, null, and undefined. They are unique among JavaScript primitives in that they do not have a literal form. The ECMAScript 6 standard uses a special notation to indicate symbols, prefixing the identifier with @@, such as @@create. This book uses this same convention for ease of understanding.

Despite the notation, symbols do not exactly map to strings beginning with “@@”. Don’t try to use string values where symbols are required.

Creating Symbols

You can create a symbol by using the Symbol function, such as:

var firstName = Symbol();
var person = {};

person[firstName] = "Nicholas";
console.log(person[firstName]);     // "Nicholas"

In this example, the symbol firstName is created and used to assign a new property on person. That symbol must be used each time you want to access that same property. It’s a good idea to name the symbol variable appropriately so you can easily tell what the symbol represents.

Because symbols are primitive values, new Symbol() throws an error when called. It’s not possible to create an instance of Symbol, which also differentiates it from String, Number, and Boolean.

The Symbol function accepts an optional argument that is the description of the symbol. The description itself cannot be used to access the property but is used for debugging purposes. For example:

var firstName = Symbol("first name");
var person = {};

person[firstName] = "Nicholas";

console.log("first name" in person);        // false
console.log(person[firstName]);             // "Nicholas"
console.log(firstName);                     // "Symbol(first name)"

A symbol’s description is stored internally in a property called [[Description]]. This property is read whenever the symbol’s toString() method is called either explicitly or implicitly (as in this example). It is not otherwise possible to access [[Description]] directly from code. It’s recommended to always provide a description to make both reading and debugging code using symbols easier.

Identifying Symbols

Since symbols are primitive values, you can use the typeof operator to identify them. ECMAScript 6 extends typeof to return "symbol" when used on a symbol. For example:

var symbol = Symbol("test symbol");
console.log(typeof symbol);         // "symbol"

While there are other indirect ways of determining whether a variable is a symbol, typeof is the most accurate and preferred way of doing so.

Using Symbols

You can use symbols anywhere you would use a computed property name. You’ve already seen the use of bracket notation in the previous sections, but you can use symbols in computed object literal property names as well as with Object.defineProperty(), and Object.defineProperties(), such as:

var firstName = Symbol("first name");
var person = {
    [firstName]: "Nicholas"
};

// make the property read only
Object.defineProperty(person, firstName, { writable: false });

var lastName = Symbol("last name");

Object.defineProperties(person, {
    [lastName]: {
        value: "Zakas",
        writable: false
    }
});

console.log(person[firstName]);     // "Nicholas"
console.log(person[lastName]);      // "Zakas"

With computer property names in object literals, symbols are very easy to work with.

Sharing Symbols

You may find that you want different parts of your code to use the same symbols. For example, suppose you have two different object types in your application that should use the same symbol property to represent a unique identifier. Keeping track of symbols across files or large codebases can be difficult and error-prone. That’s why ECMAScript 6 provides a global symbol registry that you can access at any point in time.

When you want to create a symbol to be shared, use the Symbol.for() method instead of calling Symbol(). The Symbol.for() method accepts a single parameter, which is a string identifier for the symbol you want to create (this value doubles as the description). For example:

var uid = Symbol.for("uid");
var object = {};

object[uid] = "12345";

console.log(object[uid]);       // "12345"
console.log(uid);               // "Symbol(uid)"

The Symbol.for() method first searches the global symbol registry to see if a symbol with the key "uid" exists. If so, then it returns the already existing symbol. If no such symbol exists, then a new symbol is created and registered into the global symbol registry using the specified key. The new symbol is then returned. That means subsequent calls to Symbol.for() using the same key will return the same symbol:

var uid = Symbol.for("uid");
var object = {
    [uid]: "12345"
};

console.log(object[uid]);       // "12345"
console.log(uid);               // "Symbol(uid)"

var uid2 = Symbol.for("uid");

console.log(uid === uid2);      // true
console.log(object[uid2]);      // "12345"
console.log(uid2);              // "Symbol(uid)"

In this example, uid and uid2 contain the same symbol and so they can be used interchangeably. The first call to Symbol.for() creates the symbol and second call retrieves the symbol from the global symbol registry.

Another unique aspect of shared symbols is that you can retrieve the key associated with a symbol in the global symbol registry by using Symbol.keyFor(), for example:

var uid = Symbol.for("uid");
console.log(Symbol.keyFor(uid));    // "uid"

var uid2 = Symbol.for("uid");
console.log(Symbol.keyFor(uid2));   // "uid"

var uid3 = Symbol("uid");
console.log(Symbol.keyFor(uid3));   // undefined

Notice that both uid and uid2 return the key "uid". The symbol uid3 doesn’t exist in the global symbol registry, so it has no key associated with it and so Symbol.keyFor() returns undefined.

The global symbol registry is a shared environment, just like the global scope. That means you can’t make assumptions about what is or is not already present in that environment. You should use namespacing of symbol keys to reduce the likelihood of naming collisions when using third-party components. For example, jQuery might prefix all keys with "jquery.", such as "jquery.element".

Finding Object Symbols

As with other properties on objects, you can access symbol properties using the Object.getOwnPropertySymbols() method. This method works exactly the same as Object.getOwnPropertyNames() except that the returned values are symbols rather than strings. Since symbols technically aren’t property names, they are omitted from the result of Object.getOwnPropertyNames().

The return value of Object.getOwnPropertySymbols() is an array of symbols that represent own properties. For example:

var uid = Symbol.for("uid");
var object = {
    [uid]: "12345"
};

var symbols = Object.getOwnPropertySymbols(object);

console.log(symbols.length);        // 1
console.log(symbols[0]);            // "Symbol(uid)"
console.log(object[symbols[0]]);    // "12345"

In this code, object has a single symbol property. The array returned from Object.getOwnPropertySymbols() is an array containing just that symbol.

All objects start off with zero own symbol properties (although they do have some inherited symbol properties).

Well-Known Symbols

In addition to the symbols you defined, there are some predefined symbols as well (called well-known symbols in the specification). These symbols represent common behaviors in JavaScript that were previously considered internal-only operations. Each well-known symbol is represented by a property on Symbol, such as Symbol.create for the @@create symbol.

A central theme for both ECMAScript 5 and ECMAScript 6 was exposing and defining some of the “magic” parts of JavaScript - the parts that couldn’t be emulated by a developer. ECMAScript 6 follows this tradition by exposing even more of the previously internal-only logic of the language. It does so primarily through the use of symbol prototype properties that define the basic behavior of certain objects.

Overwriting a method defined with a well-known symbol changes an ordinary object to an exotic object because this changes some internal default behavior.

@@toStringTag

Once of the most interesting problems in JavaScript has been the availability of multiple global execution environments. This occurs in web browsers when a page includes an iframe, as the page and the iframe each have their own execution environments. In most cases, this isn’t a problem, as data can be passed back and forth between the environments with little cause for concern. The problem arises when trying to identify what type of an object you’re dealing with.

The canonical example of this is passing an array from the iframe into the containing page or vice-versa. Now in a different execution environment, instanceof Array returns false because the array was created with a constructor from a different environment.

Developers soon found a good way to identify arrays. It was discovered that by calling the standard toString() method on the object, a predictable string was always returned. Thus, many JavaScript libraries began including a function that works similar to this:

function isArray(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
}

console.log(isArray([]));   // true

This may look a bit roundabout, but in reality it was found to work quite well in all browsers. The toString() method on arrays isn’t very useful for this purpose because it returns a string representation of the items it contains. The toString() method on Object.prototype, however, had this quirk where it included some internally-defined name in the result. By using this method on an object, you could retrieve what the JavaScript environment thought the data type was.

Developers quickly realized that since there was no way to change this behavior, it was possible to use the same approach to distinguish between native objects and those created by developers. The most important case of this was the ECMAScript 5 JSON object.

Prior to ECMAScript 5, many used Douglas Crockford’s json2.js, which created a global JSON object. As browsers started to implement the JSON global object, it became necessary to tell whether the global JSON was provided by the JavaScript environment itself or through some other library. Using the same technique, many created functions like this:

function supportsNativeJSON() {
    return typeof JSON !== "undefined" &&
        Object.prototype.toString.call(JSON) === "[object JSON]";
}

Here, the same characteristic that allowed developers to identify arrays across iframe boundaries also provided a way to tell if JSON was the native one or not. A non-native JSON object would return [object Object] while the native version returned [object JSON]. From that point on, this approach became the de facto standard for identifying native objects.

ECMAScript 6 explains this behavior through the @@toStringTag symbol. This symbol represents a method on each object that defines what value should be produced when Object.prototype.toString.call() is called on it. So the value returned for arrays is explained by having the @@toStringTag method return "Array". Likewise, you can define that value for your own objects:

function Person(name) {
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = function() {
    return "Person";
};

var me = new Person("Nicholas");

console.log(me.toString());                         // "[object Person]"
console.log(Object.prototype.toString.call(me));    // "[object Person]"

In this example, a @@toStringTag method is defined on Person.prototype to provide the default behavior for creating a string representation. Since Person.prototype inherits Object.prototype.toString(), the value returned from @@toStringTag is also used when calling me.toString(). However, you can still define your own toString() that provides a different behavior without affecting the use of Object.prototype.toString.call():

function Person(name) {
    this.name = name;
}

Person.prototype[Symbol.toStringTag] = function() {
    return "Person";
};

Person.prototype.toString = function() {
    return this.name;
};

var me = new Person("Nicholas");

console.log(me.toString());                         // "Nicholas"
console.log(Object.prototype.toString.call(me));    // "[object Person]"

This code defines Person.prototype.toString() to return the value of the name property. Since Person instances no longer inherit Object.prototype.toString(), calling me.toString() exhibits a different behavior.

All objects inherit @@toStringTag from Object.prototype unless otherwise specified. This default method returns "Object".

@@toPrimitive

JavaScript frequently attempts to convert objects into primitive values implicitly when certain operations are applied. For instance, when you compare a string to an object using double equals (==), the object is converted into a primitive value before comparing. Exactly what value should be used was previously an internal operation that is exposed in ECMAScript 6 through the @@toPrimitive method.

The @@toPrimitive method is defined on the prototype of each standard type and prescribes the exact behavior. When a primitive conversion is needed, @@toPrimitive is called with a single argument, referred to as hint in the specification. The hint argument is "default", specifying that the operation has no preference as to the type, "string", indicating a string should be returned, or "number", if a number is necessary to perform the operation. Most standard objects treat "default" as equivalent to "number" (except for Date, which treats "default" as "string").

TODO

@@isConcatSpreadable

TODO

@@unscopeables

TODO

Only applied with with statement object records - does not refer to other scopes.

@@isRegExp

TODO

@@iterator

TODO

@@create

TODO

@@hasInstance

TODO

Summary

TODO