Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code — DOM Manipulation

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 the most reliable ways to manipulate the DOM.

Use Newer DOM Manipulating Methods

The document object has the querySelector and querySelectorAll methods, which are more versatile than the old getElementById and getElementsByTagName methods.

querySelector returns the first element that has the given CSS selector.

querySelectorAll returns a NodeList of all the elements that have the given CSS selector.

For instance, given the following HTML code:

<p>
  foo
</p>
<p>
  bar
</p>
<p>
  baz
</p>

Then we can get the first p element with the document.querySelector method as follows:

const p = document.querySelector('p');

If we want to get all the p elements, we can write the following code:

const p = document.querySelectorAll('p');

NodeLists are array-like iterable objects, so we should convert them to an array with the spread operator so that we can manipulate the list more easily:

const ps = document.querySelectorAll('p');
const arr = [...ps];

Once we converted the NodeList to an array, we can use any array methods with it that we wish to.

For instance, we can get the p element with the content ‘bar’ as follows:

const ps = document.querySelectorAll('p');
const arr = [...ps];
const bar = arr.find(a => a.innerText === 'bar');

Now bar has the p element with the innerText value set to 'bar' .

If we use querySelector , then if the given element isn’t found, it’ll return null . Therefore, we should make sure that we check for that before doing anything.

For instance, if we have the HTML above, and we have the following code:

const div = document.querySelector('div');

Then div will be null .

Therefore, we should make sure to check for null to prevent possible crashes arising from attempting to manipulate null values.

Displaying Things with InnerHTML

The innerHTML property of an element object lets us set the HTML content of an element. It lets us add child elements to the DOM without using createElement or appendChild as the browser will automatically put the items into the DOM tree.

For instance, given the following HTML:

<p>
  foo
</p>

Then we can add bold text with the b tag as follows:

const p = document.querySelector('p');
p.innerHTML = '<b>foo</b>';

Then we’ll see that text ‘foo’ is bold.

Checking for DOM Elements with the hasAttribute Method

DOM elements has the hasAttribute method to check if an element has an attribute. This is useful for checking if an element has the given attribute before doing anything.

For instance, we can write the following code to check if an attribute exists in a given element:

<p class='foo'>
  foo
</p>

Then we can check if the class attribute is in the p element by writing:

const p = document.querySelector('p');
const hasClass = p.hasAttribute('class');

Since the class attribute exists in the p tag, then hasClass should be true . If the attribute doesn’t exist in the element, then hasAttribute returns false .

Photo by Ulises Baga on Unsplash

Getting All Attributes of an Element with the DOM Node Object’s attributes Property

A DOM element has an attributes property that returns an array-like iterable object that has all the properties of the given element in the object.

For instance, if we have the following HTML:

<p class='foo'>
  foo
</p>

Then we can get the attributes of the p tag as follows:

const p = document.querySelector('p');
const attributes = [...p.attributes];

Then attributes is an array with the attributes as entries. Notice that we used the spread operator to convert the array-like object to an array.

Each entry has properties for the name and value of the attribute. It also has properties like the child elements and text content for the attribute values.

Conclusion

The best way to get one or more elements is to use the document.querySelector or document.querySelectorAll methods respectively.

If we use querySelectorAll to get all elements with the given selector, then we should convert the returned NodeList to an array with the spread operator so that manipulation of the items will be easier.

If we use the querySelector method, then it returns null if no entries with the given selector are found, so we should check for that.

To check if an attribute exists in a DOM element, then we should get a DOM element with the hasAttribute method. It returns a boolean indicating if the attribute exists in a DOM element.

To get all the attributes of an element, we can use the attributes property of an element object. It returns an array-like iterable object, so we should again use the spread operator to spread it into an array.

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.