JavaScript is a language that’s used a lot in many places including web development and more.
Like any other language, it’s easy to commit antipatterns when we’re programming with JavaScript.
In this article, we’ll look at some antipatterns we should avoid.
Polluting the Global Namespace
Polluting the global namespace is bad because it’ll lead to name collisions,
Without strict mode, it’s also easy to accidentally declare global variables.
For instance, without strict mode, we can write something like:
x = 1;
which is the same as:
window.x = 1;
window
is the global object in browser JavaScript.
Global variables are accessible everywhere so that we can change their values anywhere.
This isn’t good because we don’t want to run into bugs because of accidental reassignments.
An example of accidental assignment would be something like:
function foo() {
return 1;
}
function bar() {
for (i = 0; i < 10; i++) {}
}
i = foo();
bar();
where we assign i
in many places. i
was assigned in multiple places in a confusing way.
Extending the Object prototype
We should never add any new properties to Object.prototype
.
This is because it’s global. It’ll affect other objects since almost all objects extend the Object.prototype
.
If everyone does this, it’s very easy for people to overwrite the Object
‘s prototype with their own code.
Instead, we should use Object.defineProperty
to define properties with property descriptors that we want to set if we want control over our object’s properties.
Using Multiple var Declarations Instead of One
Multiple var
declarations are less readable and slightly slower, so instead of writing:
var a = 1;
var b = 2;
var c = 3;
we should write:
var a = 1,
b = 2,
c = 3;
Using new Array(), new Object(), new String(), and new Number()
There’s no reason to use the Array
constructor. It’s confusing since the single argument versions and the multiple arguments version do different things.
The single-argument version takes a number and returns an array with the length set by the argument.
The multiple argument version takes the content of the array as arguments. We can pass in as many as we want to populate our new array.
For instance:
const arr = new Array(5);
returns an array with 5 empty slots.
On the other hand,
const arr = new Array(1, 2, 3);
returns [1, 2, 3]
.
Instead, we should use array literals to make things simple for us:
const arr = `[1, 2, 3];`
It’s simpler and less confusing.
Likewise, new Object()
is just extra writing. We have to define the properties on different lines as follows if we want to add properties:
const obj = new Object();
obj.a = 1;
obj.b = 2;
obj.c = 3;
Then obj
‘s value is {a: 1, b: 2, c: 3}
.
That’s just extra writing. Instead, we should use object literals instead:
const obj = {
a: 1,
b: 2,
c: 3
}
It’s much less typing and also pretty clear.
The problem with new String()
and new Number()
are that they give us the type 'object'
. There’s no reason that we need strings and numbers to have the type 'object'
.
We’ve to call valueOf()
if we want to convert them back to primitive values.
Instead, we should use the factory functions String()
and Number()
to convert things to string or number.
For instance, instead of writing:
const str = new String(1);
We write:
const str = String(1);
Also, instead of writing:
const num = new Number('1');
We write:
const num = Number('1');
Relying on the Ordering of Iterator Functions Like .map
and .filter
We shouldn’t rely on the iteration order of forEach
, map
and filter
.
The iteration also doesn’t work if we pass in async
function as the filter.
If we need to iterate through things in a specific order, then we should sort them the way we want before iteration.
Also, if we want to use the index of the entry, we can pass it into the callback so that we get index reliably.
For instance, we can write the following to get the index
and the original array in the callback as follows:
const nums = [1, 2, 3];
nums.forEach((num, index, nums) => {
if (index === nums.length - 1) {
console.log('end');
}
})
This is much better than:
const nums = [1, 2, 3];
let count = 0;
nums.forEach((num) => {
if (count === nums.length - 1) {
console.log('end');
}
count++
})
Not only the second example more complex, but it’s less reliable since we may accidentally change the count
elsewhere.
On the other hand, accessing the index and original array from the callback parameters means there’s no chance of modifying count
outside.
This also applies to map
and filter
.
Conclusion
We can should avoid antipatterns like global variables, unnecessary use of constructors, and extending the Object
‘s prototype.
Also, since we can access the numbers from the parameters, we shouldn’t create variables to track the index on the outside.