Categories
JavaScript Best Practices

JavaScript Best Practices — Property Names and Semicolons

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 add properties to objects and adding semicolons to our JavaScript code.

Quotes Around Object Literal Property Names

We should add quotes around object literal property names only when the property isn’t valid as property name if it’s not written as a string.

For instance, we don’t put quotes around the property of the following object because the property name is a valid identifier.

If we have the following object:

const foo = {
  a: 1
};

Then we don’t need quotes around a since a is already a valid identifier name in JavaScript.

In JavaScript, identifiers are case-sensitive and can contain Unicode letters, $ , _ and digits. However, a valid JavaScript identifier may not start with a digit.

If we want to name a property with an identifier that breaks any of the rules outlined above, then we need to put them around quotes so that they’re written as strings.

For instance, if we want to have a property name that has a space between 2 words, we can write the following code:

const foo = {
  'foo bar': 1
};

In the code, we have the foo object, which has the property 'foo bar' , which has to be in quotes because of the space between foo and bar .

Then we can only access it by using the bracket notation as follows:

foo['foo bar']

to get the value of the property.

Therefore, if it’s a valid identifier, then we don’t need quotes around the property name. Otherwise, we do, and we can only access it with the bracket notation.

Consistent Use of Backticks, Double, or Single Quotes in Strings

Out of all 3 string delimiters, the backtick is the most versatile since we can use it to create both template strings and regular strings.

Template strings let us interpolate expressions into out string instead of having to concatenate expressions with multiple substrings

Interpolation is just much easier to read and type than string concatenation. The amount of the code we’ve to write is much less since we don’t need the concatenation operator everywhere.

For instance, we instead of writing:

const baz = 1;
const foo = 'foo ' + baz + ' bar';

We can instead write the string above as a template string as follows:

const baz = 1;
const foo = `foo ${baz} bar`;

In the code above, we put in the expression baz into the template string instead of concatenating.

Reading and typing template strings are both easier.

With template strings, we don’t always have to interpolate expressions, we can just create a regular string like:

const foo = `foo bar`;

Also, we can use both single and double quotes within our string without having to escape it with a backslash.

For instance, we can write the following code to use single and double quotes together as a character in a string rather than a string delimiter:

const foo = `'foo' "bar"`;

This is another advantage of using backticks as string delimiter since single and double quotes are used more in sentences and clauses. However, backtick isn’t a commonly used English punctuation symbol.

Therefore, backticks are great as a string delimiter. It’s even better since we use backticks to create JavaScript template strings which can have JavaScript expressions interpolated in it.

Single and double quotes are good for quoting text.

Photo by Lukáš Vaňátko on Unsplash

Adding Our Own Semicolons is Better than Automatic Semicolon Insertion (ASI)

When we write JavaScript code, we should always write out the semicolons explicitly instead of relying on the automatic semicolon insertion capability to insert them for us automatically.

ASI doesn’t always insert them in the place that we want it to. For instance, if we have the following code:

const foo = () => {
  return
  {
    name: "foo"
  }
}

Then it’s going to be interpreted as:

const foo = () => {
  return;
  {
    name: "foo"
  };
}

Therefore when we call foo , it’s going to return undefined .

We probably don’t want that, so we should put in the semicolons ourselves as follows:

const foo = () => {
  return {
    name: "foo"
  };
}

Conclusion

We only need quotes around property names for property names that aren’t valid JavaScript identifier names.

Backticks is the best string delimiter since it can create template strings and regular string and leave single and double quotes as quote delimiters instead of string delimiters.

Categories
JavaScript Best Practices

JavaScript Best Practices— Padding, Exponentiation, and Spread

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 padding between statements, exponentiation, and spread.

Padding Lines Between Statements

We don’t need an empty line between all statements. We just need blank lines between groups of unrelated statements.

For instance, one way to group statements in a function is to write something like the following code:

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

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

