Categories
JavaScript Best Practices

JavaScript Best Practices — Creating Objects

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at the best ways to create JavaScript objects, which isn’t by using the Object constructor.

Don't Use the Object Constructor

The Object constructor lets us create an object. However, it isn’t needed to create an object since there’re shorter ways to do it.

For instance, instead of writing the following using the Object constructor to create an object, we can instead write:

const obj = new Object();
obj.a = 1;
obj.b = 2;

We can instead write:

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

As we can see, we avoid a lot of extra typing by creating an object literal directly rather than using the Object constructor.

We can also create constructor functions or classes to create a template function that we can use to create new objects.

For instance, we can create the Person constructor function using the class syntax so that we can create new Person objects with it using the new operator.

We can write the following to create the Person constructor:

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

In the class above, we have the name parameter which is used to set the name instance variable in the Person instance.

Then we can create multiple Person instances with it as follows:

const jane = new Person('jane');
const joe = new Person('joe');
const alex = new Person('alex');

In the code above, we created 3 new Person instances, with different values for this.name which we passed in from the name parameter.

We can also add instance methods to a class by writing the following:

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

  greet(greeting) {
    return `${greeting} ${this.name}`;
  }
}

In the code above, we have the greet instance method, which takes a greeting string and combine it with the this.name instance variable.

Then we can call it as follows:

console.log(jane.greet('hi'));
console.log(joe.greet('hi'));
console.log(alex.greet('hi'));

We then get:

hi jane
hi joe
hi alex

logged in the console log output as we called greet to pass in 'hi' as the value of greeting .

As we can see, with JavaScript classes, we can create objects that has the same instance variables and methods in it.

Another way to create objects is by using the Object.create method. This method is useful since we can set the prototype of an object as we’re creating the object.

For instance, if we want to create an object that has another object as its prototype, we can write the following code:

const bar = {
  a: 1
}
const foo = Object.create(bar);
foo.b = 1;
foo.c = 2;

In the code above, we have the bar object, which is used as the prototype of the foo object by using the Object.create method.

Then we added extra properties b and c to the foo object itself. b and c are foo ‘s own, or non-inherited property, and a is the property of the prototype of foo .

The prototype is the template object for the child object. An object inherits property from its prototype.

If we log the foo object, we get that foo‘s __proto__ property has property a with its value set to 1.

If we want to create an object without a prototype, we can call the Object.create method with the argument null so that the __proto__ property won’t be set.

For instance, we can write the following to create the object:

const foo = Object.create(null);
foo.b = 1;
foo.c = 2;

Then if we log the value of foo , we’ll see that it has no __proto__ property, which means that the object has no prototype.

Conclusion

There’re many ways to create an object in a concise manner. We shouldn’t use the Object constructor to create an object.

This is because it provides us with no benefits over defining object literals. They both inherit from Object and using the constructor is just a long way to do the same thing.

Alternative ways to create object including using classes as a template for new objects.

Finally, we can use the Object.create to create an object with the prototype that we want.

Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — Maps

JavaScript is an easy to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust JavaScript code.

In this article, we’ll look at some data structures that we should use in our code to make our code easier to understand.

Use Plain Objects for Dictionaries

If we want to use JavaScript objects for dictionaries and maps, then it’s a good idea to create a plain object that has no prototype before we use them as such.

To do this, we can create a plain object with Object.create method that’s has null as the argument.

For instance, we can create a plain object as follows:

const obj = Object.create(null);
obj['foo'] = 1;
obj['bar'] = 2;

Then when we log:

console.log(obj.__proto__)

Then we see that __proto__ is undefined , so we see that obj doesn’t have any prototype.

Now it’s safe to use any kind of loop or method to loop through the properties like for...in , Object.keys , or Reflect.ownKeys to loop through the keys of the object without accidentally stumbling onto keys of their prototype.

Use Maps to Store Key-Value Pairs With String or Non-String Keys

With ES2015, we have the new Map object. A good feature of the Map object is that we can add keys that aren’t strings in addition to strings.

We can also use the for...of loop to loop through a Map object since it’s an iterable object.

The equality of the keys is determined by the SameValueZero algorithm. With SameValueZero, it’s mostly the same as the === operator, except that +0 isn’t the same as 0 and -0 isn’t the same as 0.

