Categories
JavaScript Best Practices

Object-Oriented JavaScript — Objects and Constructors

JavaScript is partly an object-oriented language.

To learn JavaScript, we got to learn the object-oriented parts of JavaScript.

In this article, we’ll look at objects and constructors.

Accessing an Object’s Properties

We can access an object’s properties by using the square bracket notation or the dot notation.

To use the square bracket notation, we can write:

dog['name']

This works for all property names, whether they’re valid identifiers or not.

We can also use them to access properties by passing in a property name dynamically with a string or symbol.

The dot notation can be used by writing:

dog.name

This is shorter but only works with valid identifiers.

An object can contain another object.

For instance, we can write:

const book = {
  name: 'javascript basics',
  published: 2020,
  author: {
    firstName: 'jane',
    lastName: 'smith'
  }
};

The author property has the firstName and lastName properties.

We can get nested properties by writing:

book['author']['firstName']

or

book.author.firstName

We can mix the square brackets and dot notation.

So we can write:

book['author'].firstName

Calling an Object’s Methods

We can call a method the same way we call any other function.

For instance, if we have the following object:

const dog = {
  name: 'james',
  gender: 'male',
  speak() {
    console.log('woof');
  }
};

Then we can call the speak method by writing:

dog.speak()

Altering Properties/Methods

We can change properties by assigning a value.

For instance, we can write:

dog.name = 'jane';
dog.gender = 'female';
dog.speak = function() {
  console.log('she barked');
}

We can delete a property from an object with the delete operator:

delete dog.name

Then when we try to get dog.name , we get undefined .

Using this Value

An object has its own this value.

We can use it in our object’s methods.

For instance, we can write:

const dog = {
  name: 'james',
  sayName() {
    return this.name;
  }
};

We return this.name , which should be 'james' .

Because this is the dog object within the sayName method.

Constructor Functions

We can create constructor functions to let us create objects with a fixed structure.

For instance, we can write:

function Person(name, occupation) {
  this.name = name;
  this.occupation = occupation;
  this.whoAreYou = function() {
    return `${this.name} ${this.occupation}`
  };
}

We have the instance properties name , occupation and the this.whoAreYou instance method.

They’re all returned when we create a new object with the constructor.

Then we can use the new operator to create a new Person instance:

const jane = new Person('jane', 'writer');

The value of this os set to the returned Person instance.

We should capitalize the first letter of the constructor so that we can tell them apart from other functions.

We shouldn’t call constructor functions without the new operator.

So we don’t write:

const jane = Person('jane', 'writer');

The value of this won’t be set to the returned Person instance this way.

The Global Object

The global object in the browser is the window object.

We can add properties to it with var at the top level:

var a = 1;

Then window.a would be 1.

Calling a constructor without new will return undefined .

So if we have:

const jane = Person('jane', 'writer');

then jane is undefined .

The built-in global functions are properties of the global object.

So parseInt is the same as window.parseInt .

Conclusion

We can access object properties in 2 ways.

Also, we can create constructor functions to create objects with a set structure.

Categories
JavaScript Best Practices

JavaScript Antipatterns —Loops

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

for Loops

for loops are handy for iterating over arrays and array-like objects.

For instance, we can write:

const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  // do something with arr[i]
}

This loops the indexes of the arr array and let us do something with each entry.

It also works with array-like objects. Nodelists are array-like objects.

We can call the following methods to get a Nodelist with multiple DOM elements:

  • document.querySelectorAll()
  • document.getElementsByName()
  • document.getElementsByClassName()
  • document.getElementsByTagName()

document.querySelectorAll() is the most versatile since it accepts any CSS selector.

document.getElementsByName only returns the items with the given name attribute.

document.getElementsByClassName only returns the items with the given class name.

document.getElementsByTagName returns items with the given tag name.

Also, the following properties of the document have specific items

  • document.images — all img elements on a page
  • document.links — all a elements
  • document.forms — all forms
  • document.forms[0].elements — all fields in a form

We can loop through them as follows:

for (let i = 0; i < document.links.length; i++) {
  // do something with document.links[i]
}

With the for loop, we can define all the initial conditions in the first expression, so we can write:

for (let i = 0, max = arr.length; i < max; i++) {
  // do something with arr[i]
}

which is the same as:

for (let i = 0; i < arr.length; i++) {
  // do something with arr[i]
}

i++ is the same as i = i + 1 or i += 1 .

i++ can be tricky if we assign it to something.

i++ returns the original value of i instead of the updated value.

So if we have:

let i = 1;
const a = i++;

We get that a is 1.

for-in Loops

for-in loops are useful for looping through keys of an object and its prototypes.

Therefore, if we only want to loop through the object’s non-inherited keys, we’ve to use the hasOwnProperty method.

For instance, we can use it by writing:

const obj = {
  a: 1,
  b: 2
}

for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    //...
  }
}

That will only loop through the keys that are non-inherited as hasOwnProperty checks that.

We can also call hasOwnProperty as follows:

const obj = {
  a: 1,
  b: 2
}

for (const key in obj) {
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
    //...
  }
}

