Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There’re lots of tricky parts to JavaScript, so there’re many things to avoid. We can follow some best practices easily to make our JavaScript code easy to read.
In this piece, we look at how to deal with objects in an easily maintainable way. We go through the traps that we can fall into when we define objects.
Use Literals for Primitive Values
In JavaScript, there’re multiple ways to declare primitive variables. Primitive variables include variables of any type other than objects.
One way is to use literals like so:
let x = 1;
let y = true;
let z = '';
In the code above, we set the literal value for number, boolean, and string to each variable.
Alternatively, we can write:
let x = new Number(1);
let y = new Boolean(true);
let z = new String('');
We can also write:
let x = Number(1);
let y = Boolean(true);
let z = String('');
Of the three ways to declare primitive variables above, some ways are better than the others. The best ways are to set the literals directly. The other way is to use the functions as we have in the third example.
Why shouldn’t we use the constructor to create variables with primitive values? First, anything defined with the new
operator will have the type ‘object’ even though they have primitive values. This makes comparison between these objects difficult.
For example, if we write:
new String('foo') === new String('foo')
We get false
because they’re both of type ‘object’, and ===
will evaluate to false
if two objects don’t have the same reference in JavaScript. This means comparison is difficult.
Comparing with ==
will also evaluate to false
for the same reason.
Since they’re both of type ‘object’, it’s harder to know whether they’re actually strings, booleans, or numbers.
The other two ways are much better because they’ll get us the right type. For example, the following will get us the type ‘number’:
let x = 2;
console.log(typeof x);
This also applies to other primitive data types.
There’re no reasons to use the new
operator to declare things with primitive values. It just makes life harder.
Using new
is also slower since the JavaScript interpreter has to do one more operation than necessary to declare something that has a primitive of type ‘object’.
The Number
, String
and Boolean
functions are useful for converting objects from one type to another. For example, if we have:
let x = '2';
Then we can convert it to a number with a Number
function as follows:
let y = Number(x);
Use Literals to Declare Objects Whenever They Exist
Some objects have literals associated with them. For example, arrays have the [...]
literal. Regular expressions can be declared by surround patterns with slashes. Functions can be declared with the function
keyword or using the fat arrow.
It’s confusing to define values with constructors sometimes. For example, arrays have two constructors. One has one parameter with the array length as the parameter. The other is a comma-separated list of entries.
This means that Array(1)
will get us an empty array with length
1 and no content. On the other hand, Array(1,2,3)
will get us [1,2,3]
.
As we can see, it’s just not as clear to declare an array by using the constructor.
For functions, we have the function
keyword or Function
constructor.
Using the Function
constructor doesn’t make sense since we have to pass strings that have the code for our functions and strings for the parameter names. It opens things up for code injection attacks, and it’s a pain to write code as a string.
The function
keyword is much more clear. And it lets us write code that’s recognized by text editors as function code. There’s no reason to declare a function with the Function
constructor unless we want to define a function that has dynamic code.
Likewise, the RegExp
constructor is good for constructing regular expression objects dynamically, but otherwise is the same as the regular expression literal. The regular expression literal and constructor are the same for static regular expressions, so there’s some use for the constructor.
The Object
constructor just makes us type more code than object literals; otherwise, they’re the same. This means it’s kind of pointless to use it to declare objects.
Automatic Type Conversions
For primitive values, JavaScript can convert things to different types depending on the context.
For example, suppose we have:
1 == '1'
Then the string 1 will be converted to a number.
Suppose we have:
1 + '1'
Then the number 1 will be converted to a string so that we get '11'
. JavaScript just assumes that we’re concatenating.
On the other hand, suppose we write:
1 - '1'
We get 0 because it assumes that we’re subtracting two numbers.
Suppose we write:
1 - 'a'
Because the result isn’t a number, we get NaN
since we can’t subtract a number with a non-numeric string.
In an expression that evaluates to a boolean, the variables or values inside are evaluated to their truthy or falsy values.
Falsy values include:
- 0
null
undefined
- empty string
false
NaN
Everything else is truthy. Suppose we have the following:
0 || undefined || null || 1
We get 1. The JavaScript interpreter evaluates all the falsy values and returns 1 since that’s the last thing left.
To make sure we get the types we expect, we should convert everything to the type that we actually expect or we should check for the type.
To convert primitive values like numbers, booleans, and strings, we can use the Number
, Boolean
, and String
functions respectively. We just pass in whatever objects to these functions.
We can also use the +
sign before something to convert it to a number.
!!
also converts things to boolean according to their truthiness. The first exclamation mark converts the value according to its truthiness and then negates it. Then the second exclamation mark converts it back to the original truthiness value.
To check the type of primitive values, we can use the typeof
operator. However, note that null
has the type ‘object’. Everything has the type that we expect.
In most cases, object literals are the clearest way to define objects. The only exceptions are when we need functions with dynamically generated code or regular expressions that are dynamically generated. We should never use the new
keyword to declare things that have primitive values. Finally, we should be careful of automatic type conversions and check the types or convert them according to our needs.