In the code above, we have a blank line between the 2 for loops because each loop is its own logical group.

We just need a blank line between groups of statements that we want to group together. The blank line tells us that they should be read as a group.

Otherwise, a blank line is a waste of space and we can remove them to save vertical space.

Use of Math.pow Versus the ** Operator

Math.pow is a method that lets us do exponentiation in all versions of JavaScript since the first version up to the present version.

It takes 2 arguments, which are the base and the exponent and returns the base raised to the given exponent.

For instance, we can use it as follows:

const a = Math.pow(2, 3);

Then we get that a is 8 since 2 is raised to the power of 3.

It also works with fractional and negative powers. For instance, we can write:

const a = Math.pow(2, 0.5);

and get that a is 1.4142135623730951. We can also write:

const a = Math.pow(2, -1);

and get that a is 0.5.

In addition, we can put expressions in place of numbers as follows:

const x = 1,
  y = 2,
  z = 3;
const a = Math.pow(x + y, z);

Then we get that a is 27 because x is 1, y is 2 and c is 3 so we’re raising the base 3 to the power of 3.

With ES2015, the exponential operator is introduced. It’s denoted by ** .

We can use it to do exponentiation as follows:

const a = 2 ** 3;

and get that a is 8.

Fractional and negative powers and exponents all work as we expected with the ** operator. For instance, we can write to them as follows:

const a = 2 ** 0.5;
const b = 2 ** -1;

For expressions, we can write:

const x = 1,
  y = 2,
  z = 3;
const a = (x + y) ** z;

As we can see, using the exponentiation operator is shorter and we get the same result and it’s also more readable than calling a method that we don’t need to call.

We saved lots of typing and do the same thing, so we should use the exponentiation operator instead of calling Math.pow for doing exponentiation.

Use Object Spread Operator Over Calling Object.assign

The spread operator works on objects since ES2018. It lets us do a shallow copy of objects or merged multiple objects into one new object.

Between ES6 and ES2017, we’ve to use the Object.assign to merge multiple objects into one or make a shallow copy of it.

With Object.assign , we make a shallow copy of an object by writing the following code:

const foo = {
  a: 1
};
const bar = Object.assign({}, foo);

In the code above, we defined the foo object. Then we called Object.assign with an empty object as the first argument and the foo object as the 2nd argument to return a shallow copy of the foo object.

A shallow copy is where only the top-level properties are copied. The nested objects still reference the original object.

If we log the expressions foo === bar , it returns false , which means that foo and bar aren’t referencing the same object in memory. Therefore, we know that we made a shallow copy of the object.

The first argument of Object.assign is the target object to copy to and the rest of arguments, which can be as many as we want, are the source objects that we want to copy into the target object.

To merge multiple objects together with Object.assign , we just pass in more objects into it as arguments.

For instance, we can write the following code:

const foo = {
  a: 1
};
const baz = {
  b: 2
};
const qux = {
  c: 3
};
const bar = Object.assign({}, foo, baz, qux);

Then we get that bar is {a: 1, b: 2, c: 3} .

The spread operator makes this simpler. With it, we don’t have to call a function to merge and make shallow copies of objects. All we have to do is to use the spread operator, which is denoted by ... as follows:

const foo = {
  a: 1
};
const baz = {
  b: 2
};
const qux = {
  c: 3
};
const bar = {
  ...foo,
  ...baz,
  ...qux
};

The code above also merges all the objects into one as we did with Object.assign above.

And so we get that bar is {a: 1, b: 2, c: 3} .

We can make a shallow copy of an object as follows:

const foo = {
  a: 1
};
const bar = {
  ...foo,
};

and we get that bar is { a: 1 } but foo and bar aren’t equal when compared with the === operator as they don’t reference the same object.

Conclusion

The spread and exponentiation operator should be used instead of their older counterparts.

We don’t need a line after each statement. We need a new blank line after a group of statements that we want to group together.

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.