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
.
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.
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.
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
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!