modern JS

Features

  • modules
  • let, const
  • arrow functions
  • default parameters
  • destructuring
  • rest parameters
  • spread operator
  • template strings
  • destructuring
  • method shorthand
  • Symbols
  • Sets
  • WeakSets
  • Maps
  • WeakMaps
  • generators
  • iterators
  • tail call recursion
  • Classes
  • Proxies
  • lots more

Features

  • modules
  • let, const
  • arrow functions
  • default parameters
  • destructuring
  • template strings
  • rest parameters
  • spread operator
  • method shorthand

modules

Modules

A module is a normal js file with two differences.

  • modules are automatically in strict mode.
  • modules can use `import` and `export`

 

 

 

`export`

The `export` keyword is how modules express what parts are to be exposed to importing modules.

Anatomy of`export`

The `export` keyword identifies locals that are to be exposed to importing modules. The `export` keyword can be followed by an option `default` keyword once per module. 

An object declaration can then follow, or an object can be exported by referencing an existing object surrounded by curly braces and optionally re-namespaced with the `as` keyword.

export [default] <object declaration>

export [default] { <object reference> [as <identifier>] }

named exports

Any number of named exports may exist for a given module. Named exports can be exported at declaration time simply by adding the `export` keyword.

export const list = [1,2,3];

export function doStuff() { ... }

They can also be exported after declaration time. In this case, the local name(s) must be surrounded by curly braces.

export { list }

export { doStuff, doOtherStuff }

named exports

Named exports can also be exported under a different name than their internal one.


export { transformToAwesome as awesomeInator }

exports

Any value can be exported from a module. There are no restrictions on export types.

export function Bruceinator(name) {
    name = 'Bruce';
    return `Mind if we call you ${name}?`;
}

export const JaredsMotto = "I'm a belieber!";

export let MattsCats = ["Muffin", "Piddles", "Sweetums", "Daddy's Princess"];

export { Bruceinator as deafault };

default exports

Only one default export can exist per module. Default exports are identified by the `default` keyword after the `export` keyword.

export default function rad() { ... }
export { rad as default }

Effectively, default exports are really named exports with a special `default` name.

exports

The language preferred approach to exporting is the single export as default, and so is the easiest to construct and to consume.


// module1.js
export default function SRP(config) {
    ...
}


// module2.js
import mod from 'module1';

imports

The `import` keyword indicates modules to be loaded into the current module context.

Anatomy of`import`

The `import` keyword identifies which exposed module parts are to be brought into the existing module context. 

The `import` keyword must be followed by a name if only importing the module's default export.

If importing multiple module parts an import list (a comma separated list names surrounded by curly braces) or * is followed by an optional "`as identifier".

The location string completes the `import` statement.

import <name> [as <indentifier>] from "<location>";

import { <name>[ as <identifier>][, <name>[ as <identifier>]...] }> from "<location>";

import * as <identifier> from "<location>";

imports

​Default imports can be assigned directly to an arbitrary identifier.

// jQuery.js
export default function jQuery() { ... };


// myApp.js
import $ from 'jQuery';

imports

The import/export keywords have no way to dynamically or conditionally load a module. The location that follows `from` must be a string literal. interpolated strings will throw an exception.

// myApp.js
let loc = 'jQuery'

import $ from loc; // Exception thrown;

The ES module specification comes with a programatic API for dynamic/conditional loading of modules using the import operator: `import()`.

imports

Named exports must be imported under the name they were exported as...

// underscore.js
export function Underscore() { ... };


// myApp.js
import { underscore } from 'underscore';

Named exports can be assigned to an arbitrary identifier with the `as` keyword.

// underscore.js
export function Underscore() { ... };


// myApp.js
import { underscore as _ } from 'underscore';

import()

The import() operator can be used anywhere in the code and accepts a fully qualified resource url as the only argument. It returns a Promise.

import('https://unpkg.com/d3@5.7.0/dist/d3.min.js')
  .then((d3) => {
    ...d3 stuff
  })