Another good feature of maps is that keys in a Map are ordered. The entries in a Map is returned in the order of their insertion.

It also performs better in scenarios where there are frequent additions and removals of key-value pairs.

Also, it has methods for clearing the Map and the size property for getting the size.

We can also safely check if an entry exists by the key with the has method.

For instance, we can create a map as follows:

const foo = {
  a: 1
};
const bar = {
  b: 1
};
const map = new Map();
map.set(foo, 1);
map.set(bar, 1);

In the code above, we have the foo and bar objects, which we use as keys. This can’t be done without Map s.

Once we defined foo and bar as keys, then we call set to add the entries with foo and bar as keys with some number as values.

Then we can call methods like clear to clear all key-value pairs of the Map object.

delete can with the key be used to remove an entry with the given key. For instance, we can use it as follows:

const foo = {
  a: 1
};
const bar = {
  b: 1
};
const map = new Map();
map.set(foo, 1);
map.set(bar, 1);
map.delete(bar);

Then we won’t see the 2nd entry anymore. We have to reference the objects directly since the SameValueZero compares objects by reference.

Another good feature is that we can use for...of to loop through a Map object. For instance, if we have the following Map :

const map = new Map();
map.set('a', 1);
map.set('b', 2);

Then we can loop through it as follows:

for (const [key, value] of map) {
  console.log(key, value);
}

In the code above, we used the destructuring syntax with the key and value destructured as an array.

We can retrieve the key and value of each entry this way. Therefore, we’ll see:

a 1
b 2

from the console log output.

We can also call the Map instance’s entries method to get the entries the same way:

for (const [key, value] of map.entries()) {
  console.log(key, value);
}

Also, we can get the array of keys with the keys method and the values with the values method as follows:

for (const key of map.keys()) {
  console.log(key);
}

for (const value of map.values()) {
  console.log(value);
}

Conclusion

We can create maps and dictionaries with the Object.create(null) call. Also, since ES2015, we can use the Map object, which can use non-string keys in addition to string and symbol keys.

Items in Map s are returned in the order they’re inserted so that the order is guaranteed. Also, there’re methods to check for values and iterating through them.

Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — Doing Checks

JavaScript is an easy to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust JavaScript code.

In this article, we’ll look at how to do checks in our app to only run things when the given condition is true or false.

Types of Checks We Should Do

If we want our JavaScript program to be robust. Then there’re a few kinds of checks that we must to in our app.

There’re 3 kinds of checks. They’re the following:

  • Existence check — checking if something is available
  • Type check — checking if the data type is correct or if a property exists
  • Value check — checking if something has the value that we’re looking for

Conditional Statements

We can check for values and run code accordingly with conditional statements. There’re a few types of conditional statements in JavaScript. They include if statements, switch statements, and ternary operators.

if statements are the most versatile kind of conditional statements. The code is run if the expression inside the if statement is truthy.

Truthy expressions are the expressions that evaluate to true when they’re coerced to a boolean. Falsy expressions are ones that evaluate to false when they’re coerced to a boolean.

Falsy values in JavaScript are 0, false , '' (empty string), undefined , null , and NaN . Everything else is truthy.

For instance, we can write the following if statement:

if (foo === 1) {  
  //...  
} else if (foo === 2) {  
  //...  
} else {  
  //...  
}

In the code above, we have several checks in our if statement. First, we check if foo is 1 with the === operator and run something if it’s true .

Otherwise, we check if foo is 2, then we run something if that’s true .

In all other cases, as indicated by the else block at the end, we run something if foo isn’t 1 or 2.

When checking for things, we should use the === operator to do the checks. If we want to check if something isn’t equal to something, then we use the !== operator.

This is because they don’t do type coercion before doing the comparison.

Switch Statements

The kind of comparison above can also be done with the switch statement.

For instance, we can rewrite our example with the switch statement as follows:

switch (foo) {  
  case 1: {  
    //...  
    break;  
  }  
  case 2: {  
    //...  
    break;  
  }  
  default: {  
    //...  
    break;  
  }  
}

In the code above, we have switch (foo) , which indicates that we check foo for the given value. In this case, we check for 1 and 2 as indicated by the case blocks. They are used for running code if foo is 1 or 2 respectively.

We enclose the case statements in blocks so that we don’t have to worry about block-scoped variables clashing with other block-scoped variables that have the same name if we want to reuse the name.

