#ES6

This page is about EMCAScript 2015, or ES6. It's the new proposed style of Javascript, and it fixes a huge volume of the weird syntax things that were present on old JS. Everyone's going to convert to it eventually, so it's a good idea to learn it now.

Due to the clash of syntaxes, each section will also contain a note on my personally recommended style for each feature. Most of the time it will be in line with the Airbnb JS Style Guide, but there will be differences. This is mostly because Airbnb writes thousands of lines of industrial code across hundreds of people, and I usually just write for me. Your style guide has to fit your purposes.

Variables

'let' and 'const'

Recall that var is function-scoped. This leads to weird issues when using loops and conditionals.

if (true) {
    var tempVar = true;
}
console.log(tempVar); //'true' - oops!

ES6 introduces two new variable declarations: const and let. They are both block-scoped. let is essentially the replacement for var, except we don't get those old scoping issues anymore.

if (true) {
    let tempVar = true;
}
console.log(tempVar); //ReferenceError: tempVar is not defined

For ambiguity reasons, this also means you can no longer declare these on a single line, although I'm not sure why you'd ever do that in the first place.

if(true) let x = 10; //SyntaxError: Lexical declaration cannot appear in a single-statement context
if(true) var x = 10; //OK

const is only slightly different. As you might expect, const is for constants, where the values are immutable.

const pi = Math.PI;
pi = 3.14; //TypeError: Assignment to constant variable.

Recall that the value of an object is the reference, not the object itself. This means that properties can be changed, but the reference cannot be reassigned.

const x = {foo: 1, bar: 2};
x.foo = 3; //OK
x = {foo:10, bar:5}; //TypeError: Assignment to constant variable.

More on Scoping

In most browsers, functions by default act pretty much like var, meaning that they are both function-scoped and hoisted. The ES6 specification is supposed to change this so that function declarations are block-scoped. (I can't find the relevant spot in the spec but this website says so.)

{
    function hi() {
        console.log('hi');
    }
}
hi(); //no error pre-ES6, error in ES6

Note that most browsers, even those that fully support the rest of ES6, follow the pre-ES6 convention for legacy reasons. So if you run the above code block in Chrome, you will get no error. If you want to make block-scoped functions, just use the declaration notation for now.

{
    const hi = function() {
        console.log('hi');
    }
}
hi(); //error

Also, let and const do not hoist. (Kind of. More on this later.)

{
    a = 5;
    var a = a * 2;
    console.log(a); //'10'
}

{
    a = 5;            //ReferenceError: a is not defined
    let a = a * 2;
    console.log(a);
}

You can't redeclare let or const statements.

var a;
var a; //OK

let b;
let b; //SyntaxError: Identifier 'b' has already been declared

However, you can override variables in deeper scopes.

{
    let b = 1;
    {
        let b = 2;
        console.log(this.b); //'2'
    }
}

Remember when I said that let and const were 'kind of' not hoisted? When we use a let or const statement inside of a block scope, they 'shadow' previous declarations. This is best illustrated by example.

{
    let a = 10;
    {
        a = 30;
        console.log(a); //Uncaught ReferenceError: a is not defined
        let a = 20;
    }
}

The let a = 20 statement 'shadows' a = 30. You can think of this as the let statement being pseudo-hoisted and reserving the variable name until its usage. This results in a weird area where a cannot be accessed at all. The ES6 spec calls this the 'temporal dead zone'.

O   {
O       let a = 10;
        {
T           a = 30;
T           console.log(a); //Uncaught ReferenceError: a is not defined
I           let a = 20;
        }
    }

I've added line prefixes to this block of code. O means that the outer a is in scope (a=10). I means that the inner a is in scope (a = 20). T is the temporal dead zone, where no a is in scope.

Style Guidelines

Avoid using var.

Airbnb section.

Function-scoping is a nightmare. Just don't.

var x; //bad
const y; //good
let z; //good

Even in for loops. Yes, you have to break your habits.

for(let i=0; i<10; i++) { //good
    //...
}

Use const as much as you can. Use it for values that you know won't be changed, such as intermediate values.

//good
const array = [1, 2, 3];
for(let i=0; i<array.length; i++) {

}

(I will note that Airbnb discourages the use of the ++ notation. I do too, but I make an exception in the case of loops, where I think it is more readable.)

Use named function notation.

Airbnb section.

// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
};

// good
const foo = function foo() {
  // ...
};

The first example is hoisted. The second example gives anonymous calls in error stacks. The third example may seem verbose, but it makes debugging easier.

Technically you can use different names for the function and variable declarations (const foo = function bar) but this is confusing and I would not recommend it.

Hopefully, in the future, we can revert to our normal function notation - but until browsers universally accept the block-scoping spec, we're stuck with this.

Arrow Functions

ES6 introduces a new notation for anonymous functions called arrow functions.

Style Guidelines

Use arrow functions when they make your code more readable.

Unlike with other syntaxes, it's hard to establish an objectively better or worse with arrow functions, since they act differently. Just use your common sense and use them when your code becomes more readable, and don't use them when they don't.

For instance, if you're just mapping one value to another, use them.

const squared = x => x*x;

But if you need to bind your jQuery this, don't use them.

$('a').each(() => {
    $(this).doSomething(); //won't work!
});

Some people make the argument that even in these cases, you should only use arrow function notation and use the arguments of the jQuery callback:

$('a').each((i, v) => { //current index, current item
    $(v).doSomething(); //OK
});

But I just think this is weird and not particularly constructive. It makes more sense to just stick with the function notation, in my opinion - it's not like it was dropped from the spec, and it's more readable. Often it makes sense to use both in tandem, anyway, as in the previously given example.

$('#clicker').on('click',function() {
    setTimeout(()=> {
        $(this).toggleClass('on'); //'this' is not rebound
    }, 1000)
});

Another issue of particular interest is closures.

const secret = msg => () => msg;

function secret(msg) {
    return function() {
        return msg;
    }
}

Some will argue that the function chaining notation (=> =>) makes more sense, but others will argue that it doesn't clearly display the nested nature of functions like the traditional notation does. You could fix it with parentheses...

const secret = (msg) => (() => msg);

...but this is confusing for other reasons.

So, the conclusion is: just use whatever you think makes your code more readable. Two generally good rules of thumb are:

In all other cases just go by your own discretion (unless your team has a specific style guide).