Web developers constantly look for the latest and greatest ways to hone their craft, but changes come fast. From jQuery to Angular to Ember to React, CoffeeScript to TypeScript, it seems there is always something new. But ES6 is something different. With ES6 we are seeing the evolution of core JavaScript. It includes syntactic improvements and great new features never before seen in client-side code. Linters and transpilers for ES6 are readily available and easy to use. There is no need to wait; learn how to leverage the power of "the new JavaScript" in your applications, today!
26. var myStringArray = new Array("blue");
// ["blue"]
var myBoolArray = new Array(false);
// [false]
var myIntArray = new Array(2);
// [undefined x 2]
ES5:
27. var myIntArray = new Array(2);
// [undefined x 2]
ES5:
var fixedIntArray = Array.of(2);
// [2]
ES6:
32. var body = ['head', 'shoulders', 'knees', 'toes'];
for (var i = 0; i < body.length; i++) { tap(body[i]) };
ES5:
33. var body = ['head', 'shoulders', 'knees', 'toes'];
for (var i = 0; i < body.length; i++) { tap(body[i]) };
ES5:
var body = ['head', 'shoulders', 'knees', 'toes'];
for (var part of body) {
tap(part);
}
ES6:
34. var alphabet = 'abcdefg';
for (var letter of alphabet) {
sing(letter);
}
ES6:
E
40. var x, y, z, coords;
coords = [29, 22, 37];
x = coords[0];
y = coords[1];
z = coords[2];
ES5:
41. var x, y, z, coords;
coords = [29, 22, 37];
x = coords[0];
y = coords[1];
z = coords[2];
ES5:
var coords = [29, 22, 37];
var [x, y, z] = coords;
ES6:
42. var x, y, z, coords;
coords = [29, 22, 37];
x = coords[0];
y = coords[1];
z = coords[2];
ES5:
var coords = [29, 22, 37];
var [x, y, z] = coords;
var [foo, , bar] = coords;
ES6:
43. var user, email, display;
user = { name: 'Jeff', email: 'jeff@aranasoft.com' };
email = user.email;
display = user.name;
ES5:
44. var user, email, display;
user = { name: 'Jeff', email: 'jeff@aranasoft.com' };
email = user.email;
display = user.name;
ES5:
var user = { name: 'Jeff', email: 'jeff@aranasoft.com' };
var {email} = user;
var {name: display} = user;
ES6:
55. var foo = function(bar) {
if (bar) {
(function() {
var message = 'Hello!'; // declared here
alert(message);
})();
}
return message; // ReferenceError: message is not defined
};
ES5:
57. // assume links has an array of DOM elements
for (var i = 0; i < links.length; i++) {
links[i].onclick = function() {
console.log(i);
};
}
// whoops! all links log the max index!
ES5:
58. // assume links has an array of DOM elements
for (var i = 0; i < links.length; i++) {
links[i].onclick = (function(x) {
return function() {
console.log(x); // preserved context
};
})(i);
}
// clicking links gives the correct result
ES5:
64. function(bar) {
if (bar) {
var message = 'Hello!'; // declared here
alert(message);
}
return message; // still in scope here
};
ES5:
65. function(bar) {
if (bar) {
let message = 'Hello!'; // declared here
alert(message);
}
return message; // ReferenceError: message is not defined
};
ES6:
66. // assume links has an array of DOM elements
for (var i = 0; i < links.length; i++) {
links[i].onclick = function() {
console.log(i);
};
};
// whoops! all links log the max index!
ES5:
67. // assume links has an array of DOM elements
for (let i = 0; i < links.length; i++) {
links[i].onclick = function() {
console.log(i);
};
};
// all better, with no IIFE or extra closure!
ES6:
102. @ j e f f r e y S t r a u s s
# d e v E S 6
jeff@aranasoft.com
Editor's Notes
ECMAScript (ECMA-262) is a standard or specification. It is not, in and of itself, a programming language.
JavaScript is a programming language, and just one implementation—admittedly, by far the most widespread—of the ECAMScript specification.
But that does not mean that every feature of ECMAScript is implemented properly in JavaScript; nor does it mean that every JavaScript engine, across all browsers, platforms, and runtimes, has full feature parity.
You have seen this yourself in your own experiences with different browsers having different compatibility.
Specifically, "ES6" is the 6th Edition of the ECMA-262 standard. It has also been called by other names, including ECMAScript 2015 and ES Harmony.
Why be so pedantic about the distinction between specification and implementation?
This is NOT a new language. It is an evolution and growth of the existing standard. Much of it is stylistic. Your existing ES5 code and patterns will NOT break. It is not like changing from SQL to MongoDB, or even Angular to React. So relax.
In reviewing this talk, if you find only a couple features that will improve your productivity or your codebase, then great! Use those. You don't have to adopt the whole feature set, any more than you use every single feature of C#, Java, or Ruby, today.
Let's begin at the beginning...
This will look a bit familiar to you if you come from Ruby or if you have spent any amount of time with C# 6 and .NET 4.6.
Most people have written code like this, concatenating fragments to build strings. It is annoying and error-prone.
ES6 introduces backtick-enclosed strings, which signify to the runtime that the strings should be interpolated.
Within an interpolated string, you may include tokens. These may be simply variables; or they can contain any valid expression that may be evaluated and returns a resulting value.
Williams, William Carlos, "XXII", Spring and All (New York: Contact Editions / Dijon: Maurice Darantière, 1923).
NOTE: Be mindful of your spacing, because *all* whitespace is now respected, including any indentation on lines 2 and below.
Williams, William Carlos, "XXII", Spring and All (New York: Contact Editions / Dijon: Maurice Darantière, 1923).
This is a common way of handling "default" argument values today. Using the logical OR operator, if a value is not passed in, then the function will resort to the backup.
Second example fails because `0` is falsy. Lots of things in JavaScript coerce to false, including:
• null
• undefined
• 0
• empty string ("")
In ES6, we can assign default parameter values in much the same way we have seen in other languages.
Using the available ES6 default parameters in the function definition, you can avoid the falsiness test altogether.
ES6 adds a variety of new native collection types, such as: Maps, Sets, Typed Arrays, WeakSets, and WeakMaps. All of these are worth pursuing in independent research and have their unique use cases.
The existing JavaScript Array constructor is overloaded. If you pass an integer argument, it treats it as a LENGTH and creates an array filled with undefined.
Worse, if you pass it a floating-point numeric value, it throws an exception.
Using ES6's Array.of(items) will create an Array and avoid this confusion.
Using the built-in `arguments` collection in ES5 and earlier has some benefits. However, it is NOT a true array, but rather an array-like collection. It contains a length property and index-based access, but nothing else.
Converting it to a real Array requires going through messy access to the Prototype.
Array.from(collection) can take any array-like collection and return a new Array.
These new Array functions will work with any "iterable" collection of values. The idea of the @iterable pattern is new in ES6. You can define your own iterator functions for your objects, if you like, or use any of the native types.
Traditional `for` loops are cumbersome, and also have scope problems with the iterator/counter.
The Array.forEach function is nice, but is available ONLY for true Arrays, not other array-like or iterable collections.
The new for-of loop cycles through the collection's iterator, emitting each value until the collection is complete. Much like iterating over an IEnumerable in C#.
The new loop—and a number of other features—are usable by ANY iterable object. Even things not ordinarily considered "collections," like strings.
The "spread" operator has the effect of spinning through an entire iterable and "spreading" or "splatting" the whole thing out into a list of results all at once.
This is similarly used, as a "rest" operator, in destructuring, which is discussed in the next section.
In this example, the spread operator can be used to pass an unspecified set of arguments into a function. This would work even if the function definition has specific arguments. The spread result will be applied to those named parameters.
Destructuring is using an array- or object-like pattern to extract only the portions of data that you want from a more complex object, and assigning the values to one or more variables.
Notice in this slide and the next that the thing that looks like an "array" or "object literal" is actually the * l-value * on the left-hand side of the assignment operator. This should look unusual to you.
The result is not a new array of [x, y, z], but rather three separate variables assigned the values at position 0, 1, and 2 of the coords array.
You can use a pattern, as shown here, to take only the first and third elements from the array and ignore the one between them.
The examples here scan the `user` object for properties called email and name. If they are found, the values are assigned to new variables. If not, then undefined is assigned. For `name`, the value in user.name is assigned to the new variable called `display`.
No need for the use of a temporary variable to hold one value. They can be swapped using destructuring.
Using destructuring, the function can return an array of values, or a complex object literal, and those multiple return values can be destructured and assigned to a new set of independent variables.
Traditionally, in JavaScript, variables are not block-scoped, meaning they do not automatically go out of scope at a closing curly brace.
Instead, JavaScript (and the ECMAScript standard) are function-scoped. All variables survive for the duration of the functions within which they are declared.
The variable called message does not go out of scope until the END of the function foo. The IF block has nothing to do with its scope.
One "traditional" workaround to limit scope is to utilize IIFEs in our code.
Wrapping the block of code within a function limits scope. There are parentheses to signify to the parser that this is an expression and not a function declaration. Then, the second set of parentheses invokes the method immediately.
Causes message to go out of scope when the anonymous IIFE has completed.
However, this syntax is confusing, inelegant, and can seem arbitrary.
IIFE is one kind of closure. Generally, closure is a special pattern that combines two things: a function, and the environment in which that function was created.
Sometimes variables in the outer execution context continue to change prior to the closure's execution.
This doesn't work. Each one, on click, shows the max index, not its own. This is because the variable `i` is scoped to the global (or parent) scope.
This confusing code is using another kind of IIFE and closure. This time, the closure returns a new anonymous function, creating the variable `x` that closes over each iteration of `i` from the loop. This works, but again, it is confusing.
This is legal in JavaScript, even though the variables are accessed seemingly before they are declared lexically in the code.
The previous example is equivalent—and in fact executed—like this. The variable declarations are "hoisted" to the top of the enclosing function scope and are then accessible, but undefined, until initialized to a value.
If you forget to ever declare the variable, it may become global, or at least will bubble up to the parent scope. Not desirable.
Using let to declare your variables addresses all of these scope issues.
Here is one of our examples from before.
Changing to use `let` restricts the scope to the immediately surrounding block.
Another example from before the previously required a complicated IIFE and closure to hold each copy of `i` for future execution.
The temporal dead zone is a rather morbid name, but refers to the period between the opening of scope and the lexical declaration of a variable. While the parser is aware of the variables existing, the runtime acts as though it does not.
If you use let to declare variables instead of var, then there is no hoisting. The variables may not be accessed until declared.
Unlike CoffeeScript, there is only "fat arrow" and no "skinny arrow" syntax available.
Note that arrow functions are always anonymous and so they must be assigned to variables, unless they are intended to be executed immediately or as anonymous callbacks.
If your method has a single statement that returns a value, then a function expression is used instead of an enclosed code block.
Notice the function expression in the ES6 example does not include the `return` keyword.
Other examples. An empty argument list still requires empty parentheses. The return expression may use interpolated strings, or even other function calls.
In the case of a single-parameter function, the parentheses (in this case, around `name`) are optional. However, for consistency, I prefer to use them.
If your function expression returns an object literal, then surrounding the objection parentheses is REQUIRED, because otherwise the parser interprets the opening brace as the start of a code block.
All arrow functions bind `this` lexically, meaning its `this` is based upon the surrounding scope in which it is physically contained within the code.
Assume an object with a name property and a greet function.
Calling greet immediately greets `this.name`, which is based on the surrounding object.
After three seconds, a timeout callback is executed, but the callback is executed in the global execution context, and so `this` is not bound to `person` and `this.name` is lost.
One way to fix this is assigning `this` to something like that, or self, or even _this.
Another option is calling the Function prototype's bind() method to bind a specified value—in this case, the surrounding context's `this` to the inner function's `this`, no matter when it happens to be executed.
Rather than doing either of these, using an ES6 arrow function for the callback binds `this` lexically, meaning it is ALWAYS bound to the `person` object in which the function is shown in the code.
This site is a great resource for seeing the progress and compatibility status for features, not only in the current spec, but also in future anticipated feature sets and releases. However, it is VERY thorough and overwhelming.
The next slide has a bit.ly for it.
My favorite candidate for "worst and most overwhelming slide of all time"!
Find current versions of this chart at: bit.ly/es6-compat
Babel, with all of its many variations, is frequently called a transpiler. It converts your code that uses the new ES6 features into ES5-compliant JavaScript.
Starting with v.6.0, you need to provide preset transform configuration to babel.
You do not have to use the universal babel-preset-2015. There are individual components, so if you want to exclude `class` or `shorthand properties` or whatever, you are able to pick and choose.
You are by no means limited to the command-line interface. Babel plugins exist for a tremendous variety of development environments, testing platforms, build tools, etc. There is likely a good fit for you.
Chrome Canary is a GREAT developers' browser. It is not recommended for testing production deployment, but as a leading-edge beta browser, it has nearly complete compatibility with ES6 features, for testing how things work.
The babel website has an easy-to-use REPL ("read-evaluate-print loop") for providing ES6 code and getting real-time transpiring to ES5 JavaScript.