Categories
JavaScript Mistakes

JavaScript Mistakes — Spaces and Useless Code

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

In this article, we look at how to eliminate useless characters that may break code.

Irregular Whitespace Characters

There’re whitespace characters that aren’t considered whitespaces in all places. Therefore, we may get Unexpected token errors.

Also, it’s not shown in modern browsers, which makes code hard to visualize properly.

Line separators are also invalid characters within JSON which cause more parse errors.

The full list of irregular characters is here.

Characters with Multiple Code Points in Character Class Syntax

Characters with multiple code points, like U+2747 aren’t allowed in strings. We should use characters which are composed of single code points like regular alphanumeric characters and punctuation symbols in our strings.

Calling Global Object Properties as Functions

Some global object properties like Math , JSON , and Reflect shouldn’t be called functions since only their methods are meant to be called and properties are meant to be accessed.

For instance, the following are invalid:

let math = Math();
let json = JSON();

The correct way to use the objects above are to access their properties:

let pi = `Math.PI;
let obj = JSON.parse('{}');`

Using Object.prototypes Builtins Directly

Some methods from the Object.prototype like hasOwnProperty , isPrototypeOf , and propertyIsEnumerable aren’t meant to be called directly by the instance.

For instance, the following are incorrect:

let bar = {};
let foo = {};
const hasBarProperty = foo.hasOwnProperty("bar");
const isPrototypeOfBar = foo.isPrototypeOf(bar);
const barIsEnumerable = foo.propertyIsEnumerable("bar");

They’re meant to be called as follows:

let bar = {};
let foo = {};
const hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar");
const isPrototypeOfBar = Object.prototype.isPrototypeOf.call(foo, bar);
const barIsEnumerable = Object.prototype.propertyIsEnumerable(foo, "bar");

Extra Whitespaces in Regular Expressions Literals

Multiple whitespaces are hard to read. It’s hard to tell how many whitespaces are inside the regex. Therefore, we should use single whitespace and then specify the number of whitespaces we’re intending to match.

For instance, instead of writing:

const re = /a   b/;

We should write:

const re = /a {3}b/;

Returning Values from Setters

Setters’ return value is ignored even if it’s there. Therefore, the return value is useless inside the setter. It also creates more chances for error.

This applies to object literals, class declarations and expressions, and properties descriptors in Object.create , Objecr.defineProperty , Object.defineProperties and Reflect.defineProperty.

For instance, we should return values in the following code:

class Person {
  set firstName(value) {
    this._firstName = value;
    return value;
  }
}

or:

const person = {
  set firstName(value) {
    this._firstName = value;
    return value;
  }
}

or:

const Person = class {
  set firstName(value) {
    this._firstName = value;
    return value;
  }
}

or:

let person = {};
Object.defineProperty(person, "firstName", {
  set(value) {
    this._firstName = value;
    return false;
  }
});

Instead, we should remove the return statements from the examples above as follows:

class Person {
  set firstName(value) {
    this._firstName = value;
  }
}

or:

const person = {
  set firstName(value) {
    this._firstName = value;
  }
}

or:

const Person = class {
  set firstName(value) {
    this._firstName = value;
  }
}

or:

let person = {};
Object.defineProperty(person, "firstName", {
  set(value) {
    this._firstName = value;
    return false;
  }
});

Sparse Arrays

Empty slots in arrays are allowed in JavaScript. Array literals with only commas inside are valid. However, they may be confusing to developers.

For instance, the following arrays may be confusing to developers:

const items = [, ];
const strs = ["foo", , "bars"];

Instead, we should write the following:

const items = [];
const strs = ["foo", "bars", ];

We can have trailing commas.

Template Literal Placeholder Syntax in Regular Strings

Regular strings shouldn’t have template literal placeholders since they don’t work in regular strings and it’s easy for developers to mistaken them for template strings.

For instance, the following are probably mistakes:

"Hello ${name}!";
'Hello ${name}!';
"Sum: ${1 + 2}";

Instead, we should write:

`Hello ${name}!`;
`Sum: ${1 + 2}`;

Conclusion

There’re lots of characters that shouldn’t be in JavaScript code even though some may be allowed. Irregular whitespace characters shouldn’t be present in code. Characters made up of multiple code points shouldn’t in JavaScript strings.

Global objects shouldn’t be called directly. Only their properties are meant to be accessed.

Excess spaces in regex strings and literals, so they shouldn’t be included. Also, returning values in setters are useless, so they shouldn’t be added.

