Categories
JavaScript Best Practices

Why Should We Be Careful of JavaScript Type Coercion?

Type coercion can be a convenience feature or a trap in JavaScript. Learn why here.

Since JavaScript is a dynamically typed programming language, data types of objects and variables can change on the fly. This a problem that we’ll face often as we write more and more JavaScript programs. There’re a few things to be aware of with type coercion, which is the conversion of data types on the fly during program execution.

Type Coercion

As we mentioned, type coercion is the changing of data types on the fly. It happens when data doesn’t match the expected type. For example, if we want to manipulate numbers and string with numbers, we can write:

2*'5'

and we get back 10.

This may seem like a great convenience feature, but it also sets up lots of traps we can fall into. For example, if we have:

1 +'1'

We get:

"11"

which isn’t what we want.

JavaScript has type coercion also because the language originally didn’t have exceptions, so it returns some values for doing invalid operations. Examples of these values include Infinity or NaN , which are return when we divide a number by 0 or try to convert something that doesn’t have numerical content to a number respectively.

NaN stands for not a number.

For example, we get that:

+'abc'

if NaN since it tries to convert the string 'abc' into a number unsuccessfully, so instead of throwing an exception, it returns NaN .

More modern parts of JavaScript do throw exceptions. For example, if we try to run:

undefined.foo

Then we get ‘Uncaught TypeError: Cannot read property ‘foo’ of undefined.’

Another example would be mixing number and BigInt operands in arithmetic operations:

6 / 1n

Then we get ‘Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions.’

How Does JavaScript Type Coercion Work?

Type coercion is done within the JavaScript interpreter. There’re functions built into almost all browsers to do this. We have the Boolean for converting values to boolean, Number to convert values to numbers and so on.

Avoiding Type Coercion Traps

To avoid falling into traps caused by type coercion, we should check the type of the object and convert it to the same type before operating on them.

Number

For example, we use the Number function to convert anything into numbers. For example, we can use it as follows:

Number(1) // 1
Number('a') // NaN
Number('1') // 1
Number(false) // 0

The Number function takes an object of any type as the argument and tries to convert it to a number. If it can’t, then it’ll return NaN .

We can also use the + operator in front of a variable or a value to try to convert it to a number. For example, we can write:

+'a'

Then we get NaN . If we write:

+'1'

Then we get 1.

String

To convert objects to a string, we can use the String function. It also takes an object and tries to convert it into a string.

If we pass in an object, we get back:

"[object Object]"

For example, writing:

String({})

will get us that.

Primitive values will get us the string with the same content as the primitive value. For example, if we write:

String(123)

We get “123” .

All Objects other than ones we specifically remove the prototype from will have a toString method.

For example, if we write:

({}).toString()

We get “[object Object]” back.

If we write:

2..toString()

Then we get back “2” . Note that we have 2 dots since the first dot designates the number as a number object and then the second dot lets us call methods on the number object.

Other weird conversions involving strings that can’t be explained with reason include:

`"number" + 1 + 3        // 'number13'
1 + 3 + "number"        // '4number'
"foo" + + "bar"         // 'fooNaN'
`{}+[]+{} // '[object Object][object Object]'
`!+[]+[]+![]             // '`truefalse'
`[] + null + 2           // 'null2'`

Symbol.toPrimitive

Objects also have the Symbol.toPrimitve method that converts an object to a corresponding primitive value. It’s called when the + unary operator is used or converting an object to a primitive string. For example, we can write our own Symbol.toPrimitive method to convert various values to a primitive value:

let obj = {
    [Symbol.toPrimitive](hint) {
        if (hint == 'number') {
            return 10;
        }
        if (hint == 'string') {
            return 'hello';
        }
        if (hint == 'true') {
            return true;
        }
        if (hint == 'false') {
            return false;
        }
        return true;
    }
};
console.log(+obj);
console.log(`${obj}`);
console.log(!!obj);
console.log(!obj);

Then we get:

10
hello
true
false

from the console.log statements at the bottom of our code.

Avoid Loose Equality

Loose equality comparison is done by the == operator. It compares the content of its 2 operands for equality by converting to the same type before comparison. For example,

1 == '1'

will evaluate to true .

A more confusing example would be something like:

1 == true

Since true is truthy, it’ll be converted to a number first before comparing them. So true will be converted to 1 before comparing, which makes the expression true.

To avoid a confusing situation like this, we use the === comparison operator instead.

Then

1 === '1'

and

1 === true

will both be false , which makes more sense since their types are different. No type coercion will be done by the === operator on the operands. Both the type and content are compared.

Comparison issues we mentioned above apply to primitive values. Object are compared by their reference so if the operands have a different reference then it evaluates to false no matter which operator we use.

With these functions, we converted our variables and values to the type we explicitly have written. It makes the code much more clear and we don’t have to worry about the JavaScript interpreter trying to convert things into a type that we don’t want. Also, we should use the === operator instead of the == operator to compare primitive values.

Categories
JavaScript Best Practices

Why Should We Stop Using Objects As Maps in JavaScript?

Before ES6, to make a dictionary or a map, we often used objects to store the keys and values. This has some problems that can be avoided with maps.