It’s also important that we the break; statement at the end of each block so that the code for the other cases won’t run and only the block for the case that matches will run.

The default block is for running code when foo isn’t 1 or 2, or doesn’t match the cases given in the case blocks in general.

This is like the else block that we have in the if statement example.

Ternary Operator

The ternary operator is useful for writing condition checks that only have 2 cases, and that we want to return something for one case and something else for another.

The operator is denoted by the ? , and we distinguish the cases with the : operator.

For instance, we can use it as follows:

const foo = 1;  
const x = (foo === 1 ? 'foo' : 'bar');

In the code above, we have the expression:

(foo === 1 ? 'foo' : 'bar')

which uses the ternary operator. This expression means that if foo is 1, then we return 'foo' . Otherwise, we return 'bar' .

Therefore, since foo is 1, 'foo' is returned, and we assign that to x .

Conclusion

We should do several kinds of checks in our app to make sure that our app runs correctly and only does things when we have the data we’re looking for.

To do the checks, we should use conditional statements like if statements, switch statements, or ternary expressions.

Categories
JavaScript Best Practices

JavaScript Best Practices — Dealing with Numbers

JavaScript is a very forgiving language. It’s easy to write code that runs but has issues in it.

In this article, we’ll look at how we should work with numbers in JavaScript.

Avoid Magic Numbers

Magic numbers are numbers that are used everywhere but their meaning isn’t explicitly specified.

To make them easier to understand and change, we should assign them to a named constant so that we can see what they mean.

Also, we only have to change one place since the constant name is referenced instead of numeric literals.

So instead of writing:

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

We write:

const MAX_DONUTS = 10;
for (let i = 0; i < MAX_DONUTS; i++) {
  //...
}

Use Hard-Coded 0s and 1s if we Need to

0’s and 1’s are OK for hard coding since they’re used for incrementing or setting as an initial number.

We often increment numbers by 1, so we can hard code 1.

0 are used for initial values of variables, so that’s also OK to hard code.

Other numbers should be replaced with something more descriptive.

Anticipate Divide-by-Zero Errors

We shouldn’t expect that the denominator a fraction will never be 0. Therefore, when we divide a number by another number, we should make sure that the divisor is never 0.

Make Type Conversions Obvious

In JavaScript, types may be converted explicitly.

This may cause unexpected results.

To make them obvious, we should convert the variables explicitly.

So, we should use functions like Number , Number.parseInt , or Number.parseFloat to convert non-numbers to numbers.

The + sign before a variable or a non-number can also convert them to numbers.

Also, when we compare things that may be numbers of equality, we should use === so that we’ve to convert the operands to numbers with those functions and operators.

Avoid Mixed-Type Comparisons

Mixed type comparisons often create unexpected results.

Therefore, we should avoid them as much as possible. We should use the === operator to compare for equality.

If we’re checking for inequality, then we should convert both operands to the same type before comparing them.

So we should convert them both to numbers with those methods and operations we looked at before.

Check for Integer Division

We should check that the result of any integer division is what we expect.

We should make sure that division is done last if we have an expression with multiple arithmetic operations.

For instance, instead of writing:

10 * 3 / 10

we write:

(10 * 3) / 10

to avoid anything unexpected.

Check for Integer Overflow

We should check if our number’s absolute value is bigger than Number.MAX_SAFE_INTEGER is we’re working with large integers.

If it is, then we need to use the bigInt type instead.

bigInt is a primitive data type that can let us store integers with magnitude bigger than Number.MAX_SAFE_INTEGER .

We can do arithmetic with other bigInts only and not numbers.

Photo by Austin Distel on Unsplash

Avoid Additions and Subtractions on Numbers that have Greatly Different Magnitudes

The result might not be what we expect if we do additions and subtractions on numbers that have big difference in magnitudes.

For instance, 1000000000.00 + 0.00000000001 returns 1000000000 since there aren’t enough significant digits to include all the digits of the result.

Avoid Equality Comparisons

Equality comparisons don’t work well with numbers.

For instance if we have:

let sum = 0;
for (let i = 0; i < 10; i++) {
  sum += 0.1;
}

We get 0.9999999999999999 as the value of sum instead of 0.1

Therefore, comparing both directly won’t work very well.

We may want to write our function to see if one number is close enough to what we want to be considered equal.