Multiple consecutive commas are allowed in JavaScript array literals, but they’re confusing to developers, so they probably should be avoided.

Categories
JavaScript Mistakes

JavaScript Mistakes — Async, Type Checking, and Values

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 how to avoid race conditions when running multiple promises and more.

Assignments that can Lead to Race Conditions Because of yield and await

When using promises, it’s easy to create race conditions when we aren’t combining multiple promises properly.

For instance, if we have:

let total = 0;

const getNum = async (num) => {
  return num;
}

const add = async (num) => {
  total += await getNum(num);
}

Promise.all([add(1), add(2)]).then(() => {
  console.log(total);
});

Then we’re calling the add async function, which gets the result from the getNum promise and add it to the total .

Then we call Promise.all , which runs the code in parallel. In that time, the first value assigned to total will be overwritten because total is still set to the initial value in each promise before they were resolved.

Therefore, we’ll see 2 instead of 3 as we expect.

We can fix this mistake by assigning the resolved value in add to a variable before adding them to the total as follows:

const add = async (num) => {
  const res = await getNum(num);
  total += res;
}

Or we can just wait for both to resolve and then add the results together:

const getNum = async (num) => {
  return num;
}

const add = async (num) => {
  return await getNum(num);
}

Promise.all([add(1), add(2)]).then((nums) => {
  const total = nums.reduce((a, b) => a + b, 0);
  console.log(total);
});

Not Using isNaN to Check for NaN

NaN is a special value in JavaScript. It doesn’t equal itself when compared with == or === . For instance, the following:

NaN === NaN
NaN == NaN

returns false and:

NaN !== NaN
NaN != NaN

returns true .

NaN check also doesn’t work with indexOf and lastIndexOf :

const arr = [NaN];
console.log(arr.indexOf(NaN));
console.log(arr.`lastIndexOf`(NaN));

We’ll get -1 returned from the console log in the code above.

Therefore, we should check for NaN with the isNaN function as follows:

const foo = NaN;
if (isNaN(foo)){
  console.log('is NaN');
}

We can also use the Object.is method to check for NaN as follows:

const foo = NaN;
if (Object.is(foo, NaN)) {
  console.log('is NaN');
}

To check if NaN is in an array, we can write our own function to do it:

const arr = [NaN];
console.log(arr.some(a => isNaN(a)));

Compare typeof Expressions Against Invalid Strings

It’s easy to make typos when writing strings. Therefore, we should check if the types that we check with typeof are correct.

For instance, the following code has typos in the string that’ll cause typeof to not work as we expect them to:

typeof foo === "strnig"
typeof foo == "undefimed"
typeof bar === "nunber"
typeof bar === "fucntion"

We should make sure that the strings that we compare against are spelled correctly as follows:

typeof foo === "string"
typeof foo == "undefined"
typeof bar === "number"
typeof bar === "function"

Getters and Setters should be Together in Object and Classes

A setter isn’t very useful without its corresponding getter. For instance, if we have:

const obj = {
  set foo(val) {
    this._foo = val
  }
}

Then there’s no way to get the value of foo , and we’ll get undefined when we reference obj.foo even after we set the value.

Therefore, we should add a corresponding getter to the object to return a value. For instance, we should instead write:

const obj = {
  get foo() {
    return this._foo;
  },
  set foo(val) {
    this._foo = val
  }
}

To define a new property with defineProperty with getters and setters, we can write the following:

const obj = {};
Object.defineProperty(obj, 'foo', {
  get() {
    return this._foo;
  },
  set(val) {
    this._foo = val
  }
});

Then if we assign a value to foo , we can get the value of the foo property. Likewise, we can do the same for classes as follows:

class Obj {
  get foo() {
    return this._foo;
  }

  set foo(val) {
    this._foo = val;
  }
}

const obj = new Obj();
obj.foo = 1;

This works the same as the object example, except that we’ve to instantiate it with new first.

Conclusion

We shouldn’t do assignment with operations like += with await or yield as the right operand as the value will be overwritten instead of combined with the existing value.

Also, we should use isNaN to check for NaN instead of checking with the === or == operator.

When using typeof , we should check for typos in the strings that we check for the types against.

Setters without getters also isn’t very useful since we can’t get the value that we set.

Categories
JavaScript Mistakes

More JavaScript Mistakes — Objects, Async Code and Event Listeners