An object lets us map strings to values. However, with the pitfalls of JavaScript objects and the existence of the Map constructor, we can finally stop using objects as maps or dictionaries.

Inheritance and Reading Properties

Normally, objects in JavaScript inherit from the Object object if no prototype is explicitly set. This means that we have methods that are in the prototype of the object.

To check if the property is in the object or its prototype, we have to use the hasOwnProperty of the object. This is a pain and we can easily forget about this.

This means that we can accidentally get and set properties that aren’t actually in the object that we defined. For example, if we define an empty object:

let obj = {}

Then, when we write:

'toLocaleString' in obj;

We get the value of true returned. This is because the in operator designates properties in the object’s prototype as being part of the object, which we don’t really want for dictionaries or maps.

To create a pure object with no prototype, we have to write:

let obj = Object.create(null);

The create method takes a prototype object of the object it creates as an argument, so we’ll get an object that doesn’t inherit from any prototype. Built-in methods like toString or toLocaleString, they aren’t enumerable, so they won’t be included in the for...in loop.

However, if we create an object with enumerable properties as we do in the following code:

let obj = Object.create({
  a: 1
});

for (const prop in obj) {
  console.log(prop);
}

Then, we do get the a property logged in the for...in above, which loops through all the owned and inherited properties of an object.

To ignore the inherited properties, we can use the hasOwnProperty method of an object. For example, we can write:

let obj = Object.create({
  a: 1
});

for (const prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    console.log(prop);
  }
}

Then, we don’t get anything logged.

As we can see, accessing values with property keys can be tricky with regular JavaScript objects.

Overriding Values of Properties

With plain objects, we can easily override and delete existing properties. Any writable properties can have their value overridden or deleted.

We can assign values to any property that’s in an object. For example, we can write:

let obj = {};
obj.toString = null;

Then, when we run:

obj.toString();

We get the error Uncaught TypeError: obj.toString is not a function.

This is a big problem since we can easily change the value of any original or inherited property of an object. As we can see, we overwrote the built-in toString method with null with just one assignment operation.

This means that using objects as dictionaries or Maps is risky since we can easily do this accidentally. There’s no way to prevent this other than checking the values that may be names of built-in methods.

The Object’s Prototype

The prototype of an object is accessible by its __proto__ property. It’s a property that we can both get and set. For example, we can write:

let obj = Object.create({
  a: 1
});

obj.__proto__ = {
  b: 1
};

Then, our object’s prototype is { b: 1 }. This means that we changed the prototype of obj, which was { a: 1 } originally, to { b: 1 }, just by setting the __proto__ property of obj.

When we loop through the obj object with the for...in loop like the following code:

for (const prop in obj) {
  console.log(prop);
}

We get b logged.

This means that we have to avoid accessing the __proto__ property when we try to access our object that we use for a dictionary or map. What we have is another trap that might get us when using objects as maps or dictionaries.

Getting Own Enumerable Properties to Avoid Traps

To avoid traps of getting properties that are inherited from other objects, we can use the Object.keys to get the object’s own property names. It returns an array with the keys of the object that we defined and omits any inherited property names.

For example, if we have:

let obj = Object.create({
  a: 1
});
console.log(Object.keys(obj));

Then we get an empty array logged.

Similarly, Object.entries accepts an object as an argument and returns an array with arrays that have the key as the first element and the value of the key as the second element.

For example, if we write:

let obj = Object.create({
  a: 1
});
console.log(Object.entries(obj));

Then we also get an empty array logged.

ES6 Maps

Better yet, we should be using ES6 Map objects, which are an actual implementation of a map or dictionary.

Map objects have the set method that lets us add keys and values, which are the first and second arguments of what the method accepts respectively.

We can define Maps as we do in the following code:

let objMap = new Map();
objMap.set('foo', 'bar');
objMap.set('a', 1);
objMap.set('b', 2);

Instead of using the set method to add our keys and values, we can also pass a nested array where each entry of the array has the key as the first element and the value as the second element.

One good thing about Map objects is that we can use non-string keys. For example, we can write:

let objMap = new Map();
objMap.set(1, 'a');

We can also use nested arrays to define a Map. For example, instead of using the set method multiple times, we can write the following:

const arrayOfKeysAndValues = [
  ['foo', 'bar'],
  ['a', 1],
  ['b', 2]
]
let objMap = new Map(arrayOfKeysAndValues);
console.log(objMap)

There are also specialized methods to get entries by key, get all entries, loop through each entry, and remove entries. We can use the get method to get an entry by its key:

objMap.get('foo'); // 'bar'

We can also get a value from a non-string key, unlike objects. For instance, if we have:

let objMap = new Map();
objMap.set(true, 'a');

Then console.log(objMap.get(true)); will get us 'a'.

And we can clear all entries of the Map object with the clear method. For example, we can write:

objMap.clear();

We can get all entries with the objMap.entries method and we can use the for...of loop to loop through the items as well since it has an iterator.

Conclusion

We should stop using objects as dictionaries now. There are too many pitfalls since objects inherit from the Object object by default and other objects as we set them.

It also lets us override the value of methods like toString which isn’t a result we want most of the time.

To avoid these issues, we should use the Map object which was introduced in ES6. It has special methods to get and set entries and we can loop through them with the for...of loop or convert the object to an array.

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.