6 Things You Should Not Do in JavaScript

Written by akankov | Published 2022/09/06
Tech Story Tags: javascript | software-development | best-practices | javascript-development | javascript-frameworks | javascript-fundamentals | javascript-top-story | web-development

TLDRWe don't have silver bullets in software development. There are always trade-offs. We have to choose the best solution for our project. But we should always keep in mind that there are better ways to do things. And we should always try to use the best practices.via the TL;DR App

for in

for...in is an ECMAScript1 (ES1) feature. This variant of looping appeared in the first version of JavaScript in 1997. It is a very old and not very good way to loop through iterable objects. It is not recommended to use it in modern JavaScript. It is better to use for...of instead.

JavaScript uses prototype inheritance, and in operator will return true even to inherited properties. Let's take a look at the example:

const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
    console.log(key);
}
// Output:
// a
// b
// c

Everything looks fine and predictable here. But what if we add a property to the prototype of the object? Let's add a property d to the prototype of obj:

obj.__proto__.d = 4
for (const key in obj) {
    console.log(key);
}
// Output:
// a
// b
// c
// d

In that example, we intentionally added a property to the object's prototype. But in real life, it is not that easy to control the prototype of the object. It is possible that some libraries will add a property to the prototype of the object. And in that case, for...in will return that property as well.

There are a few ways how to avoid id. The first one is using hasOwnProperty method:

const obj = { a: 1, b: 2, c: 3 };
obj.__proto__.d = 4

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

// Output:
// a
// b
// c

The second one is using Object.keys method:

const obj = { a: 1, b: 2, c: 3 };
obj.__proto__.d = 4
for (const key of Object.keys(obj)) {
    console.log(key);
}

// Output:
// a
// b
// c

As you can see, both options are not very convenient. In the last example, I used for...of loop. And it's quite an often case then developers confuse for...in and for...of loops. They do look very similar, but the result is different. My suggestion will be to use Map for these purposes. Map is a collection of key-value pairs. It is a good alternative to the object. It is more convenient to use Map instead of the object. Let's take a look at the example:

const obj = { a: 1, b: 2, c: 3 };
obj.__proto__.d = 4

const m = new Map(Object.entries(obj));

for (const key of m.keys()) {
  console.log(key)
}
// Output:
// a
// b
// c

.forEach()

There are quite a few array methods in JavaScript. .map, .reduce, .find, .concat to name a few. All of them have a distinct meaning. forEach on the other hand, is a way to iterate over an array. It cannot transform the array. It cannot return a value. It can only produce side effects.

Also, it's not possible to break the loop and pass an async function to it. According to some researchers, it's twice as slow as for loop. So, my suggestion will be to use for loop instead of forEach.

labels

Probably you have never used labels in JavaScript. But if you have, you should stop using them. Labels are a way to mark a block of code. It is possible to use break or continue to break or continue the execution of the code. But it is not recommended to use them. It is better to use for loop with break or continue instead of labels. Let's take a look at the example:

let str = '';

loop1:
  for (let i = 0; i < 5; i++) {
    if (i === 1) {
      continue loop1;
    }

    if (i >= 4) {
      break loop1;
    }

    str = str + i;
  }

console.log(str);
// expected output: "023"

In the previous example, removing labels will not change the result. That example was pretty simple. But in real life, it is not that easy to understand the code. It can get messy really fast.

Object.create()

There are two types of inheritance in JavaScript. The first one is prototype inheritance. The second one is class inheritance. Object.create is a way to create an object with a specific prototype. Let's take a look at the example:

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

const obj2 = Object.create(obj);

console.log(obj2.a); // 1
console.log(obj2); // {}

Prototype inheritance is quite an old way of inheritance. Also, it's very convenient because you don't need to create classes, interfaces, abstract classes, etc. All you need is to have a base object and create a new object with that base object as a prototype. But that flexibility comes with a price. It can result in unpredictable object structure.

Let's take a look at the example:

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

const obj2 = Object.create(obj);
obj2.d = 4;

const obj3 = Object.assign({}, obj2);

console.log(obj3.d); // 4

console.log(obj3.a); // undefined
console.log(obj2.a) // 1

So it's much safer to use class-based inheritance in JavaScript.

A named function using the arrow operator

Arrow functions had their debut in javascript in 2015. Arrow functions are a new way to write anonymous function expressions and are similar to lambda functions in some other programming languages.

Nowadays, there are a couple of ways to create an anonymous function in javascript.

// traditional way
(function (a) {
  console.log(a);
});

// arrow functions
a => console.log(a);

Arrow functions have much more compact syntax and therefore are more readable in cases where you need to pass anonymous functions using array methods like map, filter, reduce etc.

So they are great for anonymous functions. But what if you need to create a named function? Let's take a look at the example:

function foo() {
  console.log('foo')
}
const bar = () => {
  console.log('bar')
}

foo() // foo
bar() // bar

Both these functions will behave in the same way, but the creation of an arrow function is a bit more clunky. There is one advantage of an arrow function. It's possible to override foo with another value, and for bar it's not possible because it's defined as constant.

foo = 'not a function and this is fine';
bar = 'not a function and this is not fine'; // TypeError: Assignment to constant variable.

null

There is one unavoidable data type in javascript. And it is undefined. Sometimes people use null and undefined interchangeably. But that approach is not correct. In JavaScript undefined means a full absence of anything. For example, a function that returns nothing technically will return undefined. Missing function parameters will also have undefined as their value.

function foo(a) {
    console.log(a);
    a++;
}

const result = foo(); // undefined

console.log(result); // undefined

null is a special type that also means the absence of value. But null means that values were intentionally set to nothing.

In order to use default parameters in a function, you need to use undefined instead of null. Let's take a look at the example:

function func(a = 1, b = 2) {
 return a + b; 
}

console.log(func(undefined, 3)) // 4

console.log(func(null, 3)) // 3 null converted to 0

Undefined is unavoidable. It will appear every time the value is missed intentionally or by mistake. But null is not. JavaScript is a very flexible language, so it's a developer's responsibility to keep the code safe. Also, it's much easier to check the value for undefined and much harder for null.

console.log(typeof undefined) // undefined
console.log(typeof null) // object 

Conclusion

We don't have silver bullets in software development. There are always trade-offs. We have to choose the best solution for our project. But we should always keep in mind that there are better ways to do things. And we should always try to use the best practices. I welcome you all to share your thoughts and suggestions in the comments below. Thank you for reading!


Written by akankov | Convert coffee into code )
Published by HackerNoon on 2022/09/06