Anticipate Rounding Errors

As e can see, rounding errors happen frequently. If we add a number 10 times, it doesn’t equal to the number we expect, for example.

Conclusion

When we deal with numbers in JavaScript, we should keep a few things in mind.

Rounding errors are a problem that w should be aware of.

When we apply arithmetic operations to numbers, we don’t always get the same thing as we do the same thing on paper.

Therefore, we should be careful when checking for values.

Mixed-type comparisons are also bad. We should convert everything to numbers before comparing them.

Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — Type Checks

JavaScript is an easy to learn programming language. It’s easy to write programs that run and does something. However, it’s hard to account for all the uses cases and write robust JavaScript code.

In this article, we’ll look at how to do type checks properly in JavaScript so that we won’t run into data type errors when we’re running our apps.

typeof Operator

We can use the typeof operator to check for primitive data types and also some object types like functions.

These checks are needed so that we know that we’re working on the right type of data. For instance, we don’t want to use the + operator with strings when we actually want to add numbers together.

To use it, we add typeof before the value that we want to check. Possible values that are returned by the typeof operator are the following:

  • Undefined — "undefined"
  • Null — "object"
  • Boolean — "boolean"
  • Number — "number"
  • BigInt (new in ECMAScript 2020) — "bigint"
  • String — "string"
  • Symbol (new in ECMAScript 2015) — "symbol"
  • Function object (implements [[Call]] in ECMA-262 terms) — "function"
  • Any other object — "object"

These are all the possible type name strings that typeof can return. Most of these are primitive value types, except for 'object' and 'function' .

Note that typeof null returns 'object' . This is a bug, but since it breaks too much existing code for this to be corrected, it’s kept the way it is.

For instance, we can do the following checks with the typeof operator:

typeof 3 === 'number';
typeof 3.1 === 'number';
typeof(40) === 'number';
typeof Math.LN2 === 'number';
typeof Infinity === 'number';
typeof NaN === 'number';

The code above all return true. As we can see from the code above, numbers, Infinity and NaN all return the type 'number' when we use them with the typeof as operands.

An example for a BigInt check is the following:

typeof 41n === 'bigint';

BigInts have an n after the numeric literal to distinguish them from normal numbers. Also, they can only be integers and can only operate on other BigInts in most cases.

To check for strings we can write something like the following:

typeof '' === 'string';
typeof 'bla' === 'string';
typeof `template` === 'string';

This works for both normal and template strings.

To check for booleans, we can write:

typeof true === 'boolean';
typeof false === 'boolean';
typeof Boolean(0) === 'boolean';
typeof !!(1) === 'boolean';

Boolean() and !! do the same thing. They both convert the argument or operand to a boolean. The expressions above all return true .

To check for symbols, we can write:

typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'

The expressions above all return true .

And to check for functions, we can write:

typeof () => {} === 'function';
typeof class C {} === 'function';
typeof Math.sin === 'function';

Note that since classes are just syntactic sugar for constructor functions in JavaScript, classes when used with typeof returns 'function' . The expressions above all return true .

To check if something is an object, we can write something like:

typeof {a: 1} === 'object';
typeof [1, 2, 3] === 'object';
typeof new Date() === 'object';

The expressions above all return true .

We should use wrapper objects for primitive values, so the following are useless and confusing:

typeof new Boolean(true) === 'object';
typeof new Number(1) === 'object';
typeof new String('abc') === 'object';

All 3 expressions are true because they all have types 'object' . This isn’t a problem with object literals since they return the types we expect.

However, these don’t and they don’t do anything better than the literals, so we should avoid using constructors for defining primitive values.

The typeof operator doesn’t work with undeclared variables since ES2015.

For instance, if foo isn’t declared, then in ES2015 or later, then typeof foo will throw an error in our code.

Whereas before ES2015, typeof foo returns undefined if the variable is undeclared.

With block-scoped variables or constants, we can only do operations on them when the variable is declared. For instance, the following:

typeof foo === 'undefined';
let foo;

will give us a ReferenceError.

Therefore, we should be careful when using the typeof operator so we don’t cause more crashes during our checks.

Conclusion

The typeof operator lets us type checks mainly on primitive values. However, we can also use it to check if a function or class is a function.

Other kinds of objects will return 'object' with the typeof operator so it isn’t that useful.