Overview
I have managed to avoid most of the gnarly details of javascript beyond ES5 for many years. Unfortunately a side project has driven me to finally take it on board. These are my notes, gleaned mostly by reading ES6 In Depth from Jason Orendorff of Mozilla.
I now know enough to be more dangerous.
- Iterators
- Generators
- String interpolation
- Variadic and default arguments
- Destructuring
- Arrow functions
- Symbols
- Collections
- Proxies
- Classes
- var, let and const
- Modules, import/export
- Links
Iterators
The of
loop operator is like in
but you don't need to filter out elements with hasOwnProperty
for (var value of myCollection) {
console.log(value);
}
myCollection
could be an array []
, "a string"
, Map
's andSet
's (or any generator, see next section). To iterate over an object's keys you'd have to use Object.keys()
Generators
function* someGenerator(any, args) {
yield "any type of return value" + any;
console.log(args);
yield "as many as you like";
}
Keywords here are function*
specifically the *
suffix ( this is a generator) and yield
(generate a return a value). Additionally it is possible to yield all the values of another generator using yield*
.
function* anotherGenerator(any, args) {
// all of these are returned
yield* someGenerator(any, args);
// and then this one
yield "as many as you like";
}
The actual return type of a generator function is Object.Generator
. Thought you can skip direct use of this type by using the for of
loops.
var g = someGenerator();
var item = g.next();
// possible values for item
// { value: some_value, done: false }
// { value: undefined, done: true }
When done
boolean is false
there is more data, The value is whatever yield
returned from the generator.
Generators run in the callers thread. They can get more complicated but frankly until I've used them in the wild I can live without that.
String interpolation
Back-ticks can be used to quote strings (with line breaks too),
var aString = `any text
with line breaks`;
but more importantly and contain variables (and expressions) to expand and can span lines (note the ${...}
),
var u = {name:"Adam",isAdmin:false};
console.log(`User: ${u.name} ${u.isAdmin?"admin":"not-admin"}`);
// User: Adam not-admin
Escape with '\'
for both ‘$’ and ‘`’.
Tagged template literals
Prefix a backtick string with a tag. The tag is a function pointer that is designed to operate during string expansion.
function myTagger(template, ...args) {
var result = "";
for (var i = 0; i < args.length; i++) {
if (i < template.length) {
result += template[i];
}
result += "*" + args[i] + "*";
}
return result;
}
var v1 = 2, v2 = "four", v3 = "last";
console.log(myTagger`one ${v1} three ${v2} ${v3}`);
// one *2* three *four* *last*
...args
is variadic, explained in the next section (but it does what you expect).
myTagger
could make the arguments uppercase, or force a particular date or decimal layout.
More details here.
Variadic and default arguments
Variadic (aka rest) arguments
Works the way you want them to, prefix the last argument with ...
indexed from the variadic parameter not from the first argument. This works with arrow functions too, unlike the built in arguments
variable that works with functions but not arrow functions.
function fn(one, two, ...theRest){
console.log(`one=${one}, two=${two}`)
for (var i = 0; i < theRest.length; i++) {
console.log(`theRest[${i}]=${theRest[i]}`)
}
}
fn(1,2,"three","four",5);
// one=1, two=2
// theRest[0]=three
// theRest[1]=four
// theRest[2]=5
The opposite of rest parameters is spread, which expands any iterable value into separate parameters.
var f = (p1, ...pr) => {console.log(p1, pr);};
var a = [1, 2, 3];
f(99, a); // p1===99, pr[0]===a
f(...a); // p1===1, p[0]===2, p[1]===3
Also, with arrays
var [head, ...tail] = [1, 2, 3, 4];
var reJoined = [head, ...tail];
Defaults for function arguments
Important differences with javascript defaults compared to C# or Java:
- Can be anywhere in the argument list
- Defaults are evaluated at call time
- Arguments are evaluated left to right (see
arg3
andarg4
below)
var fnX() => "some-result";
function fn (
arg1 = "some-default",
arg2,
arg3 = (arg2 ? "arg1 is set" : "arg1 is not set"),
arg4 = fnX()
) {
console.log(`${arg1} | ${arg2} | ${arg3} | ${arg4}`);
}
fn();
// some-default | undefined | arg2 is not set | some-result
fn("v1");
// v1 | undefined | arg1 is not set | some-result
fn(undefined, "replacement");
// some-default | replacement | arg1 is set | some-result
fn(1, 2, 3, 4);
// 1 | 2 | 3 | 4
Destructuring
This feature allows extracting one or values from an object or array.
Destructuring Arrays
// pull the values from an array to separate variables
var z = [10, -99, 20, 30];
// note the blank skips the value '-99
var [a, /*this is a blank space*/ , b, c, d] = z;
// note d is undefined
console.log(`a=${a} b=${b}, c=${c}, d=${d}`);
// a=10 b=20, c=30, d=undefined
var [first, ...theRest] = z;
console.log(`first=${first}, theRest=${theRest}`);
// first=10, theRest=-99,20,30
Also for swapping variables,
var a = 69;
var b = 42;
console.log(a, b);
[a, b] = [b, a];
console.log(a, b);
Destructuring Objects
var customer = {name:"Adam", job:"typist", age: 21};
// new variable different name for attribute
var {name: n} = customer;
// local variable with same name as object
// pull more than one property
var {job, age, missing, use_default_if_missing = 99} = customer;
console.log(n, job, age, missing, use_default_if_missing);
// Adam typist 21 undefined 99
Push this idea further and use for function arguments and multiple function returns.
var fn = ({url, someOption = true}) => {
console.log(`url:${url}, option:${someOption}`);
return [10, "ok"];
};
var args = {url : "a-url"};
var [a, b] = fn(args);
// url: a-url, option: true
console.log(a, b);
// 10, ok
Given destructuring we can return multiple values from a function and de-structure them to single values either through an object or array
Destructuring Maps
Given the new type, Map
and the iterator with of
we can do this,
var map = new Map();
map.set("key1", "value1");
map.set(42, "value2");
for (var [key] of map) {
console.log("only keys", key);
}
// only keys key1
// only keys 42
for (var [, value] of map) {
console.log("only values", value);
}
// only values value1
// only values value2
Arrow functions
Work as expected, and as described above. However,
this
behaves differently with arrow functionsarguments
magic variable is not available in arrow functions
function literals
var obj = {
value: 10,
fnES5 : function(x) { return this.value; },
fnES6(x) { return this.value; },
// arrow function has no 'this'
fnES6arrow: x => this.value
};
console.log(
obj.fnES5(),
obj.fnES6(),
obj.fnES6arrow()
);
// 10 10 undefined
Note the syntax (above) for fnES6
declaration.
Symbols
Symbols are a new feature, partly to prevent new features breaking old websites.
In the snippet beJavascript allow attribute access via dot notation or via index []
, So given the literal string name for the attribute it is accessible
var x = {};
x.enabled = true;
x["enabled"] = true;
The new Symbol()
allows creating a key to index the property on that will never collide with anything else as the key is not a string, so two separate libraries could, for example, add the same logically named attribute to the global window
object without any issues.
var enabled = Symbol("is enabled");
// add attribute value
window[enabled] = true;
// view the value
console.log( (window[enabled] );
// true
The argument is enabled
is a description (for debugging mainly). Interesting attributes of Symbol
console.log( Symbol() === Symbol() ); // false
console.log(
Symbol("description") === Symbol("description")
); // false
console.log( typeof Symbol() ); // symbol
// get a shared symbol
console.log(
Symbol.for("some shared key") === Symbol.for("some shared key")
); // true
// There are 17 (as of writing) symbols that are 'well known' and build in
console.table(Reflect.ownKeys(Symbol));
// "length", "name", "prototype", "for", "keyFor",
// "asyncIterator", "hasInstance", "isConcatSpreadable",
// "iterator", "match", "replace", "search", "species",
// "split", "toPrimitive", "toStringTag", "unscopables"
Collections
There are 4 interesting collections
Map
A Map
is a better version of making a hash from {}
. Better because there is clear separation between key/value pairs and methods on the hash table. It is also more explicit, with members .set()
, .get()
,.has()
, .keys()
, etc. It is also iterable.
Note that not every type can successfully be used as a key, or more precisely objects that have value equality do not have always hash equality, resulting in duplicates.
Set
A Set
works as expected, being a unique set of elements with no values, jus tthe keys. One thing to note is the constructor that takes a parameter is added by iteration.
let s1 = new Set("adam"); // length 3 [a, d, m]
let s2 = new Set(["adam"]); // length 1 ["adam"]
Weak collections
They work as their non-weak other half but miss some members and iteration. Specifically designed to prevent your code hanging on to elements that could otherwise be garbage collected.
Proxies
Proxies are complicated but give you the opportunity to hook into all manner of operations on an object from get/set of an attribute value to function calls and adding/removing a property.
A basic example:
var person = {
_n: "",
// get/set functions explained later
get name() {return this._n;},
set name(value) {this._n = value}
};
function loggingProxy(target) {
let handler = {
get: function (target, key) {
console.log(`PROXY: getting ${key}!`);
return target[key];
},
set: function (target, key, value) {
console.log(`PROXY: setting ${key}!`);
return target[key] = value;
}
};
return new Proxy(person, handler);
}
var p = loggingProxy(person);
p.name = "adam";
// PROXY: setting name!
console.log(p.name);
// PROXY: getting name!
// adam
Classes
To make something like classes in OO languages we now have,
class B1 {
message = "Hello";
viewMessage(msg) {
return `${this.message} ${msg}`;
};
}
class C1 extends B1{
F1 = 1;
static S1 = 0;
constructor(p1, p2, p3) {
super();
this.F1 = p1;
this.F2 = p2;
C1.S1++;
};
get aField() { return this.F1 };
set aField(f) {
// some validation
this.F1 = f;
};
// logically static Count
count() {return C1.S1};
viewMessage(msg) {
return "# " + super.viewMessage(msg.toUpperCase());
};
}
var c = new C1();
c.aField = "a value";
console.log([c.message, c.aField, c.count(), c.viewMessage("world")]);
// ["Hello", "a value", 1, "# Hello WORLD"]
Notes
- Static methods cannot be called via instances
count()
, ifcount()
was static it would be accessed asC1.S1
- static attributes are allowed
S1
but are accessed asclassName.attributeName
- Fields can be declared at class level,
F1
- Fields do not have to be declared at class level
F2
- There is no notion of public/private/etc.
- The semi-colons at the end of method declarations are optional
get
must have zero parametersset
must have exactly one parameter
Jason covers (inheritance](https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/) in depth too which adds more complexity/power if you want it. For now, Know that
new.target
refers to the constructor called with new (possibly useful form a base class)extends xxx
does not have to be a class- It is possible to extend built in classes, e.g.
Array
super()
in a constructor must be called before referencing this in the constructor (the base does thethis
allocation)
var, let and const
TD;DR; for new code use let
instead of var
except where you want a constant, then use const
.
var belongs_in_global_namespace = "global";
let global_too = "but cannot be re-declared"
function a_function() {
const can_only_be_assigned = "at declaration time!";
let func_scoped = 1;
var vars_are_func_scoped = "declare_var";
if (func_scoped) {
// new scope block for let assignments
let func_scoped = 3;
// not for vars_are_func_scoped
var vars_are_func_scoped = "overwrite declare_var";
}
console.log(`func_scoped still ${func_scoped}`);
console.log(`vars_are_func_scoped changed ${vars_are_func_scoped}`);
}
a_function();
// func_scoped still 1
// vars_are_func_scoped changed overwrite declare_var
Notes
- Once declared a
const
value cannot be re-declared or re-assigned- A
let
variable cannot be re-declared but can be re-assigned
Modules, import/export
Module support is static, i.e. no conditional import or export. Use an existing module system for that (for now).
Exporting
Exports are explicit and can appear anywhere outside of a function/class in a file. Elements can be exported individually or as a group. Elements can be aliased during export.
// a-module.js
function fn1() {console.log("a-module::fn1");}
function fn2() {console.log("a-module::fn2");}
class c1{constructor() {console.log("a-module::c1 constructor");}}
export function fn3() {console.log("a-module::fn3");}
export {fn2, c1 as Class1};
export const happy = true;
export default {
happy: happy,
fn2: fn2,
other: () => "hello"
};
fn1
is not exported, everything else is explicitly exported in various ways. Class c1
is exported with an alternate name Class1
.
The export default {}
block allows for the ‘default’ import (see next section).
The syntax export XXX {}
and export {} as XXX
are equivalent.
Importing
Import explicit elements or everything. Aliasing is allowed.
// main.js
import {fn2 as someFn, happy, Class1} from "./a-module.js";
import mod from "./a-module.js"
function ready(){
someFn();
var x = new Class1();
console.log(`happy = ${happy}`);
document.getElementById("id").innerText = mod.other();
}
// for demo in browser
if (document.readyState != 'loading') {ready();}
else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', ready);
}
with a little index.html;
<head>
<script type="module" src="main.js"></script>
<script type="module" src="a-module.js"></script>
</head>
<body>
<div id="id">waiting...</div>
</body>
Combining index.html
, main.js
and a-module.js
we can demonstrate import/export in a browser.
Note the type="module"
in the script
tags are needed to make it work otherwise you will see
ⓧ
Uncaught SyntaxError: Unexpected token {
import/export in nodejs (I'm only interested for unit testing) appears to only be available behind an experimental switch. I'll update if I work out how to make use of it.
There is still loads more but I'm caught up enough for now.
Links
- (Iterators and the for of loop)[https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/)
- (Generators)[https://hacks.mozilla.org/2015/05/es6-in-depth-generators/)
- (Template strings)[https://hacks.mozilla.org/2015/05/es6-in-depth-template-strings-2/)
- (Rest parameters and defaults)[https://hacks.mozilla.org/2015/05/es6-in-depth-rest-parameters-and-defaults/)
- (Destructuring)[https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/)
- (Arrow functions)[https://hacks.mozilla.org/2015/06/es6-in-depth-arrow-functions/)
- (Symbols)[https://hacks.mozilla.org/2015/06/es6-in-depth-symbols/)
- (Collections)[https://hacks.mozilla.org/2015/06/es6-in-depth-collections/)
- (Proxies and reflect)[https://hacks.mozilla.org/2015/07/es6-in-depth-proxies-and-reflect/)
- (Classes)[https://hacks.mozilla.org/2015/07/es6-in-depth-classes/)
- (Let and const)[https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/)
- (Modules)[https://hacks.mozilla.org/2015/08/es6-in-depth-modules/)