Categories
JavaScript Best Practices

JavaScript Antipatterns — Globals and Variables

JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.

In this article, we’ll look at some antipatterns that we should avoid when we write JavaScript code, including the avoidance of global variables.

Writing Maintainable Code

We got to write maintainable code to save us time when changing code.

Messy code is impossible to read and work with.

Even if we can do something with them, we’ll create bugs easily.

Minimizing Globals

We got to minimize the use of global variables.

This means that we shouldn’t create any global variables ourselves and we just use the ones that are built into the browser.

To avoid creating global variables, we should have strict mode on.

Strict mode is on for modules by default. And we can use the 'use strict' directive to turn it on for scripts.

We can put that anywhere in scripts, but we should put that at the top so that it applies to the whole script.

With strict mode on, we can’t write things like:

x = 1;

to create the global variable x .

However, even with a strict mode on, we can still attach properties to the window property to create global variables.

To avoid that, we just don’t write things like:

window.x = 1;

We just don’t want to deal with them as they can be changed anywhere so it’s hard to trace.

Also, their names can easily conflict.

The Problem with Globals

Global variables are shared by all scripts.

They live in the global namespace and are used not only by our own scripts but also 3rd party libraries, ads scripts, analytics scripts, etc.

To avoid creating global variables, we should use the let or const keywords.

Variables and constants created with them are block-scoped so that they’re only available within a block.

So we can write:

let x = 1;  
const y = 2;

and we won’t create global variables.

We also don’t want to write things like:

let x = y = 1;

since y would be a global variable while x is block-scoped.

The assignment is always evaluated right to left.

Side Effects When Forgetting let or const

If we forgot let or const , we’ll create global variables.

Access to the Global Object

The global object can be accessed with the window object in the browser.

However, we can create a function to access the global object in any context by writing:

const global = (function () {  return this; }());

At the top level, this is the global object.

So when we run that, global will be assigned to the window object in the browser.

This is because we didn’t invoke the function with the new keyword. Rather, we just called it directly.

Single let or const Pattern

let or const can be used to declare multiple variables and constants as follows:

let x = 1,  
  y = 2,  
  z = 3;

or we can write:

const x = 1,  
  y = 2,  
  z = 3;

They’ll all be let or const , so it’s different from chain assignments that we saw before.

It’s also great for assigning a property from the previous item to another variable.

For instance, we can write:

const el = document.querySelector("body"),  
  style = el.style;

We have the body DOM object assigned to el and then el.style is assigned to style all in one line.

Hoisting

We only have to worry about hosting when we use var to declare variables.

Hoisting is where the variable is available before it’s assigned.

Only the variable is available, but its value isn’t.

That’s just confusing, so it’s one reason that we shouldn’t use var .

Conclusion

There’re a few ways to write maintainable JavaScript code.

We should avoid global variables as much as possible.

Also, we should use let or const to declare variables and constants respectively.

If we really need global variables, we can access it safely by using a function.

We can also a property of a variable or constant that’s been assigned to the variable after if we use a comma to write multiple assignments.

Categories
JavaScript Best Practices

JavaScript Antipatterns — Naming and Documentation

JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.

In this article, we’ll look at some antipatterns that we should avoid when we format JavaScript code, including naming and documentation.

Naming Conventions

JavaScript code has some naming conventions that are standard to it.

We should follow it so that we can have consistency within a project.

However, we can tweak them slightly to fit our own style as long as it doesn’t conflict with other people’s styles.

Capitalizing Constructors and Classes

Constructors are names PascalCase.

Each word has the first letter capitalized.

For instance, we can define one as follows:

function FooConstructor() {...}

or:

class MyClass {...}

Other functions are camelCase.

Separating Words

Most identifiers are usually written in camelCase except for constructors as we mentioned above.

For instance, we can define variables by writing let firstName; .

A function can be written as calculateArea() .

Constants are written in all upper case with words separated by an underscore.

For instance, we can write const MAX_DONUTS = 1;

Property names are also usually written in camelCase, so we get person.firstName = 'bob';

Other Naming Patterns

There’re some names that don’t follow those conventions.

For instance, there are constant properties in the Number object that are written in the upper case.

For instance, Number.MAX_SAFE_INTEGER is a constant that has an upper case property name.

That indicates that it’s constant.

We may also have properties beginning with an underscore to indicate that it’s private.

