Hackernoon logoTop 12 Lesser Known Tips for JavaScript Best Practices by@wownetort

Top 12 Lesser Known Tips for JavaScript Best Practices

image
Nikita Starichenko Hacker Noon profile picture

Nikita Starichenko

7+ years full-stack developer

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.

1. Variables declared with "var" should be declared before they are used

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"

2. Variables should be declared with "let" or "const"

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;

3. The global "this" object should not be used

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) {
    // ...
  }
}

4. Variables and functions should not be declared in the global scope

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() { }
})();

5. "undefined" should not be assigned

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
}

6. "NaN" should not be used in comparisons

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");
}

7. Jump statements should not occur in "finally" blocks

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
    }
}

8. Promise rejections should not be caught by 'try' block

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);
  }
}

9. "await" should not be used redundantly

An async function always wraps the return value in a Promise. Using return

await
 is therefore redundant.

Bad example:

async function foo() {
  // ...
}

async function bar() {
  // ...
  return await foo(); // Noncompliant
}

Good example:

async function foo() {
  // ...
}

async function bar() {
  // ...
  return foo();
}

10. "void" should not be used

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.

Bad example:

void (function() {
   ...
}());

Good example:

(function() {
   ...
}());

11. Shorthand promises should be used

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).

Bad example:

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');

12. "future reserved words" should not be used as identifiers

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

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.