Formatting
Follow the formatting style used by Prettier (with default settings).
- Indent with 2 spaces
- Use semicolons (
;
) at the end of statements - Use double quotes for strings
- Keep opening braces (
{
) on the current line, with one space before, and closing braces (}
) on a new line
Recommended: install Prettier (npm i -g prettier
or install for your editor) and use it to automatically reformat your JS code.
const settings = {
indentSize: 2,
semicolons: true
};
console.log(opinion(settings.semicolons));
function opinion(pref) {
let opinion = "Semicolons are ";
if (pref === true) {
opinion += "alright, mate.";
} else if (pref === false) {
opinion += "superfluous.";
} else {
opinion = "What is a semicolon?";
}
return opinion;
}
Comments
- Use single-line comments (
// …
) to comment on the next line or the next few lines of code. - Use a JSDoc-style comment (
/** … */
) before a complex function or a module.
/**
* Make a header "sticky"
* (Adds/removes a class when scrolling)
*/
function stickifyHeader(element, activeClass, offset) {
// 250ms not working when scrolling too quicky
const latency = 100;
…
}
Naming things
- Use
lowerCamelCase
for names: variables, functions, object keys… - Use
UpperCamelCase
for classes, and for UI components (in React, Vue.js, etc.). - Use descriptive names rather than generic ones.
// Use descriptive names
const element1 = document.querySelector(…);
const element2 = document.querySelector(…);
const button = document.querySelector(…);
const container = document.querySelector(…);
// For names with 2 or 3 words,
// use lowerCamelCase style
const userSettings = {
limit: 20,
showInvisible: false
};
function validateSettings() {
…
}
// And UpperCamelCase for classes
class MobileHeader extends React.Component {
…
}
Global variables
Global variables from a script can clash with other scripts in the page, creating subtle bugs.
- Avoid creating global variables; use the IIFE pattern or ES6 modules to isolate variables
- If you need to set a parameter for a script in your HTML code, use data attributes
Variables and the window
object
In web pages, global variables are “hosted” by the window
object. This means that a global variable myVar
can also be accessed at window.myVar
.
Variables become global when:
- Assigning explicitely:
window.myVar = "hello";
- Declaring a variable at the top level of a script
- Forgetting the
var
/const
/let
keyword
// top level variables are global
var count = 50;
console.log(count); // 50
console.log(window.count); // 50
// variables in functions are not,
// UNLESS you forget the var keyword
function showCookieBanner() {
var banner = document.querySelector(…);
visible = !banner.hidden;
}
<!-- using data attributes for settings -->
<script>var sliderItems = 2;</script>
<div class="Slider" data-items="2">
…
</div>
Modules
- Create one JS file for each major feature or UI part.
- Wrap the contents of each file in an IIFE manually, or use a bundler such as Browserify, Rollup or webpack.
Writing IIFEs by hand
The immediately-invoked function expression (IIFE) pattern prevents us from creating global variables. The ()
after the function executes it (that’s the “immediately-invoked” part), otherwise our code would not run.
// my-module.js
!function() {
// only exists in the scope of the parent function
var local = "Hot variables in you area";
// we can still make some values or methods global if we want to
window.myModule = function() {…}
}();
When you concatenate several modules, you should end up with a bundled JS file that looks like this:
// my-module.js
!function(){…}();
// other-module.js
!function(){…}();
Using a script bundler
With a JS code bundler such as Browserify, Rollup or webpack, you don’t need to wrap each module in a function: the bundler will take care of it.
For example with webpack and ES6 syntax:
// app.js
import "some-dependency";
import "./scripts/my-module.js";
import "./scripts/other-module.js";
// my-module.js
const local = "Hot variables in you area";
export function myModule() {…}
Using ES6
ECMAScript 2015 (also called ES6 or ES2015) is a major version of the JavaScript standard, with most of its features already implemented by major browsers in 2017. To support older browsers, it might be useful to transpile your ES6 code into ES5.
- For new projects, use ES6 syntax and features.
- Use a transpiler (Babel, Bublé) to generate ES5 code.
- Avoid ES6 syntax and features for existing projects not using a transpiler.
Browser support
If you’re not transpiling to ES5 and are targetting older browsers, avoid using ES6 syntax and features. Some examples of minimum compatibility:
- Arrow functions (
x => x * 2
): needs Edge 12, Chrome 45, Safari 10 - Promises: Chrome 32, Firefox 29, Edge 12
const
andlet
statements: needs IE11, Chrome 49for...of
loops: Edge 12, Chrome 38async
/await
: Chrome 55, Firefox 52, Edge 14, Safari 10.1- ES6 modules: experimental support in some browsers only
If you are transpiling ES6 to ES5, note that some features require adding a polyfill to your JS code, and that can increase the size of your JS bundle a lot!
Debugging
- In the browser’s Developer Tools, try to be familiar with both the Console and the JS Debugger (“Sources” in Chrome, “Debugger” in Firefox)
- Remove
console.log
calls anddebugger
statements from your code before committing
How to set breakpoints in your JS code:
function potentiallyBuggyCode() {
…
// don’t forget to remove this statement
debugger;
}
Tooling up
A lot of JavaScript development uses Node.js and associated packages, even if we’re not using Node.js to run JS on the server.
Minimum requirements
- Install Node.js version 8 or higher
- Learn the basics of how
package.json
is structured - Use
npm
to manage dependencies and scripts - Use a JS code bundler (such as webpack) to create one or several JS bundles for the frontend
Recommended tools
- Use nvm to install several Node.js versions and switch between them.
- Use Prettier to format your JS and CSS code. You can install a Prettier plugin for your code editor of choice.
- Consider using eslint to check your JS code, and stylelint to check your CSS or SCSS code.
- We have ready-to-use configs for webpack and for gulp. TODO: publish webpack config
In this example package.json
we installed user-facing code as dependencies (npm install --save
), and all the tools used to build or test code as development dependencies (npm install --save-dev
).
{
"name": "my-project",
"scripts": {
"build": "webpack --env=prod",
"dev": "webpack-dev-server --env=dev"
},
"dependencies": {
"jquery": "^3.2.1",
"some-other-lib": "^1.0"
},
"devDependencies": {
"babel-core": "^6.25.0",
…
"eslint": "^4.7.1",
…
"webpack": "^3.4.1",
"webpack-dev-server": "^2.6.1"
}
}
Type coercion
JavaScript may automatically convert a value from one type to another (for example, from a string to a number):
- When writing
if (myVar)
(or using operators like?
,!
,||
and&&
), JS will convert the value to a boolean (true
orfalse
) - When comparing two values with
x == y
,x != y
,x < y
andx > y
, some type conversion can happen if the values have different types
Conversion to boolean
The following values convert to false
: 0
and NaN
, the empty string ""
, undefined
, null
and false
. Everything else converts to true
, including objects and empty arrays!
- Be careful with
if (myVar)
; try to write a more precise check if possible - For arrays and some object types, you may need to check
myVar.length
Comparing values
- Prefer the strict equality and inequality operators (
x === y
,x !== y
) - Avoid
==
and!=
(although sometimes it can be useful, for examplemyVar != null
checks if the value is different fromnull
andundefined
)
The this
keyword
In a function, the this
keyword can refer to different things depending on how the function is called. This can be confusing for beginners, and it’s often preferable to explicitly pass parameters to a function.
- When working with classes (for example with libraries that make heavy use of classes and
this
such as React or Vue.js), do use thethis
keyword. - Otherwise, try to avoid it.
With jQuery
Some jQuery methods, such as $(x).each(…)
and $(x).on(…)
, change the value of this
in the callback function to the current HTML element in the loop. Try to use the function’s arguments instead.
$('p').each(function(index, element) {
// avoid 'this', prefer the 'element' argument
console.log(this === element);
});
$('a').on('click', function(event) {
// avoid 'this', prefer 'event.currentTarget'
console.log(this === event.currentTarget);
});