Since there are no private variables in constructors and classes, we may have to do that to indicate that we shouldn’t access it directly.

For instance, we may write:

class Person {
  constructor(name) {
    this._name = name;
  }
}

We can do the same with methods. For instance, we can write:

class Person {
  constructor(name) {
    this._name = name;
  }

  _greet() {
    //...
  }
}

Likewise, we can do the same with objects:

const person = {
  getName() {
    return `${this._getFirst()} ${this._getLast()}`;
  },
  _getFirst() {
    // ...
  },
  _getLast() {
    // ...
  }
};

_getFirst and _getLast are private as indicate by the underscore at the beginning of the name.

So we shouldn’t call them directly even though we can.

Alternatively, we can use the following conventions for private and protected members:

  • trailing underscore means private
  • leading underscore for protected and 2 for private

We can adapt this to our liking.

Writing Comments

We should write comments for code that isn’t immediately obvious.

That means we should write comments on every variable or statement.

We should just explain things that aren’t clear in the code.

Also, we can discuss any decisions that we made if we need to justify them.

Writing API Docs

API docs are essential. If we let external users use our API then we need to document them.

Even though it might be boring and unrewarding, we got to do it so that everyone knows how to use our APIs.

We can usually write some comments and convert them into API documentation by using things like JSDoc.

Writing to Be Read

We got to write our docs so that it’s actually useful to the readers who read it.

Otherwise, we’re wasting our time. And the readers of our documentation would be frustrated.

The information should be accurate and the structure got to be clear and easy to follow.

We probably have to go through multiple drafts to get them right.

Writing API docs also provides an opportunity to revisit our code, so we can take a look at the code and revision it at that time if needed.

We should assume that other people would read it. Otherwise, we wouldn’t have to write it in the first place.

Peer Reviews

Peer reviews also improve our code. Reviewers can provide suggestions for improvements.

It also lets us see other people’s styles and learn what we missed from there code.

In the end, reviews help us write clear code at least because we know someone else will read it.

Source control systems are also essential. Not only it helps us undo bad changes easily.

It also lets people look at our code when we check them in.

Conclusion

We should stick with some naming conventions. Like camelCase for variables and upper case for constants.

Writing docs is also important since we need to tell me how to use our stuff.

Peer reviews are also good since we can learn from other people.

Categories
JavaScript Best Practices

JavaScript Antipatterns — Immediate Functions

JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.

In this article, we’ll look at some antipatterns that we should avoid when we’re defining and using functions.

Returning Functions

Returning functions is possible in JavaScript since functions are just regular objects.

For instance, we can define a function that returns another function as follows:

const foo = () => {
  return () => {
    console.log('bar');
  }
}

We return a function that logs 'bar' in the function above.

We can call it if we call foo to return the function. Then we can call it:

const bar = foo();
bar();

Self-Defining Functions

If we assign a function to a variable that already holds a function, then the old function will overwrite the existing one.

For instance, we can write:

let foo = () => {
  console.log("foo");
  foo = () => {
    console.log("bar");
  };
};

Then if we call it twice, we’ll get 2 different results:

foo()
foo()

And we’ll get:

foo
bar

in the console log output.

However, it’s probably not very useful in most cases even though we can do it.

Immediate Functions

We can also define immediately invoked function expressions (IIFEs), which are functions that are created immediately and called.

For instance, we can write an IIFE as follows:

(() => {
  console.log('foo');
})();

In the code above, we have the anonymous function that’s wrapped in parentheses.

Then we can call it right away by putting parentheses at the end.

This way, we don’t have to create a named function and call it somewhere else.

The pattern for creating an IIFE is as follows:

  • Define a function using a function expression or arrow function
  • Add a set of parentheses at the end to make the function run immediately.
  • Wrap the whole function in parentheses

This is useful because we can have variables that aren’t accessible to the outside.

It also can act as a module in that it can return things inside the function.

Then we can assign the returned values to a variable.

However, now that we have modules, we can use those instead of using IIFEs to hide private variables.

One good use of IIFEs is that we can use them to call async functions immediately after it’s defined.

So we can write:

(async () => {
  const val1 = await promise1;
  //...
  const val2 = await promise2;
  //...
})();

This way, we can use async functions without defining a named function.

Parameters of an Immediate Function

Immediate functions can also take parameters like any other function.

For instance, we can add parameters and use them as follows:

((firstName, lastName) => {
  console.log(`${firstName} ${lastName}`);
})('joe', 'smith');

The arguments are passed in at the end of the IIFE and run the function with those arguments right away.

So we get ‘joe smith’ from the console log output.

We can also use IIFEs to access global objects in a safe manner.

It lets us access global variables by using this at the top level.

So we can write:

const global = ((global) => {
  return global
})(this);

We pass in this from the outside, so we can use an arrow function to return the global parameter.

In the browser, this should be the window object.

Returned Values from Immediate Functions

We can return values from IIFEs as we saw before.

For instance, we can write the following:

const result = (() => {
  return 1 + 2;
})();

We’ll see 3 assigned to result since we called the function and returned the value right away.

It’s also useful for storing private data while returning others.

For instance, we can write:

const result = (() => {
  let x = 1;
  return x + 2;
})();

Then we get the same result. x isn’t available on the outside of the function.

IIFEs can also be used to define object properties.

So we can write something like:

const obj = {
  message: (() => {
    const who = "me",
      what = "call";
    return `${what} ${who}`;
  }()),

  getMsg() {
    return this.message;
  }
};

Then we get the message is 'call me' since the value of the template, the string is returned by the IIFE.

getMsg is the same because this is obj and message is 'call me' .

Conclusion

We can use IIFEs to store private data. Also, we can use them to return what we want.

They’re also handy for running async functions without defining a named function.

They take parameters like any other function.

Functions can also return other functions.

Categories
JavaScript Best Practices

JavaScript Antipatterns — Functions

JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.

In this article, we’ll look at some antipatterns that we should avoid when we’re defining and using functions.

Names and Hoisting

Function declarations ae hoisted. This means that they’re pulled up to the top.

Their definition can’t be assigned to variables or properties or appear in invocations as parameters.

For instance, if they’re at the top-level:

function foo() {}

Then they’re available everywhere.

It the function is inside a function as follows:

function local() {
  // local scope
  function bar() {}
  return bar;
}

Then bar is returned when we called local , so that it’s not available everywhere.

Function’s name Property

To get the name of the function, we can use the name property of the function.

For instance, if we write:

function foo() {}

Then we can check the name as follows:

console.log(foo.name);

However, if we have the following:

let bar = function foo() {}

Then bar.name is still 'foo' .

This also works with arrow functions. So if we write:

let bar = () => {}

Then bar.name is still 'bar' .

Function Hoisting

Functions declarations are hoisted.

So we can define a function declaration anywhere and call it.

For instance, we can write:

foo();

function foo() {
  console.log('foo');
}

And 'foo' will be logged.

However, if we the following function expression:

bar();

var bar = function() {
  alert('bar');
};

Then we get bar is not a function error.

This is one reason we shouldn’t use var to declare variables.

var only hoists the variable, but not its value.

This is a major source of confusion for many people looking at JavaScript apps.

Callback Pattern

Functions are objects, so we can pass them into other functions as parameters.

For instance, we can write the following:

function foo(callback) {
  // ...
  callback();
  // ...
}

or:

const foo = (callback) => {
  // ...
  callback();
  // ...
}

callback is a function that can be called.

A callback can be a standalone function or a method of an object.

For instance, we can write:

const person = {
  name: 'joe',
  getName() {
    console.log(this.name);
  }
};

const foo = (callback) => {
  callback();
}

foo(person.getName);

We don’t get 'joe' logged.

Instead, we should write:

const person = {
  name: 'joe',
  getName() {
    console.log(this.name);
  }
};

const foo = (callback, obj) => {
  callback.call(obj);
}

foo(person.getName, person);

We need the call method to call the callback with the object that we want to set as the value of this inside the callback function.

call lets us change the value of this and call it with arguments that we want.

We may also want to check if the value passed in is actually a function.

To do that, we can use the typeof operator to do that.

For instance, we can write:

const foo = (callback, obj) => {
  if (typeof callback === "function") {
    callback.call(obj);
  }
}

This will make sure that callback is actually a function before calling it.

Asynchronous Event Listeners

Async event listeners are used everywhere in JavaScript.

Therefore, we should be aware of them.

The callbacks passed in there are called in an indeterminate amount of time. For instance, when an event is triggered.

For instance, we can write:

document.addEventListener("click", console.log, false);

to log the clicks events triggered on the page.