This will avoid issues when the obj object redefined hasOwnProperty to be something else.

We can also write:

const obj = {
  a: 1,
  b: 2
}

const hasOwn = Object.prototype.hasOwnProperty;
for (const key in obj) {
  if (hasOwn.call(obj, key)) {
    //...
  }
}

to cache the hasOwnProperty method.

for-of Loop

The for-of loop is more versatile than the other loops. It’s useful for looping through the arrays and array-like objects.

So it’s a great alternative to the for loop for looping through those objects.

It also works with all the document properties listed above and also new data structures like sets and maps.

We can use it as follows:

for (const link of document.links) {
  // do something with link
}

For arrays, we can write:

for (const a of arr) {
  // do something with a
}

It also works with the destructuring assignment syntax:

for (const { foo, bar } of arr) {
  // do something with foo and bar
}

It also works great with sets and maps:

for (const s of set) {
  // do something with a
}

For maps, we can write:

const map = new Map([
  ['a', 1],
  ['b', 2]
])

for (const [key, value] of map) {
  // do something with key and value
}

Conclusion

A for loop is great for looping through arrays and array-like objects.

However, the for-loop beats that by letting us loop through any iterable object.

The for-in loop has limited uses for looping through keys in an object. If we want to loop through their prototype’s keys as well, then we can use that.

Categories
JavaScript Best Practices

JavaScript Antipatterns — Arrays, JSON, and Regex

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 arrays, JSON, and regex.

Array Literals

We can define an array with the Array constructor with array literals.

The constructor has 2 versions. The one argument version is different from the one with multiple arguments.

If we pass in one argument, it’ll create an array with the given length with all empty entries.

If we pass in more than one argument, then an array with the arguments will be created.

For instance, we can create one as follows:

const empty = new Array(5);

or:

const arr = new Array(1, 2, 3);

Array Literal Syntax

The array literal syntax lets us create an array without the array constructor.

For instance, we can write:

const arr = [1, 2, 3];

This syntax is simple and straightforward.

It’s cleaner than using the new operator and the constructor.

Avoid the Array Constructor

Because of the different versions of the array constructor, then we would have issues because of the confusion between them.

If we pass in a floating-point number into the Array constructor, then we get an error.

This is because a floating-point number isn’t a valid length.

For instance, if we have:

const a = new Array(3.14);

Then we get ‘Uncaught RangeError: Invalid array length’.

Check if an object is an Array

Using the typeof operator isn’t useful for checking if an object is an array.

If we write:

console.log(typeof [1, 2]);

we get 'object' on the console log output.

Therefore, we can’t use the typeof operator to check if an object is an array.

We can use the code instanceof Array to check if something is an array.

It may fail in IE when used across frames, but this shouldn’t be an issue most of the time.

So we can use:

[1, 2] instanceof Array

Also, we can use the Array.isArray method, which does work across frames.

We can use it by writing:

Array.isArray(`[1, 2])`

It works across frames and only returns true if it’s actually an array.

JSON

JSON stands for JavaScript Object Notation.

It’s the main data transfer format for JavaScript apps.

It’s just the combination of arrays and the object literal notation.

The only difference between a JavaScript object and a JSON string is that we’ve to wrap keys with double-quotes.

JSON strings can’t have functions or regex literals.

Working with JSON

The best way to work with JSON is to use the JSON.parse method, which is available since ES5.

We can sue it to parse JSON strings to JavaScript objects.

To convert an object to a string, we can use the JSON.stringify method.

For instance, we can use them as follows:

const str = JSON.stringify({
  foo: 1,
  bar: new Date(),
})

Then we get:

"{"foo":1,"bar":"2020-04-20T15:18:51.339Z"}"

as the value of str .

To parse a JSON string, we can write:

const obj = JSON.parse("{\"foo\":1,\"bar\":\"2020-04-20T15:18:51.339Z\"}")

Once an object is converted to a string, then it’ll be converted back to a string.

So we get:

{foo: 1, bar: "2020-04-20T15:18:51.339Z"}

as the value of obj .

Regular Expression Literal

A regex in JavaScript is also an object.

We can create them using the RegExp constructor or a regex literal.

For instance, we can create it as follows:

const re = /foo/gm;

as a regex literal, or:

const re = new RegExp("foo", "gm");

using the constructor.

The regex literal is shorter and we don’t have to think about using the constructor.

So it’s better to use the literal.

If we use the constructor, we’ve to escape quotes and backslashes.

Therefore, it’s hard to read and modify.

To make things simple, we should stick with the literal notation.

Regular Expression Literal Syntax

The regex syntax uses forward slash to delimit them and then we have the modifiers.

They include:

  • g — global match
  • m — multiline
  • i — case insensitive match

The modifiers can be added in any order.

For instance, we can use regex literals in the string replace method:

const str = "abc123".replace(/[a-z]/gi, "");

Then we stripped all the letters our of the string and get 123 .

The regex matches all letters in the string and replaced them with empty strings.

Conclusion

We should use literals to define arrays and regex whenever we can.

Ther literal notations are usually shorter and cleaner.

Also, the Array.isArray method is great for checking if an object is an array.

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.