Important Things To Note

  • Modules are singletons
  • Module objects are frozen. There is no extending or mutating exported objects by manipulating them outside the module.
  • There is no error recovery mechanism such as try/catch for failed modules. :( but there is form the import() operator via .catch()
  • Modules have no control over how/when it's dependencies are loaded. This encourages "pure" modules.
  • ES Modules support cyclic dependencies.

let & const

let

`let` is essentially `var` with block scoping

 

`let` declarations are not hoisted and therefore are not direct replacements for `var` since lexically they create a "temporal dead zone" where references are neither declared nor defined. `var` declarations do hoist and therefore are declared at every point in the lexical scope, though they may not be defined yet.

The Temporal Dead Zone

let a = 1;

console.log(a); // 1
console.log(b); // undefined
console.log(c); // Reference Error

var b = 2;
let c = 3;

const

`const` has all the same rules as `let` with the addition that `const` variables cannot be reassigned (though they can be mutated if the data-structure is itself mutable).

 

Attempts to reassign result in a silent failure, so there's that.

const a = 1;
a = 'one'; // No Error

const b = [2];
b.push(3);

console.log(a); // 1
console.log(b); // [2,3]
console.log(c); // Reference Error

const c = 4;

arrow (lambda)
functions

lambdas

In computer programming, an anonymous function (also function literal or lambda abstraction) is a function definition that is not bound to an identifier. Anonymous functions are often:

  1. arguments being passed to higher-order functions, or
  2. used for constructing the result of a higher-order function that needs to return a function.

https://en.wikipedia.org/wiki/Anonymous_function

arrow function syntax

Arrow functions are quite a bit more terse than standard JavaScript functions.

function (arg) {
    return doStuff();
}
         (arg) {
    return doStuff();
}
         (arg) => {
    return doStuff();
}
          arg  => {
    return doStuff();
}
          arg  => return doStuff();
          arg  =>        doStuff();
function (arg) {
    return doStuff();
}

arg => doStuff();
function (arg) {
    return doStuff();
}

arg => doStuff();
(arg1, arg2) => doStuff();
function (arg) {
    return doStuff();
}

arg => doStuff();
(arg1, arg2) => doStuff();
(arg1, arg2) => {
    let arg = arg1 + arg2;
    return doStuff(arg);
}

arrow functions

Arrow functions are particularly well suited -- indeed created for -- lambda functions in javascript. When anonymous functions are passed as arguments or returned as a value from another function, they are excellent candidates for arrow functions.

Alonz-y Alonzo!

In 1936, Alonzo Church ...wrote... about functions. His model was called the λ-calculus. (λ is the lowercase Greek letter lambda.) This work was the reason Lisp used the word LAMBDA to denote functions, which is why we call function expressions “lambdas” today.

Church wanted this model in order to prove things about computation in general.

[H]e found that he only needed one thing in his system: functions

the power of simplicity

Think how extraordinary this claim is. Without objects, without arrays, without numbers, without if statements, while loops, semicolons, assignment, logical operators, or an event loop, it is possible to rebuild every kind of computation JavaScript can do, from scratch, using only functions.

arrow functions

  • The new arrow function syntax is not a replacement for standard function definitions in JavaScript.
  • Arrow functions do not have function names. They are anonymous (they can be assigned to a value though).
  • Arrow functions do not have their own `this` context. Within the arrow function, any reference to `this` will, refer to the `this` of the outer scope.
  • Arrow functions do not have their own `arguments` context. As with `this`, any reference to `arguments` will, refer to the `arguments` of the outer scope.
  • If an arrow function is to immediately return a plain object, it must be wrapped in parens ({}).

default
parameters &
destructuring

this or that

In ES5 to provide default values to arguments, code such as the following is often employed:

function doStuff(val1, val2) {
    val1 = val1 || 'default';
    val2 = val2 || {};
}

For the most part this has got the job done, but here area several cases where this approach falls apart. 

doStuff('', false); //Oh noes!

proper defaults

In ES6 gives us proper default values for arguments

const doStuff = (val1='default', val2={}) => { ... }

No worries about false negatives or other falsey gotchas

doStuff('', false); //success

destructuring

Destructuring allows us to extract individual values out of arrays...

let a = [1,2,3,4];

let [b, c] = a;

b === a[0]; // true
c === a[1]; // true

destructuring

...or objects.

let o = {
    a: 1,
    b: 2
};

let {a, b} = o;

a === o.a; // true
b === o.b; // true

destructuring

We can even assign the extracted values to new names!

let o = {
    a: 1,
    b: 2
};

let {a: x, b: y} = o;

x === o.a; // true
y === o.b; // true

Though to my eyes, this syntax looks backwards. :)

default parameters + destructuring = AMAZING!!!