We can replace 'click' with other events like 'keypress' , 'mouseover' , 'mousemove' , and many others.

Timeouts

Async callbacks are also used by timers.

So setTimeout and setInterval all take callbacks which let us call them in when the given amount of time in milliseconds has elapsed.

For instance, we can write:

setTimeout(() => console.log('time is up'), 500);

And we get that string logged in 500ms.

For setInterval , we can do the same thing:

setInterval(() => console.log('time is up'), 500);

Then the string will be logged every half a second.

Conclusion

Functions are first-class in JavaScript, which means that they’re treated like any object.

They have properties and can be passed into other functions as arguments.

This is why we can have callbacks in JavaScript. We can pass in a function and call it inside another function.

Categories
JavaScript Best Practices

JavaScript Antipatterns — Formatting and More

JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.

In this article, we’ll look at some antipatterns that we should avoid when we format JavaScript code, naming, and white spaces.

Coding Conventions

We got to establish some coding conventions so that it’s predictable and neat.

That helps with understanding a lot better.

Indentation

It’s impossible to read code without indentation. Therefore, we should indent our code.

To do that, we should indent code inside blocks.

So we write:

const foo = () => {  
  console.log('foo')  
}

or:

for (let i = 0; i < max; i++) {  
  console.log('foo')  
}

or:

if (val === 'foo') {  
  console.log('foo')  
}

We do the same with any kind of nesting.

2 spaces is a good idea. It’s more portable than tabs as they’re the same everywhere.

A space character is always a space character, but tabs are up for interpretation.

Curly Braces

As we can see, we have curly braces in all our blocks.

We can skip them if we only have one line in the if or loop body.

But it’s not a good idea to skip them since it makes reading the blocks much easier.

Therefore, we should always use curly braces to delimit blocks.

For instance, we shouldn’t write:

for (let i = 0; i < 10; i++)  
  console.log(i);

But instead, we write:

for (let i = 0; i < 10; i++) {  
  console.log(i);  
}

Similar instead of writing the following if block:

if (i === 1)   
  console.log(i);

We write:

if (i === 1) {  
  console.log(i);  
}

Opening Brace Location

The opening brace is location is important. We may interpret the code differently in different places.

In JavaScript, it’s usually in the first line of the block.

For instance, we have:

if (i === 1) {  
  console.log(i);  
}

and:

for (let i = 0; i < 10; i++) {  
  console.log(i);  
}

and:

const foo = () => {  
  console.log('foo')  
}

where the opening brace is always on the first line.

This is especially important is we’re returning something.

For instance, the following:

const foo = () => {  
  return {  
    foo: 'foo'  
  }  
}

is different from:

const foo = () => {  
  return   
  {  
    foo: 'foo'  
  }  
}

The code above is actually the same as:

const foo = () => {  
  return;  
  {  
    foo: 'foo'  
  }  
}

So it won’t return the object.

Javascript automatically inserts semicolons when it thinks it makes sense.

Therefore, in return statements, we should have the opening curly brace in the same line as the return keyword.

White Space

Having white space also improves readability.

For instance, if we have:

x<1

That’s not too readable.

However, if we have:

x < 1

That’s a lot clearer.

Good places to put white spaces include the following locations:

  • after the semicolons for separating parts of a loop — for (let i = 0; i < 10; i++){ }
  • initializing multiple variables — let a = 1, b = 2;
  • after the comma that separate array item s— [1, 2, 3]
  • after commas in object properties — {a: 1, b: 2}
  • separating function arguments — fn(a, b, c)
  • before curly braces in functions declarations — function fn() {}
  • after the function keyword — function () {};

We should also put spaces between operands in arithmetic or comparison operations.

For instance, 1 + 2 is better than 1+2 ;

Naming Conventions

Names are also important. They’ve to descriptive and they have to be consistent.

This way we can predict what things mean and won’t have to look as hard to find out their true meaning.

Capitalizing Constructors and Classes

Constructor and class names should start with a capital.

For instance, we write:

function FooConstructor() {...}

and:

class Foo {...}

All words should start with capital as we can see.

Conclusion

We should stick to some common conventions for formatting JavaScript code.

This includes capitalizing first letters of words in constructor and class names.

Also, we should add spaces and indentation to make reading code easier.

In addition, we should always have curly braces for blocks whether they’re required or not.

Also, we should have braces in the right location so that they won’t be interpreted in ways that we don’t expect.