JavaScript

Code can be checked against these coding standards using the ESLint coding standards configuration, available on npm as humanmade/coding-standards. This is also automatically run when running phpcs checks.

Some of the more basic style rules such as tab indentation may also be enforced automatically within your editor by using an .editorconfig file.

Modern JavaScript Modern JavaScript

Modern JavaScript syntax (commonly “ES6” or “ESNext”) should be used wherever it makes sense, and compiled to ES5 syntax for browser compatibility using babel. This syntax is usually much more readable than the older ES5 alternatives. New features includes classes, arrow functions, and module import/exports, among other useful syntax additions.

Selected resources:

Classes Classes

ES6 includes a class syntax which should be used instead of ES5-style constructor functions and prototype methods.

For example, in ES5 you would add methods to a function’s prototype:

var MyClass = function () {
    this.something = 0;
};
MyClass.prototype.add = function () {
    this.something++;
};

In ES6, this is now written:

class MyClass {
    constructor() {
        this.something = 0;
    }

    add() {
        this.something++;
    }
}

Methods should have one empty line between them (i.e. }\n\n).

Arrow functions Arrow functions

Arrow functions are shorter forms of closures/anonymous functions that automatically bind to the existing context.

For example, in ES5:

window.setTimeout( this.update.bind( this ) );
// or:
window.setTimeout( ( function () {
    this.update();
} ).bind( this ) );

In ES6, this is instead written:

window.setTimeout( () => {
    this.update();
});

This may be shortened to a one-liner by dropping the braces:

window.setTimeout( () => this.update() );

The shortened form only allows one statement, and the result of this is automatically returned. This can be used for functional programming:

var sorted = items.map( item => item.id ).sort( id => id );

Avoid using .bind(this) Avoid using <code>.bind(this)</code>

As the body of a short arrow function will always share context (the value of this) with its containing scope, use short arrow functions instead of calling func.bind(this). This tends to be easier to understand.

// Avoid:
function callback() {
    this.handle();
}
something( callback.bind( this ) );

// Prefer:
something( () => {
    this.handle();
} );

.bind() may be used if the function should be bound to something other than this.

For partial application (pre-binding a subset of function arguments), arrow functions are again preferred:

// Avoid:
var newHandle = this.handle.bind( this, arg1, arg2 );

// Prefer:
var newHandle = ( ...args ) => this.handle( arg1, arg2, ...args );

Modules Modules

Browser JavaScript should be split up into modules using ES6 module syntax.

import MyClass, { namedExport } from './MyClass';

export default class SubClass extends MyClass {
    myMethod() {
        /* ... */
    }
}

In addition to or instead of the default export in the example above, a module may also provide one or more named exports:

export function fetchPosts( args = {} ) { /* ... */ }

export function fetchSinglePost( id, args = {} ) { /* ... */ }

export function fetchPages( args = {} ) { /* ... */ }

Files exporting a class (whether a React component or otherwise) should only export one single class, and should provide it using export default. When using higher-order components, it may also be useful to expose the original, unwrapped class as a named export for testing. Use named exports sparingly, however: if your class file exports utility functions, they are often better placed in a separate utility file.

You may also find it useful to consistently declare default exports at the bottom of their containing module file.

Variable declaration Variable declaration

const & let should be used in place of var when declaring variables.

const should be used in most cases. Note that const means that a variable may not be re-assigned later, not that it is immutable. Complex types like Objects and Arrays may be declared with const then later mutated, and only re-assignment will cause an error:

const primitiveValue = 'forty-two';
const obj = {};

// These would error:
primitiveValue = 42;
obj = function() {};

// This will not error:
obj.property = 42;

let should be used only if you will need to re-assign the variable’s value.

// Avoid:

for ( var i = 0; i < 5; i++ ) {
    window.setTimeout( () => console.log( i ) );
}
//=> 5 5 5 5 5

let obj = [ 'only assigned once' ];

var str = 'default value';
if ( condition ) {
    str = 'other value maybe assigned later';
}

// Prefer:

for ( let i = 0; i < 5; i++ ) {
    window.setTimeout( () => console.log( i ) );
}
//=> 0 1 2 3 4

const obj = [ 'only assigned once' ];

let str = 'default value';
if ( condition ) {
    str = 'other value maybe assigned later';
}

Note that const and let are block scoped, meaning that their declarations are limited to the surrounding block (paired curly brackets), not just to their containing function. In particular, a let variable will not travel upwards from a conditional block such as an if:

// Avoid:

if ( condition ) {
    let x = 1;
}
x; // undefined variable "x"

// Prefer:

if ( condition ) {
    const x = 1;
    doSomethingToX( x );
}

// Use "let" if x is needed outside the block:
let x;
if ( condition ) {
    x = 1;
}

Variable declarations should generally be just-in-time, typically when you initialise it to a value. If you cannot declare the variable when you initialise it, the declaration should be as close as practicable:

// Avoid:

function () {
    let x;

    something();

    if ( condition ) {
        x = 1;
    }
}

// Prefer:

function () {
    something();

    let x;
    if ( condition ) {
        x = 1;
    }
}

Finally, note that let and const are not subject to variable hoisting. A variable declared with let or const may not be referenced until after it is declared.

Destructuring Assignment Destructuring Assignment

Destructuring assignment lets you concisely pull multiple values out of an object:

// Avoid:

const TraditionalAssignment = props => {
    const content = props.content;
    const title = props.title;
    const id = props.id;
    // ...
};

// Prefer:

const DestructuredAssignment = props => {
    const { content, title, id } = props;
    // ...
};

Destructuring is also available for function parameters, although we recommend only using this when dealing with a small number of properties. These are equivalent:

const TraditionalParameters = ( props ) => {
    const { title, content } = props;
    // ...
};
const DestructuredParameters = ( { title, content } ) => {
    // ...
}

Note: it is possible to nest destructuring assignment syntax. Do not do this; it is very hard to read.

Spread operator Spread operator

The ... spread operator can simplify several operations in JavaScript, and should be used in place of more verbose syntax. It can take a little while to learn the different ways the spread operator is used, but once you are familiar with it the code is much more obvious.

// Avoid:

const newObject = Object.assign( {}, oldObject, {
    overridden: 'property',
} );

// Prefer:

// Object rest spread operator
const newObject = {
    ...oldObject,
    overridden: 'property',
};

// Bonus: object rest spread within argument destructuring
const MyComponent = ( { title, ...props } ) => (
    <div>
        <h2>{ title.content.rendered }</h2>
        { renderEverythingButTheTitle( props ) }
    </div>
);
// Avoid:

const nodes = Array.prototype.slice.call( document.querySelectorAll( 'p' ) );

// Prefer:

// Iterable spread operator
const nodes = [ ...document.querySelectorAll( 'p' ) ];
// ES5:

const arr = [ some, values ];
const largerArr = [ newValue ].concat( arr );
// [ newValue, some, values ];

// ES6:

const arr = [ some, values ];
const largerArr = [ newValue, ...arr ];
// [ newValue, some, values ];

Other Object Enhancements Other Object Enhancements

In addition to the spread operator, object declarations now support computed property names:

// Avoid:
const obj = {};
obj[ someDynamicKeyName ] = 'value';

// Prefer:
const objWithComputedPropertyName = {
    [ someDynamicKeyName ]: 'value',
};

A shorthand property syntax is also available to handle the common situation where an object key is assigned a variable with the same identifier:

const title = getUserInput( 'title' );
const content = getUserInput( 'content' );

// ES5:

return {
    title: title,
    content: content,
};

// ES6:

return {
    title,
    content,
};

Style Style

Trailing commas Trailing commas

Multi-line array and object declarations should always have a trailing comma after each item. This cleans up the diff for future changes.

Single-line declarations do not need trailing commas.

Do not align object properties Do not align object properties

{
    object: 'values',
    need: 'not',
    be: 'aligned',
    in: 'js',
}

Multi-property objects should place properties on separate lines Multi-property objects should place properties on separate lines

// Avoid:
const obj0 = { key1: 'value1', key2: 'value2' };

// Prefer:

// Single-property objects may span one or multiple lines.
const obj1 = {
    key: 'value',
};
const obj2 = { key: 'value' };

// Multi-property objects should always span multiple lines.
const obj3 = {
    title: 'A Good Title',
    meta: {
        key: 'Rating',
        value: 10,
    },
};

Avoid Yoda conditions, you must Avoid Yoda conditions, you must

Yoda conditions are dumb and solve the wrong problem. You have my permission to not use Yoda conditions. – RM

Prefer functional programming over imperative iteration Prefer functional programming over imperative iteration

Rather than using loops, use functional programming concepts like .map, .filter, etc.

Libraries Libraries

Avoid Underscore Avoid Underscore

Most functionality in Underscore.js is available natively in the browser, and can be compiled by Webpack/Babel into ES5-compatible calls. Khan’s style guide includes a guide to replacing calls.

If you do need a specific method from Underscore, consider using Lodash instead. Lodash allows importing individual functions, providing only the functionality you need:

import uniqueId from 'lodash/uniqueId';