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.