JavaScript is a language that’s friendlier than many other programming languages in the world. However, it’s still very easy to make mistakes when writing JavaScript code through misunderstanding or overlooking stuff that we already know. By avoiding some of the mistakes below, we can make our lives easier by preventing bugs and typos in our code that bog us down with unexpected results. In this article, we’ll look mistaking object keys as array indexes, not understanding asynchronous code, misusing event listeners.

Confusing Object Keys and Array Indexes

In JavaScript, the brackets can be used to accept an object which can take a string containing a property name and then get the value from it. For example, if we have the following object:

const obj = {
  foo: 1,
  bar: 2
}

Then we can access the value of the foo property by writing either:

obj.foo

or we can use the bracket notation and write:

obj['foo']

The bracket notation is also used to access an array entry by its index. For example, if we have:

const arr = [1, 2, 3];

Then we write:

arr[0]

to get the first entry of the arr array.

We have to be careful in that we don’t confuse normal objects and arrays since the values of them can be accessed with the bracket notation, and the JavaScript interpreter doesn’t distinguish between the 2. This means that code like:

arr['foo']

are still allowed in JavaScript. It doesn’t stop us from commit the mistake of accessing an array-like we do with a regular object. If we log the value of arr[‘foo’] we just get undefined .

If we don’t know whether a variable is a regular object, an array or something, else, then we can use the Array.isArray method which checks if a value that we pass in if an array. It takes one argument which is any objects that we want to check to see whether if it’s an array.

By using the Array.isArray method, we’ll be sure that we’re accessing an array if Array.isArray returns true. For example, we can use it as the following code:

const arr = [1, 2, 3]

if (Array.isArray(arr)) {
  console.log(arr[0]);
}

This way, we’re sure that arr is an array before trying to access an array entry.

Not Understanding Asynchronous Code

Because of the single thread nature of JavaScript, lots of code is going to be asynchronous since running code line by line all the time is going to hang the computer if it’s waiting for something that takes some time such as a response from an HTTP request.

With asynchronous code, we often have run code that’s in a callback function, which runs in an indeterminate amount of time. They can’t be treated like synchronous code that runs line by line.

For example, if we want to get some data from a GET request, we often have to use an HTTP client that makes the request asynchronously and the response is obtained in an indeterminate amount of time.

If we use the Fetch API built into most modern browsers, we’ll get the response data asynchronously. For example, we’ll have code that looks something like the following:

fetch('[https://jsonplaceholder.typicode.com/todos/1'](https://jsonplaceholder.typicode.com/todos/1%27))
  .then((response) => {
    return response.json()
  })
  .then((responseBody) => {
    console.log(responseBody);
  })

As we can see we have multiple callback functions that do various things. If we write something like:

const response = fetch('[https://jsonplaceholder.typicode.com/todos/1'](https://jsonplaceholder.typicode.com/todos/1%27))

const responseBody = response.json()
console.log(responseBody);

It’s not going to work since the correct code doesn’t run code line by line. In the correct code, the fetch function is called, which returns a promise, then inside the callback function in the first then method, we return the result of theresponse.json() object, which returns another promise which resolves to the JSON body of the response. Then in the callback function of the second then method, we log the responseBody which is obtained by resolving the promise returned by response.json() call.

Promises may be run sequentially by chaining them, but they don’t run instantaneously and so they don’t run like synchronous code.

A shorthand that looks more like synchonous code would be using the async and await keyword introduced with ES2017. We can write:

fetch('[https://jsonplaceholder.typicode.com/todos/1'](https://jsonplaceholder.typicode.com/todos/1%27))
  .then((response) => {
    return response.json()
  })
  .then((responseBody) => {
    console.log(responseBody);
  })

to:

(async () => {
   const response = await fetch('[https://jsonplaceholder.typicode.com/todos/1'](https://jsonplaceholder.typicode.com/todos/1%27));
   const responseBody = await response.json();
   console.log(responseBody);
 })();

An async function looks like it’s running line by line, but it’s just a shorthand for the then method calls with callbacks passed inside. We can’t return anything other than a promise inside an async function.

Adding Too Many Event Listeners

If we have the following HTML code:

<input type="checkbox" />

<button>Click</button>

and the corresponding JavaScript code:

const checkbox = document.querySelector('input[type=checkbox]');
const button = document.querySelector('button');

checkbox.addEventListener('change', () => {
  if (checkbox.checked) {
    button.addEventListener('click', () => {
      alert('Alert');
    });
  }
});

Then we’ll see that if we toggle the checkbox on and off a few times and then click the button, we’ll see the ‘Alert’ pop up multiple times. This is because we used the addEventListener method on the button object, which represents the button element that we have in the HTML. The addEventListener method attaches a new click event listener each time the checkbox is checked. This means that we have multiple event listeners listening to the click event of the button. When the click event is finally fired by clicking the button, all the click event listener functions that we attached to the button all run, causing the ‘Alert’ pop up to appear multiple times.

This means we attached too many event listeners to the button.

The right way to do this is to set the onclick property of the button with the same event listener function instead. Likewise, we can also do the same thing for the checkbox object even though we don’t have the same problem for consistency. We can instead write:

const checkbox = document.querySelector('input[type=checkbox]');
const button = document.querySelector('button');

checkbox.onchange = () => {
  if (checkbox.checked) {
    button.onclick = () => {
      alert('Alert');
    };
  }
};

This way, we keep overwriting the onclick event listener assigned to the button.onclick property instead of keep attach new click event listeners to it.

In JavaScript, the brackets can be used to accept an object which can take a string containing a property name and then get the value from it. With arrays, we can use the bracket notation to access an array element by its index. There’s no way to distinguish whether an object with an array just by looking at the code. Therefore, we should check whether an object is actually an array before trying to access items by its index.

Lots of code in JavaScript are asynchronous since JavaScript is single-threaded, so having all the code run line by line will stall the browser. This means that we have to use asynchronous code to avoid stalling the computer by letting code that doesn’t return a result immediately wait in the background. We have to be aware that code are in callback functions of asynchronous functions are there because they can’t live outside the callback function.

Finally, we shouldn’t use addEventListener too often since it keeps adding new event listeners to a DOM object without discarding the old ones. Instead, we can use the set the event listener function to the property of the DOM object which listens to the event we want to listen to. For example, if we want to listen to the click event, then we can set the onclick property of a DOM object with the event handler function that we defined.

Categories
JavaScript Mistakes

JavaScript Mistakes — null vs undefined, Constructors, and Scopes

JavaScript is a language that’s friendlier than many other programming languages in the world. However, it’s still very easy to make mistakes when writing JavaScript code through misunderstanding or overlooking stuff that we already know. By avoiding some of the mistakes below, we can make our lives easier by preventing bugs and typos in our code that bog us down with unexpected results. In this article, we’ll look at the confusion between null and undefined, scope issues with asynchronous code, and scopes and using object literals vs. constructors for built-in objects.

Confusing Null and Undefined

In JavaScript, both null and undefined mean no value for a variable. They’re pretty similar but there are some important differences. One if that null is of type object, so when we use the typeof operator on null as we do below:

typeof null

We get 'object'.

On the other hand, if we use the typeof operator on undefined, as we do below:

typeof undefined

We get 'undefined'.

If we compare them with the === operator, as we do below:

undefined === null

We get false since they’re of 2 different types. However, when we compare them with the == operator:

undefined == null

Then we get true since they’re both falsy. Since they’re 2 different types, they shouldn’t be confused with each other.

One thing to note is that for function default parameters, they will only be assigned if the value is undefined and not null.

const hello = (name = 'World') => console.log(`Hello, ${name}!`)
hello(null) // Hello, null!
hello(undefined) // Hello, World!

Asynchronous Code and Scope

One confusing aspect of JavaScript is that the visibility of a variable remains in the parent scope. This means that when a loop runs with a loop variable that we want to access in the callback of the setTimeout function, the index variable would have already been incremented to meet the end condition before the callback in setTimeout is called. This means that we’ll only get the final value of the loop variable after the loop is finished, which gets passed into the callback of the setTimeout function.

For example, if we have the following code:

for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  }, 100);
}

We get 10 logged 10 times. This is because the code in the setTimeout ‘s callback function runs after the loop is finished. i would have been updated to 10 by then. If we want to keep the value of the loop variable in each setTimeout callback function call. Then we have to pass the value of i into the callback function by wrapping the setTimeout inside a function.

If we want to see 0 to 9 logged instead, then we’ve to write the following:

for (var i = 0; i < 10; i++) {
  ((i) => {
    setTimeout(() => {
      console.log(i);
    }, 100);
  })(i)
}

The code above wraps the setTimeout call inside the anonymous function, which we call every time the loop runs, and we pass in the value of i in each iteration into the function. Then the value of i will be passed into the console.log rather than the value of i after the loop is done.

Photo by Francesco De Tommaso on Unsplash

Using Constructors over Literals

In JavaScript, constructors aren’t always useful for creating new instances of objects. This is especially true for built-in objects like arrays, numbers, and strings. We construct new instances of objects with the new operator like other languages such as C# or Java. However, it doesn’t always do what we expect.

For example, we can create a new Array object with the new operator. For example, we can write:

let arr = new Array('a', 'b', 'c', 'd');

Then we get [“a”, “b”, “c”, “d”] like we expect. However, when we only pass in one argument to the Array constructor, as we do in the code below:

let arr1 = new Array(23);

Then we get an empty array with length property set to 23.

It turns out that there’re 2 Array constructors. One takes one argument, which is the length of the array, and the other takes an infinite list of objects separated by commas, which are the list of elements for the new array.

However, if the one and only argument isn’t a number, then it does create an array with the argument as the only entry of the array. For example, if we write:

let arr = new Array('a');

We get [“a”] .

If the only argument is a negative number, it also fails to create a new array. We get the error message Uncaught RangeError: Invalid array length .

Using the Array constructor is confusing because there’re 2 constructors that take different kinds of arguments.

For numbers and strings, we have different problems when we use constructors to create these objects. When we use the new operator to create a number or string object, we get problems when we compare them with the === operator. For example, if we try to compare 2 strings with the same content where one is a string literal and the other is created with the constructor, as we have in the following code:

'abc' === new String('abc');

We get false even though their content are exactly the same. This is because the string literal 'abc' is of type string, but the object created with new String(‘abc’) is of type object. Again, this is confusing.

We have to call the valueOf method on the string object to make it a string type object.

Likewise, if we have:

1 === new Number(1);

We also get false for the same reason. 1 is of type number while, new Number(1) is of type object. Again, we have to call the valueOf method to convert new Number(1) back to a number-type object.

The solution to all these problems is solved by using the literal notation whenever it’s available. JavaScript doesn’t need to know the length of the array beforehand since it just assigns undefined to whatever position that doesn’t have a value assigned to it.

Conclusion

In JavaScript, both null and undefined means no value for a variable that’s been assigned these values. They’re pretty similar but there’re some important. One if that null is of type object , and undefined is of type undefined . This means that null === undefined will be evaluated to false .

Whenever we have asynchronous code and we want to pass in some variable’s value into a callback that’s called by an asynchronous function like setTimeout , we have to pass in the outside variable into the callback function wrapping the asynchronous code with a function and then pass in the variable from the outside to the callback.

Finally, we should avoid using constructors for creating built-in objects like arrays, numbers, and strings. For numbers and strings, the type will be different from the literal type which will have the actual primitive types of number or string. For arrays, it’s because there’re 2 constructors which take different arguments and we’ll get unexpected results depending on the numbers of arguments we pass in.

Categories
JavaScript Mistakes

Common JavaScript Mistakes — Part 2

JavaScript is a language that’s friendlier than many other programming languages in the world. However, it’s still very easy to make mistakes when writing JavaScript code through misunderstanding or overlooking stuff that we already know. By avoiding some of the mistakes below, we can make our lives easier by preventing bugs and typos in our code that bog us down with unexpected results.


Confusing Undefined and Null

JavaScript has both undefined and null for non-values. However, there are quite a few differences between the two. undefined means that the variable may have been declared, but nothing is set to it. A variable can also be explicitly set as undefined . The type of an undefined variable, when checking the type with the typeof operator, will get us the type 'undefined'. Functions that don’t return anything returns undefined. On the other hand, null values have to be explicitly set by functions that return null or just set directly to a variable. When we check an object that has the null value set, we’ll find that the type of it is'object' if a variable has the null value.

For this reason, it’s probably easier to stick to undefined whenever we can when we’re setting variable values to non-values. It reduces confusion and we only have to check that the type of a variable is 'undefined' to see whether it’s undefined. That’s less painful than having two checks, for both null and undefined.

To write functions that return undefined, we don’t have to do anything like the following example:

const f = () => {}

To set a variable that was assigned some other value to undefined, we can write:

x = undefined;

To check if a property value is undefine, we can write:

typeof obj.prop === 'undefined'

or

obj.prop === undefined

To check if a variable is undefined, we can write the following code:

typeof x === 'undefined'

A declared variable that hasn’t been assigned anything automatically has the value undefined.

If we have to check for null, then we can write:

obj.prop === null

or

x === null

for variables. We can’t use the typeof operator for checking null because the data type of null is 'object'.

Photo by Mikhail Vasilyev on Unsplash


Confusing Addition and Concatenation

In JavaScript, the + operator is used for both adding two numbers and concatenating strings together. Because JavaScript is a dynamic language, the operands are all automatically converted to the same type before the operation is applied. For example, if we have:

let x = 1 + 1;

then we get two because they’re both numbers. The + operation was used for addition like we expected. However, if we have the following expression:

let x = 1 + '1';

then we get '11' because the first operand was coerced into a string before the + operation is applied. The + operation was used for concatenation instead of addition. When we use the + operator on multiple variables, this makes knowing the type even harder. For example, if we have:

let x = 1;
let y = 2;
let z = x + y;

as we expect, we get three because x and y are both numbers. On the other hand, if we have:

let x = 1;
let y = '2';
let z = x + y;

then we get '12' because y is a string, so the + operator was used for concatenation instead. To solve this issue, we should convert all the operands to numbers first before using them with the + operator. For example, we should rewrite the above example into the following:

let x = 1;
let y = '2';
let z = Number(x) + Number(y);

The code above will get us 3 as the value of z since we converted them both to numbers with the Number factory function first. The Number function takes in any object and returns a number if it can be parsed into a number, or NaN otherwise. An alternative way to do this is to use the new Number(...).valueOf() function, as we do in the following code:

let x = 1;
let y = '2';
let z = new Number(x).valueOf() + new Number(y).valueOf();

Since new Number(...) is a constructor that creates an object type, we want to use the valueOf function to convert it back to a primitive type to make sure that what we get is a number type. A shorter way to do this is to write:

let x = 1;
let y = '2';
let z = +x + +y;

The + sign in front of a single operand will try to convert the single operand into a number or toNaN if it can’t be converted into a number. It does the same thing as the Number function. We can also convert a variable to a particular type of number. The Number object has a parseInt function to convert a string or object into an integer and a parseFloat function to convert a string or object into a floating-point number. parseInt takes the object you want to convert to a number as the first argument. It also takes a radix as an optional second argument, which is the base of the mathematical numeral systems. If the string starts with 0x, then the radix will be set to 16. If the string starts with anything else, then the radix will be set to 10.

We can use them as in the following examples:

let x = 1;
let y = '2';
let z = Number.parseInt(x) + Number.parseInt(y)

Also, we can use the parseFloat function as in the following code:

let x = 1;
let y = '2';
let z = Number.`parseFloat`(x) + Number.`parseFloat`(y)

We will get 3 in both of the examples above.


Breaking Return Statements Into Multiple Lines

JavaScript closes a statement at the end, so one line code is considered distinct from the other. For example, if we have:

const add = (a, b) => {
  return
  a + b;
}

we get undefined if we run console.log(add(1, 2)); since we ran the return statement, which ended the function execution, before a + b is run. Therefore, a + b will never be run in this function. To fix this, we either have to put the return statement all in one line or use parentheses to surround what we want to return. For example, we can write:

const add = (a, b) => {
  return a + b;
}

This will log 3 if we run console.log(add(1, 2)); since we are actually returning the computed result in the function. We can also write:

const add = (a, b) => {
  return (
    a + b
  );
}

This is handy for returning expressions that might be longer than one line. This will also log 3 if we run console.log(add(1, 2));. For arrow functions, we can also write:

const add = (a, b) => a + b

for single-line functions since the return statement is implicit for single-line arrow functions. We can also write:

const add = (a, b) => (a + b)

to get the same thing. This also works for single-line arrow functions.

In JavaScript, if a statement is incomplete, like the first line of:

const power = (a) => {
  const
    power = 10;
  return a ** 10;
}

inside the function then the JavaScript interpreter will run the first line together with the second line to get the full statement. So:

const
  power = 10;

is the same as:

const power = 10;

However, for complete statements like return statements, the JavaScript interpreter will treat them as separate lines. So:

return
  a ** 10;

is not the same as:

return a ** 10;

Even though JavaScript is a friendly language, it’s still very easy to make mistakes when writing JavaScript code. It’s easy to confuse undefined and null when we aren’t familiar with JavaScript. Because of the dynamic typing nature of JavaScript, operators like the + operator that can do multiple things can easily be converted to a type we don’t expect and produce the wrong result. Also, if statements can be complete on their own, then they shouldn’t be written in their own lines if we want both lines to be together.