JavaScript Best Practices
JavaScript Best Practices
"react/prefer-stateless-function": "off",
"react/require-default-props": "off",
"react/forbid-prop-types": "off"
}
We recommend to use ESLint as JavaScript linter over any other one. Its the current to go forward.
http://www.npmtrends.com/jshint-vs-eslint-vs-jslint-vs-babel-eslint-vs-jscs
https://www.sitepoint.com/comparison-javascript-linting-tools/
Compatibility guide for ES5, ES6 and ES7 for different browsers and mobile OS
http://kangax.github.io/compat-table/es5/
2) Choose easy to understand and short names for variables and functions.
This is a no-brainer but it is scary how often you will come across variables like x1, fe2 or xbqne in
JavaScript, or on the other end of the spectrum variable names
like incrementorForMainLoopWhichSpansFromTenToTwenty or createNewMemberIfAg
eOverTwentyOneAndMoonIsFull.
None of these make much sense good variable and function names should be easy to understand
and tell you what is going on not more and not less. One trap to avoid is marrying values and
functionality in names. A function called isLegalDrinkingAge()makes more sense
than isOverEighteen() as the legal drinking age varies from country to country, and there are
other things than drinking to consider that are limited by age.
See your code as a narrative. If you can read line by line and understand what is going on, well done.
If you need to use a sketchpad to keep up with the flow of logic, then your code needs some work.
Variables declared with const are just like let except that you cant assign to them, except at the
point where theyre declared.
It is a good coding practice to initialize variables when you declare them. This will:
Give cleaner code
Provide a single place to initialize variables
Avoid undefined values
Note: let and const are block scoped. Therefore, referencing block-scoped identifiers before they
are defined will produce a ReferenceError. Leave var declarations inside of legacy code to denote that
it needs to be carefully refactored. When working on a new codebase, use let for variables that will
change their value over time, and const for variables which cannot be reassigned.
Always treat numbers, strings, or Boolean as primitive values. Not as objects. Declaring these types as
objects, slows down execution speed, and produces nasty side effects:
let x = "John";
let y = new String("John");
(x === y) // is false because x is a string and y is an object.
Beware that numbers can accidentally be converted to strings or NaN (Not a Number). JavaScript is
loosely typed. A variable can contain different data types, and a variable can change its data type:
let x = "Hello"; // typeof x is a string
x = 5; // changes typeof x to a number
0 == ""; // true
1 == "1"; // true
1 == true; // true
function myFunction(x, y = 0) {}
Problem: all variables are global and can be accessed; access is not contained, anything in the page
can overwrite what you do.
var current = null;
var labels = {
'home': 'home',
'articles': 'articles',
'contact': 'contact'
};
function init() { };
function show() { current = 1; };
function hide() { show(); };
Module Pattern: You need to specify what is global and what isnt - switching syntax in between.
Keep consistent syntax and mix and match what to make global.
module = function () {
var current = null;
var labels = {
'home': 'home',
'articles': 'articles',
'contact': 'contact'
};
var init = function () { };
var show = function () { current = 1; };
var hide = function () { show(); }
return { init: init, show: show, current: current }
}();
module.init();
[Note]: Use JSDoc Standard to document your code. Helps Code Editor Intelligence and Auto
documentation with tools like: DocumentationJS
Good code explains itself is an arrogant myth. Comment what you consider needed - but dont tell
others your life story. Avoid using the line comment //. /* */ is much safer to use because it doesnt
cause errors when the line break is removed.
If you debug using comments, there is a nice little trick:
module = function () {
var current = null;
/*
var init = function(){
};
var show = function(){
current = 1;
};
var hide = function(){
show();
}
// */
return { init: init, show: show, current: current }
}();
Comments should never go out to the end user in plain HTML or JavaScript.
...Two months down the line: All styles must comply with the new company style guide; no borders
are allowed and errors should be shown by an alert icon next to the element.
People shouldnt have to change your JavaScript code to change the look and feel.
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for (var i = 0, j = inputs.length; i < j; i++) {
if (inputs[i].className === 'mandatory' && inputs.value === '') {
inputs[i].className += ' error';
}
}
Using CSS inheritance, you can avoid having to loop over a lot of elements.
21) Use Shortcut Notations
Shortcut notations keep your code snappy and easier to read once you get used to it.
This code
var lunch = new Array();
lunch[0]='ADT';
lunch[1]='CNN';
lunch[2]='C01';
lunch[3]='Travelers';
This code
if(v){var x = v;} else {var x =10;}
Is the same as...
var x = v || 10;
This code
var direction;
22) Modularize
The Module Pattern uses functions and closures to provide encapsulation in functional languages like
JavaScript.
The Module Pattern tends to work well for a chunk of code that could be described as a Singleton
class.
Your JavaScript shouldn't be floating off in the global namespace, where it collides with other stuff
you've included.
Although JavaScript doesn't have built-in notions of namespaces, its object model is flexible enough
that you can emulate them. Here's an example:
MyNamespace.MyModule = function()
{
// Your module is now in a namespace!
}
[NOTE]: Use Arrow functions for IIFE (doesnt create a new scope)
(() => {
var x = 123;
console.log(x);
})();
Use IIFE to create UMD modules
If you're writing a one-off chunk of JavaScript that never needs to be referred to by other code, it's
wise to anonymously scope it so it won't get accidentally referenced elsewhere.
console.log(x);
In the code above, the first console.log() will succeed, but the second will fail. You won't be able to
reference x outside of the anonymous function.
Everything that is likely to change in your code should not be scattered throughout your code.
This includes labels, CSS classes, IDs and presets. By putting these into a configuration object and
making this one public we make maintenance easy and allow for customization.
Above using the generic really throw-away variable names ul and li here, We
need nestedul and datali for the nested list items. If the list nesting were to go even deeper I would
need more variable names, and so on and so on. It makes more sense to put the task of creating
nested lists for each member in its own function and call this with the right data. This also prevents
us from having a loop inside a loop.
The addMemberData() function is pretty generic and is very likely to come in handy at another time.
Taking these thoughts on board, we can rewrite the code as follows:
function renderProfiles(o){
var out = document.getElementById(profiles);
for(var i=0;i<o.members.length;i++){
var ul = document.createElement(ul);
var li = document.createElement(li);
li.appendChild(document.createTextNode(data.members[i].name));
li.appendChild(addMemberData(o.members[i]));
}
out.appendChild(ul);
}
function addMemberData(member){
var ul = document.createElement(ul);
for(var i=0;i<member.data.length;i++){
var li = document.createElement(li);
li.appendChild(
document.createTextNode(
member.data[i].label + +
member.data[i].value
)
);
}
ul.appendChild(li);
return ul;
}
Your functions should do one thing only on one level of abstraction. Functions should either do
something (modify) or answer something (query), but not both.
// DON'T
function getUserRouteHandler (req, res) {
const { userId } = req.params
// inline SQL query
knex('user')
.where({ id: userId })
.first()
.then((user) => res.json(user))
}
// DO
// User model (eg. models/user.js)
const tableName = 'user'
const User = {
getOne (userId) {
return knex(tableName)
.where({ id: userId })
.first()
}
}
[NOTE]: Use Array methods for loop instead because they have their own scope, examples:
forEach, reduce, map, every
Loops can become very slow if you dont do them right. One of the most common mistake is to read
the length attribute of an array at every iteration:
This means that every time the loop runs, JavaScript needs to read the length of the array. You can
avoid that by storing the length value in a different variable:
An even shorter way of achieving this is to create a second variable in the pre-loop statement:
Another thing to ensure is that you keep computation-heavy code outside loops. This includes
regular expressions and more importantly DOM manipulation. You can create the DOM nodes
in the loop but avoid inserting them into the document.
// DO
function getRegisteredUsers ({ fields, include, fromDate, toDate }) { /* i
mplementation */ }
getRegisteredUsers({
fields: ['firstName', 'lastName', 'email'],
include: ['invitedUsers'],
fromDate: '2016-09-26',
toDate: '2016-12-13'
})
In short, make sure that all the data that goes into your systems is clean and exactly what you need.
This is most important on the back end when writing out parameters retrieved from the URL. In
JavaScript, it is very important to test the type of parameters sent to your functions (using
the typeof keyword). The following would be an error if members is not an Array (for example for a
string itll create a list item for each character of the string):
function buildMemberList(members){
var all = members.length;
var ul = document.createElement('ul');
for(var i=0;i<all;i++){
var li = document.createElement('li');
li.appendChild(document.createTextNode(members[i].name));
ul.appendChild(li);
}
return ul;
}
In order to make this work, you need to check the type of members and make sure it is an array:
function buildMemberList(members){
if(typeof members === 'object' &&
typeof members.slice === 'function'){
var all = members.length;
var ul = document.createElement('ul');
for(var i=0;i<all;i++){
var li = document.createElement('li');
li.appendChild(document.createTextNode(members[i].name));
ul.appendChild(li);
}
return ul;
}
}
Arrays are tricky as they tell you they are objects. To ensure that they are arrays, check one of the
methods only arrays have. Another very insecure practice is to read information from the DOM and
use it without comparison. For example, I once had to debug some code that caused the JavaScript
functionality to break. The code that caused it was for some reason beyond me reading a user
name out of the innerHTML from a page element and calling a function with the data as a
parameter. As the user name could be any UTF-8 character this included quotation marks and single
quotes. These would end any string and the remaining part would be erroneous data. In addition,
any user changing the HTML using a tool like Firebug or Opera DragonFly could change the user
name to anything and inject this data into your functions.
The same applies to forms that validate only on the client side. I once signed up for an unavailable
email address by rewriting a select to provide another option. As the form wasnt checked on the
back end the process went through without a hitch.
For DOM access, check that the element you try to reach and alter is available and what you expect it
to be otherwise your code may fail or cause strange rendering bugs.
To make sure that your code is fast and doesnt slow down the browser to a halt try to keep DOM
access to a bare minimum. Instead of constantly creating and applying elements, have a tool
function that turns a string into DOM elements and call this function at the end of your generation
process to disturb the browser rendering once rather than continually.
However, this attribute has long since been deprecated; so leave it out.
If you have JS files whose only purpose is to add functionality -- for example, after a button is clicked
-- go ahead and place those files at the bottom, just before the closing body tag. This is absolutely a
best practice.