function rockStar({ name='Claudio Sanchez', 
                    bandPosition= 'lead singer', 
                    BFF= 'Cory'} = {}) {

    return `Hello! My name is ${name}. I'm a ${bandPosition}. ${BFF} is my BFF!!`;
}

rockStar({
    name: 'Jack White',
    bandPosition: 'everything kind of guy'
}); // "Hello! My name is Jack White. I'm a everything kind of guy. Cory is my BFF!!"


rockStar({
    name: 'Stephen Christian'
}); // "Hello! My name is Stephen Christian. I'm a lead singer. Cory is my BFF!!"

rockStar({}); // "Hello! My name is Claudio Sanchez. I'm a lead singer. Cory is my BFF!!"
rockStar(); // "Hello! My name is Claudio Sanchez. I'm a lead singer. Cory is my BFF!!"


template
strings

template strings

Template strings are string literals with special powers. They are denoted by `backticks` rather than single or double quotes.

`I look an awful lot like a plain old string with no apparent special powers.`

template strings

One of the primary special powers of a template string is string interpolation. As you would expect, simple string insertion works just dandy.

const lovedOne = 'Ma',
    stringConcatOperator = '+';

`Look ${lovedOne}! I don't need to use ${ stringConcatOperator } to concat strings!`;

//"Look Ma! I don't have to use + to concat strings!";

template strings

But any arbitrary expression works just fine as well.

`Look ${ getClosestLovedOne() }! 
I don't need to use ${ String.fromCharCode(Number('0x2A') + 1) } to concat strings!`;


//"Look Ma! \nI don't have to use + to concat strings!";

One word.

MULTILINE STRINGS!!!

const teplateStringLimerick = `
    Making multiline strings in JS
    Was, basically anyone's guess
    But finally we've got
    A solution that's not
    A hot stinking pile of mess
`;

OMG tagged templates

It's like gummyberry juice for your template strings

const getClosestLovedOne = () => `Ma<script>pwnIt();</script>`

sanitize`Look ${ getClosestLovedOne() }! 
I don't need to use ${ String.fromCharCode(Number('0x2A') + 1) } to concat strings!`;


//"Look Ma<script>pwnIt();</script>! \nI don't have to use + to concat strings!";

OMG tagged templates

Tagged templates is really just syntactic sugar for a call to a function you implement that carries a particular signature.

//[strings between replacements], [the interpolated parts]


const sanitize = (templateData, ...replacements) =>
  templateData
    .map(piece => piece.replace('<', '&lt;').replace('>', '&gt;'))
    .reduce((str, piece, i) => `str${piece}${replacements[i] || ''}`));

OMG tagged templates

could be something like this...

//imperial is a style of mustache, get it?
imperial`
<ul>
{#for user in ${users}}
    <li>{user}</li>
{/for}
</ul>
`;

//"Cory"

or this...

i18n('de')`Welcome, ${user.name}`;

//"Welcomen, Cory"

or whatever, like literally whatever.


rest
parameters

rest parameters

Rest parameters are a way of collecting all the rest of of the arguments passed into a function after the named parameters have been consumed.

const sum(a, b, ...cs) => cs.reduce((sum, c) => sum + c, a+b);

sum(1,2,3,4,5,6); // 21
const sum = (...args) => {
    //just like arguments, but better
}

Things to consider

  • Rest parameters are similar to the `arguments` object, but with some differences
  • The rest parameter is a proper array, `arguments` is not
  • The rest parameter will consume all the remainder of the arguments passed to a function after the named parameter arguments have been filled.


spread
operator

'aight, spread 'em.

The spread operator operates on an iterable and spreads them in place. This may not sound terribly useful at first, but there are several ways this makes JavaScript development easier.

// Convert and array-like to an array-proper
[...document.querySelectorAll('.things')]

// A more elegant alternative to Function.prototype.apply
fn.call(null, ...someArray);

Things to consider

  • The spread operator operates on any iterable (that's a thing in ES6)
  • Spreading in place in a context that does not expect a comma separated list will result in an exception  


method
shorthand

Method Shorthand

ES6 offers a convenience for defining methods on objects 

// The old and busted

const someObj = {

  someMethod: function (arg) {

    // do some stuff.
  }
}


// The new hotness

const someObj = {

  someMethod(arg) {

    // do some stuff.
  }
}

Resources