Hi everyone! There is a lot of information about different JS best practices. About various life hacks and features in this language. I want to tell you about equally useful, but less popular tips for working with this JavaScript.
Variables declared with
var
have the special property that regardless of where they're declared in a function they "float" to the top of the function and are available for use even before they're declared. That makes scoping confusing, especially for new coders.To keep confusion to a minimum,
var
declarations should happen before they are used for the first time.Bad example:
var x = 1;
function fun(){
alert(x); // Noncompliant as x is declared later in the same scope
if(something) {
var x = 42; // Declaration in function scope (not block scope!) shadows global variable
}
}
fun(); // Unexpectedly alerts "undefined" instead of "1"
Good example:
var x = 1;
function fun() {
print(x);
if (something) {
x = 42;
}
}
fun(); // Print "1"
ECMAScript 2015 introduced the
let
and const
keywords for block-scope variable declaration. Using const creates a read-only (constant) variable.The distinction between the variable types created by
var
and by let is significant, and a switch to let will help alleviate many of the variable scope issues which have caused confusion in the past.Because these new keywords create more precise variable types, they are preferred in environments that support ECMAScript 2015. However, some refactoring may be required by the switch from
var
to let
, and you should be aware that they raise SyntaxErrors in pre-ECMAScript 2015 environments.This rule raises an issue when
var
is used instead of const
or let
.Bad example:
var color = "blue";
var size = 4;
Good example:
const color = "blue";
let size = 4;
When the keyword this is used outside of an object, it refers to the global this object, which is the same thing as the window object in a standard web page. Such uses could be confusing to maintainers. Instead, simply drop the this, or replace it with window; it will have the same effect and be more readable.
Bad example:
this.foo = 1; // Noncompliant
console.log(this.foo); // Noncompliant
function MyObj() {
this.foo = 1; // Compliant
}
MyObj.func1 = function() {
if (this.foo == 1) { // Compliant
// ...
}
}
Good example:
foo = 1;
console.log(foo);
function MyObj() {
this.foo = 1;
}
MyObj.func1 = function() {
if (this.foo == 1) {
// ...
}
}
Any variable or function declared in the global scope implicitly becomes attached to the global object (the window object in a browser environment). To make it explicit this variable or function should be a property of window. When it is meant to be used just locally, it should be declared with the const or let keywords (since ECMAScript 2015) or within an Immediately-Invoked Function Expression (IIFE).
This rule should not be activated when modules are used.
Bad example:
var myVar = 42; // Noncompliant
function myFunc() { } // Noncompliant
Good example:
window.myVar = 42;
window.myFunc = function() { };
or
let myVar = 42;
let myFunc = function() { }
or
// IIFE
(function() {
var myVar = 42;
function myFunc() { }
})();
undefined
is the value you get for variables and properties which have not yet been created. Use the same value to reset an existing variable and you lose the ability to distinguish between a variable that exists but has no value and a variable that does not yet exist. Instead, null should be used, allowing you to tell the difference between a property that has been reset and one that was never created.Bad example:
var myObject = {};
// ...
myObject.fname = undefined; // Noncompliant
// ...
if (myObject.lname == undefined) {
// property not yet created
}
if (myObject.fname == undefined) {
// no real way of knowing the true state of myObject.fname
}
Good example:
var myObject = {};
// ...
myObject.fname = null;
// ...
if (myObject.lname == undefined) {
// property not yet created
}
if (myObject.fname == undefined) {
// no real way of knowing the true state of myObject.fname
}
NaN
is not equal to anything, even itself. Testing for equality or inequality against NaN
will yield predictable results, but probably not the ones you want.Instead, the best way to see whether a variable is equal to
NaN
is to use Number.isNaN(), since ES2015, or (perhaps counter-intuitively) to compare it to itself. Since NaN !== NaN, when a !== a, you know it must equal NaN
.Bad example:
var a = NaN;
if (a === NaN) { // Noncompliant; always false
console.log("a is not a number"); // this is dead code
}
if (a !== NaN) { // Noncompliant; always true
console.log("a is not NaN"); // this statement is not necessarily true
}
Good example:
if (Number.isNaN(a)) {
console.log("a is not a number");
}
if (!Number.isNaN(a)) {
console.log("a is not NaN");
}
Using return, break, throw, and continue from a
finally
block overwrites similar statements from the suspended try and catch blocks.This rule raises an issue when a jump statement (break, continue, return and throw) would force control flow to leave a
finally
block.Bad example:
function foo() {
try {
return 1; // We expect 1 to be returned
} catch(err) {
return 2; // Or 2 in cases of error
} finally {
return 3; // Noncompliant: 3 is returned before 1, or 2, which we did not expect
}
}
Good example:
function foo() {
try {
return 1; // We expect 1 to be returned
} catch(err) {
return 2; // Or 2 in cases of error
}
}
An exception (including reject) thrown by a promise will not be caught by a nesting try block, due to the asynchronous nature of execution. Instead, use catch method of Promise or wrap it inside await expression.
This rule reports try-catch statements containing nothing else but call(s) to a function returning a Promise (thus it's less likely that catch is intended to catch something else than Promise rejection).
Bad example:
function runPromise() {
return Promise.reject("rejection reason");
}
function foo() {
try { // Noncompliant, the catch clause of the 'try' will not be executed for the code inside promise
runPromise();
} catch (e) {
console.log("Failed to run promise", e);
}
}
Good example:
function foo() {
runPromise().catch(e => console.log("Failed to run promise", e));
}
// or
async function foo() {
try {
await runPromise();
} catch (e) {
console.log("Failed to run promise", e);
}
}
An async function always wraps the return value in a Promise. Using return
await
is therefore redundant.async function foo() {
// ...
}
async function bar() {
// ...
return await foo(); // Noncompliant
}
Good example:
async function foo() {
// ...
}
async function bar() {
// ...
return foo();
}
The
void
operator evaluates its argument and unconditionally returns undefined. It can be useful in pre-ECMAScript 5 environments, where undefined could be reassigned, but generally, its use makes code harder to understand.void (function() {
...
}());
Good example:
(function() {
...
}());
When a Promise needs to only "
resolve
" or "reject
", it's more efficient and readable to use the methods specially created for such use cases: Promise.resolve(value) and Promise.reject(error).let fulfilledPromise = new Promise(resolve => resolve(42));
let rejectedPromise = new Promise(function(resolve, reject) {
reject('fail');
});
Good example:
let fulfilledPromise = Promise.resolve(42);
let rejectedPromise = Promise.reject('fail');
The following words may be used as keywords in future evolutions of the language, so using them as identifiers should be avoided to allow an easier adoption of those potential future versions:
await
class
const
enum
export
extends
implements
import
interface
let
package
private
protected
public
static
super
yield
Use of these words as identifiers would produce an error in JavaScript strict mode code.
P.S. Thanks for reading! More tips coming soon!
Special thanks to SonarQube and their rules - https://www.sonarqube.org/
More tips: Top 25 C# Programming Tips