Coding convention is a collection of rules set in place to ensure readability and manageability of codes. Especially because JavaScript is more flexible in terms of syntax (dynamic type, binding this, native object manipulation etc.), without a mutually agreed upon rules among developers, it will be near impossible to understand or to debug other's codes. Abiding by the coding convention increases readability and decreases chances of errors or bugs that might affect the performance of the program. For bigger projects, it is much more cost-efficient and reasonable to follow the coding conventions.
This document prioritizes readability and manageability given that it does not damage the performance of the overall program, and will strive to illuminate the ambiguity undetectable by ESLint (assuming linters like ESLint are used).
Note This document deals with ES5 and higher, and is written based on ES6. Conventions needed for ES5 are marked with
(ES5)
tags.
Depending on the development environment, indentation by tabs and spaces could look different, so if not unified, it makes the code harder to read. Therefore, before starting a project, the developer must choose between spaces and tabs. This can differ from project to project depending on individual preferences, and when using spaces, use either two or four spaces. When the indentation is all agreed upon, integrate it onto the editor for future use.
Note FE Development Lab uses two spaces. ESLint – Indent
Although JavaScript does not strictly enforce this rule, it does frequently cause unintended errors and makes debugging more difficult.
Note ESLint – Indent
camelCase
.Deciding the names of variables and functions is part of the coding convention. Most notable conventions are Camel Case, Pascal Case, Hungarian Notation, and Snake Case. Each has its pros and cons, and different languages ask for different conventions. In FE Development Lab, Camel Case is used.
Note ESLint – Camel Case
// Bad
let class;
let enum;
let extends;
let super;
let const;
let export;
let import;
SYMBOLIC_CONSTANTS;
class ConstructorName {
...
};
// Number, string, and a Boolean
let dog;
let variableName;
// Array - For arrays, use plural nouns
const dogs = [];
// Regular Expression - All regular expressions start with a 'r'
const rDesc = /.*/;
// Function
function getPropertyName() {
...
}
// Event Handler - All event handlers start with 'on'
const onClick = () => {};
const onKeyDown = () => {};
// Returning Boolean - All functions that return a Boolean start with 'is'
let isAvailable = false;
let _privateVariableName;
let _privateFunctionName;
// for Objects
const customObjectName = {};
customObjectName.propertyName;
customObjectName._privatePropertyName;
_privateCustomObjectName;
_privateCustomObjectName._privatePropertyName;
let _privateVariableName;
let _privateFunctionName;
// If it is an object
const customObjectName = {};
customObjectName.propertyName;
customObjectName._privatePropertyName;
_privateCustomObjectName;
_privateCustomObjectName._privatePropertyName;
parseHTML
parseXML
JavaScript is built based on global variables. Therefore, every compile takes place in a single shared global (window
) object. Granted, global variable appears to be convenient because it can be accessed anywhere in the program, but it also means that global variables can be changed anywhere in the program. This can cause critical errors in running the program.
// Bad
myglobal = "hello";
// Bad
function sum(x, y) {
result = x + y;
return result;
}
// Bad
function foo() {
let a = b = 0; // This is same as let a = (b = 0), where 'b' becomes implicitly global.
}
// Good
function sum(x, y) {
let result = x + y;
return result;
}
// Good
function foo() {
let a, b;
a = b = 0;
}
let
for values that change, and const
for values that do not. Never use var
.When a const
variable is declared, it tells the program that “this variable will never take a new value,” and makes the code easier to read and to maintain. let
keyword assigns variables within the block and is assigned in block scopes like in many other programming languages, and can help prevent errors.
Note ESLint – no-var
const
before let
.Declaring const
before let
makes codes to be more organized and easy to read.
// Bad - No Grouping
let foo;
let i = 0;
const len = this._array.length;
let bar;
// Good
const len = this._array.length;
const len2 = this._array2.length;
let i = 0;
let j = 0;
let foo, bar;
const
and let
at the point of first usage.Variables declared using const
and let
take the block scope, so are not subjected to hoisting.
// Bad - Declared variables outside of the block scope
function foo() {
const len = this._array.length;
let i = 0;
let j = 0;
let len2, item;
for (; i < len; i += 1) {
...
}
len2 = this._array2.length;
for (j = 0, len2 = this._array2.length; j < len2; j += 1) {
item = this._array2[j];
...
}
}
// Good
function foo() {
const len = this._array.length;
for (let i = 0; i < len; i += 1) {
...
}
// Declared at point of use
const len2 = this._array2.length;
for (let j = 0; j < len2; j += 1) {
const item = this._array2[j];
...
}
}
When referencing both outside modules and inside modules, it increases readability if an empty line is placed between declarations.
const lodash = require('lodash');
const $ = require(jquery);
const handlebars = require('handlebars');
const d3 = require('d3');
const pluginFactory from '../../factories/pluginFactory';
const predicate from '../../helpers/predicate';
const raphaelRenderUtil from '../../plugins/raphaelRenderUtil';
Self-assigning does nothing, and could be signs of an error caused by incomplete refactoring.
// Bad
foo = foo;
[a, b] = [a, b];
[a, ...b] = [x, ...b];
({a, b} = {a, x});
// Good
foo = bar;
let foo = foo;
[foo = 1] = [foo];
var
, always declare at beginning of the function scope. (ES5)
Although JavaScript utilizes code blocks, it does not provide an actual namespace for blocks. Therefore, as long as a variable is declared within a block, it can be called from anywhere in the block. This is because hoisting happens internally when JavaScript compiles, and it damages readability and complicates debugging.
// Bad - Variable is declared in the middle of the scope
function foo() {
...
var bar = '';
var quux = '';
}
// Good
function foo() {
var bar = '';
var quux = '';
...
}
var
keyword and should be assigned as they are declared. (ES5)
If a single var
is used to declare multiple variables, the code can easily become unorganized, so FE Development Lab always uses one var
per variable.
// Bad - Single var is used to declare multiple variables
var foo = '',
bar = '',
quux = '';
// Good - One var is used for each declaration
var foo = '';
var bar = '';
var quux = '';
var
, variables should not be declared in block scopes. (ES5)
Variables declared with var
live in function scopes. If a variable is declared inside for statements and if statements, for example, it is possible to mistake the scope as a block, causing unintended errors.
// Bad
var length = 100;
for (var i = 0; i < length; i += 1) {
...
}
// Good
var length = 100;
var i;
for (i = 0; i < length; i += 1) {
...
}
// Good
var i = 0;
var length = 100;
for (; i < length; i += 1) {
...
}
var
variable separately if the gap between the declaration and the point of use compromises readability. (ES5)
If the gap between the declaration and the actual use is too large, thereby causing readability issues, it is allowed to declare and assign separately. In this case, too, the variable must be declared at the beginning of the scope.
// Bad
function foo() {
var i = 0;
var len = this._array.length;
for (; i < len; i += 1) {
...
}
// Use of var limited within the statement
for (var j = 0, len2 = this._array2.length; j < len2; j += 1) {
// Use of var limited within the statement
var item = this._array2[j];
...
}
}
// Good - Declared and assigned separately
function foo() {
var i;
var j;
var len;
var len2;
var item;
i = 0;
len = this._array.length;
for (; i < len; i += 1) {
item = this._array[i];
...
}
// Declare at the beginning, and assign at the point of use
j = 0;
len2 = this._array2.length;
for (; j < len2; j += 1) {
item = this._array2[j];
...
}
}
var
variables that are used after the conditional in the beginning, and assign at the point of use. (ES5)
Such is also a case where the large gap between declaration and the point of use may compromise the readability; it is permissible to assign at the point of use.
// Bad
function foo(isValid) {
var i = 0;
var len = this._array.length;
if (!isValid) {
return false;
}
for (; i < len; i += 1) {
...
}
}
// Good
function foo(isValid) {
var i, len;
if (!isValid) {
return false;
}
// Declare at the beginning of the scope, and assign at the point of use.
i = 0;
len = this._array.length;
for (; i < len; i += 1) {
...
}
}
var
. (ES5)
Because using a single var
to declare multiple variables over multiple lines may make the code to appear unorganized, declare everything in one line.
// Bad - Unnecessarily used multiple lines
var foo,
bar,
quux;
// Good - Declared in one line
var foo, bar, quux;
// Good
var foo;
var bar;
var quux;
(ES5)
When some variables are assigned and some are only declared, it can be helpful to group them separately, and declare variables with assignments first.
// Bad
var foo;
var bar;
var qux;
var i = 0;
var j = 0;
var len = this._array.length;
var len2 = this._array2.length;
var item;
// Bad
var i = 0, length = ary.length, j, k;
// Good
var i = 0;
var j = 0;
var len = this._array.length;
var len2 = this._array2.length;
var foo, bar, quux, item;
Literal notations are shorter than constructor functions and can decrease the possibility of errors.
// Bad
const emptyArr = new Array();
const arr = new Array(1, 2, 3, 4, 5);
// Bad - Used object constructor
const emptyObj = new Object();
const obj = new Object();
// Good
const emptyArr = [];
const arr = [1, 2, 3, 4, 5];
// Good
const emptyObj = {};
const obj = {
pro1: 'val1',
pro2: 'val2'
};
When cloning a complex object, using a spread operator is more precise and more readable than using an iterator.
const len = items.length;
let i;
// Bad
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// Good
const itemsCopy = [...items];
For ES5 environments, use Array.prototype.slice
. (ES5)
// Good
itemsCopy = items.slice();
Consistent lining convention increases code readability among cooperating developers.
// Bad
var a = [1
];
// Good
var c = [1];
var d = [
1
];
// Bad
const d = [1,
2, 3];
const e = [
function foo() {
dosomething();
}, function bar() {
dosomething();
}
];
// Good
const a = [1, 2, 3];
const b = [
1,
2,
3
];
// Bad - Needs line change
const obj = {foo: 'a', bar: 'b'}
// Good
const obj = {foo: 'a'};
// Good
const obj = {
foo: 'a'
};
// Bad
var obj = {
foo : 'a'
}
// Good
var obj = {
foo: 'a'
}
Shorthand can be used to define complex object literals in a concise manner.
// Bad
const atom = {
value: 1,
addValue: function(value) {
return atom.value + value;
}
};
// Good
const atom = {
value: 1,
addValue(value) {
return atom.value + value;
}
};
Method Syntax
, separate each class member by empty lines.// Bad
class MyClass {
foo() {
//...
}
bar() {
//...
}
}
// Good
class MyClass {
foo() {
//...
}
bar() {
//...
}
}
When a string is passed to the function constructor, the engine parses it using the eval
function and compromises compilation speed.
Note ESLint - no-new-func
// Bad - Used function constructor
const doSomething = new Function('param1', 'param2', 'return param1 + param2;');
// Good - Declared a function
function doSomething(param1, param2) {
return param1 + param2;
}
// Good - Used method syntax
const doSomething = function(param1, param2) {
return param1 + param2;
};
Because when the function created by function expression is hoisted, the value is not assigned to the function, and an error will occur if a function is used before declaration.
// Bad - Used before declaration
const sumedValue = sum(1, 2);
const sum = function(param1, param2) {
return param1 + param2;
};
// Bad - Used before declaration
const sumedValue = sum(1, 2);
function sum(param1, param2) {
return param1 + param2;
};
// Good
const sum = function(param1, param2) {
return param1 + param2;
};
const sumedValue = sum(1, 2);
The grouping operator ()
used in IIFE, while useful, can cause readability issues, so it is recommended to consistently follow the style presented below.
// Bad
(function() {
...
})();
// Good
(function() {
...
}());
(ES5)
JavaScript before ES6 operated under function scopes. The function declared with a declaration inside of the block scope can be mistaken for only being valid in the block even though it takes the function scope. On the other hand, for a function declared using the function expression inside a block scope, the declaration itself is hoisted to the top, but the assignment takes place within the block. Therefore, it does not leave any room for mistakes.
// Bad
if (condition) {
function someFunction() {
}
} else {
function someFunction() {
}
}
// Good
var someFunction;
if (condition) {
someFunction = function() {
...
}
} else {
someFunction = function() {
...
}
}
Arrow functions are automatically bound to higher context without extra binding this, so arrow functions are less verbose and more readable.
// Bad
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
// Good
[1, 2, 3].map(x => {
const y = x + 1;
return x * y;
});
By omitting the parenthesis when only one parameter is required, developers can better benefit from the functionalities of arrow functions.
// Bad
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// Good
[1, 2, 3].map(x => x * x);
// Good
[1, 2, 3].reduce((y, x) => x + y);
If the body of a function consists of a single expression, the curly braces {…}
may be omitted by using the implicit return. In other cases, return
statement may not be omitted.
// Bad
[1, 2, 3].map(number => {
const nextNumber = number + 1;
`A string containing the ${nextNumber}.`;
});
// Good - Used implicit return
[1, 2, 3].map(number => `A string containing the ${number + 1}.`);
By not using a linebreak before the function body, it makes it easier to differentiate between a mistakenly omitted return statement and an implicit return.
// Bad
(foo) =>
bar;
(foo) =>
(bar);
(foo) =>
bar =>
baz;
// Good
(foo) => bar;
(foo) => (bar);
(foo) => bar => baz;
(foo) => (
bar()
);
async
function.async
makes it so that the error ‘thrown’ by the asynchronous executor cannot be ‘caught,’ and it also complicates debugging because a promise is never rejected.
// Bad
const result = new Promise(async (resolve, reject) => {
resolve(await foo);
});
// Good
const result = new Promise((resolve, reject) => {
readFile('foo.txt', function(err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
Destructuring provides an intuitive and logical way to extract the necessary values from an object, and increases the code readability.
// Bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// Bad
const first = arr[0];
const second = arr[1];
// Good
function getFullName(obj) {
const {firstName, lastName} = obj;
return `${firstName} ${lastName}`;
}
// Good
const [first, second] = arr;
// Good
function getFullName({firstName, lastName}) {
return `${firstName} ${lastName}`;
}
// Good
const changeFirstName = user.firstName;
// Good
const {firstName: changeFirstName} = user;
Template literal decreases the complexity of the code by providing an elegant way to handle string concatenation.
// Bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// Bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// Bad - Use regular quotation marks when not using template literal
function sayHi(name) {
return `How are you name?`;
}
// Good
function sayHi(name) {
return `How are you, ${name}?`;
}
extends
with class
to generate objects and inheritances.The extends
method provides much simpler syntax than creating inheritances based on prototypes
.
// Bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
};
// Good
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const {value} = this._queue;
this._queue.splice(0, 1);
return value;
}
}
prototypes
.Follow the previously agreed upon rules to extend the object to write predictable code.
// Bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
return this._queue[0];
};
// Good
class PeekableQueue extends Queue {
peek() {
return this._queue[0];
}
}
import
and export
.When other methods to integrate modules are used, the code consistency is compromised.
// Best
import {es6} from './AirbnbStyleGuide';
export default es6;
// Bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;
// Good
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;
*
.For reasons such as having to support with
syntax, wildcard import can cause identifier corruption every time the module changes if the name is not set.
// Bad
import * from './AirbnbStyleGuide';
// Good
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
Although it may look concise, maintain the code consistency by distinctively separating imports and exports.
// Bad
export {es6 as default} from './airbnbStyleGuide';
{…}
and use clear line changes.When the block only consists of one line, it is possible to skip the curly braces {...}
, but it makes the code structure awkward. Although it is possible, it raises the possibility of future errors, and becomes a not-so-dormant threat to the code.
// Bad
if(condition) doSomething();
// Bad
if (condition) doSomething();
else doAnything();
// Bad
for(let prop in object) someIterativeFn();
// Bad
while(condition) iterating += 1;
// Good
if (condition) {
...
}
// Good
if (condition) {
...
} else {
...
}
Without the empty spaces between keywords and conditional statements, the code becomes too compact and hard to read.
// Bad
var i = 0;
for(;i<100;i+=1) {
someIterativeFn();
}
// Good
var i = 0;
for(; i < 100; i+=1) {
someIterativeFn();
}
do-while
statement, the semicolon ;
follows the while statement.// Bad
do statement while(condition)
// Good
do {
...
} while (condition);
switch-case
, except for the first case, use line changes before each case statement.// Good
switch (value) {
case 1:
doSomething1();
break;
case 2:
doSomething2();
break;
case 3:
return true;
default:
throw new Error('This shouldn\'t happen.');
}
switch-case
, each case must end with one of break
, return
, or throw
, and if it does not have a default
statement, // no default
comment must be included.If multiple cases serve one purpose, it is permissible to not include the break
, but if the purpose varies even in the slightest, include the break
and edit the code differently.
// Bad - breaks are not included even though cases 1 and 2 serve different purposes
switch (value) {
case 1:
doSomething1();
case 2:
doSomething2();
break;
case 3:
return true;
// no default
}
// Bad - nothing hints at 'no default' even if it does not include a default
switch (value) {
case 1:
doSomething1();
break;
case 2:
doSomething2();
break;
case 3:
return true;
}
// Good - if multiple cases serve the same purpose, it is permissible to not include the break
switch (value) {
case 1:
case 2:
doSomething();
break;
case 3:
return true;
// no default
}
Predetermined type checkers make codes more predictable. It is recommended to use FE Development Lab’s code utility, the TOAST UI CodeSnippet.
// String
typeof variable === 'string'
tui.util.isString(variable)
// Number
typeof variable === 'number'
tui.util.isNumber(variable)
// Boolean
typeof variable === 'boolean'
tui.util.isBoolean(variable)
// Object
typeof variable === 'object'
tui.util.isObject(variable)
// Array
Array.isArray(arrayObject)
tui.util.isArray(variable)
// Null
variable === null
tui.util.isNull(variable)
// Undefined
typeof variable === 'undefined'
variable === undefined
tui.util.isUndefined(variable)
// Element Node
element.nodeType === 1
tui.util.isHTMLNode(element)
===
and !==
to test for strict equality.Using ==
or !=
creates ambiguity within the conditional statement, because it disregards the types of data, and can lead to type coercion.
Note ESLint - eqeqeq
const numberB = 777;
// Bad
if (numberB == '777') {
...
}
// Good
if (numberB === 777) {
...
}
By using predetermined methods to test conditions, the code becomes more predictable. It is recommended to use FE Development Lab’s code utility, TOAST UI CodeSnippet.
// String - isNotEmptyString?
if (string) ...
if (tui.util.isNotEmpty(string)) ...
// String - isEmptyString?
if (!string) ...
if (tui.util.isEmpty(string)) ...
// Array - isNotEmpty?
if (array.length) ...
if (tui.util.isNotEmpty(array)) ...
// Array - isEmpty?
if (!array.length) ...
if (tui.util.isEmpty(array)) ...
// Object - isNotEmpty?
if (tui.util.isNotEmpty(object)) ...
// Object - isEmpty?
if (tui.util.isEmpty(object)) ...
// is value assigned?
if (tui.util.isExisty(variable)) ...
// is variable true?
if (variable) ...
// is variable false?
if (!variable) ...
The return value should be returned only once at the end of the function. However, if the function has a conditional statement, this does not necessarily have to be true. By following such convention, functions become more predictable and concise.
// Bad
function getResult() {
...
if (condition) {
...
return someDataInTrue;
}
...
return someDataInFalse;
}
// Allow
function foo(isValid) {
...
// conditional statement
if (!isValid) {
return;
}
...
return someDataInTrue;
}
// Good
function getResult() {
let resultData;
...
if (condition) {
...
resultData = someDataInTrue;
} else {
...
resultData = someDataInFalse;
}
return resultData;
}
return
statement.When a return
is written directly after other commands, it makes the code difficult to read. Therefore, always leave an empty line before the return
statement.**
// Bad
function getResult() {
...
return someDataInFalse;
}
// Good
function getResult() {
...
return someDataInFalse;
}
Generalized iterator methods decrease the possibility of a mistake.
// Good
var i, len
for (i = 0, len = array.length; i < len; i += 1) ...
// Good
[1, 2, 3].forEach(array, function(value, index) {
...
});
If the environment does not support iterator methods, use iterator methods provided by code-snippet.js. (ES5)
// Good
tui.util.forEachArray(array, function(value, index) {
...
});
for-in
statement.Unintentionally inherited properties could cause problems within the code.
// Good
for (const prop in object) {
if (object.hasOwnProperty(prop)) {
...
}
}
(ES5)
By declaring the counter first, it helps to prevent issues caused by the variable executing without having been reset.
// Bad
for (var i = 0; i < array.length; i += 1) ...
// Bad
for (var i in array) ...
// Good
var i, len
for (i = 0, len = array.length; i < len; i += 1) ...
// good
var key;
for (key in object) ...
When unnecessary closures are used, the increased number of scope chain references can compromise the performance and the readability.
// bad
let data1, data2, ...;
forEach(arr, function() {
data1 = someFunction1();
data2 = someFunction2();
...
});
// Allow
function foo() {
const length = ary.length;
let i = 0;
...
forEach(ary, function(data1, data2) {
...
// Variable declared out of scope out of necessiry; closure allowed.
i += (length + 2);
...
});
}
// Good
function foo() {
...
// Variable declared within the anonymous function scope.
forEach(ary, function(data1, data2) {
const data1 = someFunction1(data1);
const data2 = someFunction2(data2);
...
});
}
// Bad
function someFunction() {
...
// Comments on statements
statements
}
// Good
function someFunction() {
...
// Comments on statements
statements
}
single line comment
.// Bad
var someValue = data1;//Needs empty space before and after the comment
// Bad
var someValue = data1; /* multiline comment */
// Good
var someValue = data1; // Appropriate empty spaces before and after comment
multiline comment
, synchronize the *
indentation. Leave the first and last line of the comment empty.// Bad - '*' Inappropriate indentation
/*
* Comments
*/
// Bad - Disallow commenting on the first line
...
/* var foo = '';
* var bar = '';
* var quux;
*/
// Good - '*' Appropriate indentation
/*
* Comments
*/
comment out
an entire code block, use single line comment structure.// Bad - Used multline comments
...
/*
* var foo = '';
* var bar = '';
* var quux;
*/
// Good - Used single line comments
...
// var foo = '';
// var bar = '';
// var quux;
Codes compact with operators and keywords are difficult to read.
// Bad
var value;
if(typeof str==='string') {
value=(a+b);
}
// Good
var value;
if (typeof str === 'string') {
value = (a + b);
}
// Bad - Inappropriate white spaces
if ( typeof str === 'string' )
// Bad - Inappropriate white spaces
var arr = [ 1, 2, 3, 4 ];
// Good
if (typeof str === 'string') {
...
}
// Good
var arr = [1, 2, 3, 4];
Separating values and commas with white spaces increases readability. At FE Development Lab a white space is always included after the comma.
// Bad - No white space
var arr = [1,2,3,4];
// Good
var arr = [1, 2, 3, 4];
So far, this guide has discussed the coding convention. Coding convention is a must-have set of rules for JavaScript projects. This guide documents the basic JavaScript styles used at FE Development Lab. It is possible to integrate even more sophisticated coding convention if a project calls for it. It is the author’s hope that this guide helps developers write codes that are readable and manageable.
The employee trainings provided at FE Development Lab related to this document are as listed below. It is recommended to take these courses as well.
This document is an official Web Front-End development guide written and maintained by NHN Cloud. FE Development Lab. Any errors, questions, and points of improvement pertaining to this document should be addressed to the official support channel (dl_javascript@nhn.com).
Last Modified |
---|
2019. 03. 29 |