Use `const` and make your JavaScript code better
Learn why using const
makes your code better and learn three quick tips to use it in your code to make it easier to reason about.
Don’t use var
First of all, don’t use var
. There are a few differences between var
, let
and const
and the most important one to me is that let
and const
remove the error-prone behavior with variable hoisting.
See this example:
// ❌ Don't do this
function foo() {
console.log(bar) // No error, but prints undefined
var bar = 1234
}// ✅ This is better
function foo() {
console.log(bar) // ReferenceError: bar is not defined
const bar = 1234
}
Having an error while developing is better than having buggy behavior in production, so I prefer the strictness of let
and const
. Besides, your linter should’ve warned you about the variable not being defined, so with let
, const
and the proper tooling you won’t have any of the previous problems.
There’s only one case where I still use var
: Node.js’ REPL or the browser’s console. Why? Because I want to be able to reuse the same variable name many times. That’s a desirable behavior when you are experimenting with things, but not in production code!
Const != immutable
It’s very important to understand const
. It doesn’t imply immutability.
A variable is like a pointer to a value (it’s a pointer for objects, it’s an assigned value for primitives). const
prevents the variable to be assigned to another value. We could say it makes the pointer immutable, but it doesn’t make the value immutable too!
const arr = [1, 2, 3]arr.push(4) // this is totally finearr = ['foo', 'bar'] // TypeError: Assignment to constant variable.
So beware that arrays and objects assigned to const
variables can be mutated. However numbers, booleans and strings are immutable per se, so they cannot be mutated. Not because you are using const
but just because they are intrinsically immutable.
Why const is better
But why const
is better? In my opinion it makes the code easier to reason about, basically because since it cannot be reassigned you have less scenarios, side effects or edge cases to reason about. One super simple example:
const somethingConst = 'some default value'// A lot of code here...console.log(somethingConst)// You know what the code above will print
// without going through all the previous code.
// You just know it, since the variable cannot be changed
Use a default value with the || operator
In many scenarios the value assigned to a variable will depend on some conditions. For example it is very frequent to assign the returned value of a function to it, but maybe the function returns null
or undefined
and in that case you want to use a default value. In that case you will probably be tempted to use an if
but then you cannot use const
since you will reassign the variable value if the condition is met, and we cannot do that, and we don’t want to do that with const
. But, in JavaScript there’s a nice solution to that.
// ❌ Don't do this
let foo = something()
if (!foo) {
foo = defaultValue
}// ✅ This is better
const foo = something() || defaultValue
Beware that defaultValue
will be used for any falsy value returned by something()
.
Use the ternary operator ? :
Sometimes you want to assign one value or another depending on a condition that is neither the first value nor the second one. You will be tempted to write an if
again in this case. But you can make use of the ternary operator instead!
// ❌ Don't do this
let foo
if (condition) {
foo = valueA
} else {
foo = valueB
}// ✅ This is better
const foo = condition ? valueA : valueB
If the sentence is long enough I will split it into different lines:
const foo = conditionLongEnoughToSplitTheSentence
? valueA
: valueB
Extract functions
But if there are multiple conditions or they are really complex or long enough, it can be a good idea to extract a function instead.
// ❌ Don't do this
let foo
if (conditionA) {
if (conditionB && !conditionC) {
foo = value1
} else if (conditionD) {
foo = value2
} else {
foo = value3
}
} else {
foo = value4
}// ✅ This is better
const calculateFoo = () => {
if (conditionA) {
if (conditionB && !conditionC) {
return value1
} else if (conditionD) {
return value2
}
return value3
}
return value4
}
const foo = calculateFoo()
Extra ball: TypeScript
Another benefit of using const
is that the TypeScript compiler will calculate the types for you.
// ❌ Don't do this
let foo: number[] | string[]
if (condition) {
foo = [1, 2, 3]
} else {
foo = ['a', 'b', 'c']
}// ✅ This is better
const foo = condition ? [1, 2, 3] : ['a', 'b', 'c']
In the first example you have to add the typing information; otherwise the any
type is assigned to foo
, and that’s something you want to avoid. In the second example the compiler is able to deduct the types so you don’t have to write them!
Nevertheless sometimes you might want to add the typing information yourself to enforce the typing and prevent the future you (or a teammate) change the expression and thus change the variable type.
// This enforces the typing, so you prevent somebody
// changing the type of foo indirectly by changing the
// type of either `value1` or `value2`
const foo: number[] = condition ? value1 : value2
I only enforce the typing this way when there’s something that can be error-prone or is confusing.
To recap:
- Don’t use
var
- Use the || or ternary operators to initialize
const
variables that need to be calculated based on some conditions. - Extract functions if the conditions are long or complex enough.