Undefined is one of the reasons for confusion and can get under the skin of a developer. But what is it exactly?
Definition of undefined
It is a value
undefined value
primitive value used when a variable has not been assigned a value
Undefined is a special value that lets the programmer know that a variable has not been given a value. Same goes for a return of a function, that does not, in fact, return anything. It differs a bit from null, though. Undefined means that a variable has no value (or there is no property of given name) because it wasn’t assigned with a value so far (or was assigned with a value undefined). The null value means that it was purposely cleared by the programmer. It seems like a good practice to set a variable to null if you mean to clear it, and not to undefined. Both of them are falsy, watch out for some existing code, though (for example a library), that explicitly checks for undefined!
It is a variable
undefined is a property of the global object; i.e., it is a variable in global scope.
The initial value of undefined is the primitive value undefined
There is a global property called undefined, actually. You could even go and try to overwrite it – a word “undefined” is not among the reserved keywords.
1 2 3 |
console.log(window.undefined); // has a value of "undefined" var undefined = 'A string here'; console.log(window.undefined); // undefined |
It is doomed to fail, though. Actually, it will result in an error in strict mode. Let’s investigate deeper!
1 |
console.log(Object.getOwnPropertyDescriptor(window, 'undefined')); |
1 2 3 4 5 6 |
{ configurable: false, enumerable: false, value: undefined, writeable: false } |
All of the window.undefined descriptors are set to false, and therefore value of window.undefined can’t be changed. It is probably due to the fact, that window.undefined is a source of undefined primitive value.
Another way to obtain it is through the void operator.
It is a type
Undefined type
type whose sole value is the undefined value
As you could read in one of my previous posts, it is one of the built-in types. Again, it differs from null. Here, another catch concerning it comes to light.
1 |
console.log(typeof null) // 'object' |
It is a long-standing bug in JavaScript and it will probably never be fixed because a lot of already existing code depends on it. It is a shame that it didn’t at least get to be fixed in the strict mode. For a great explanation of origins of the bug check out a blog post on 2ality.
Any variable that has the undefined value, will be of the undefined type also.
The problem
I often see many beginners come across an error like that:
Uncaught TypeError: Cannot read property ‘propertyName’ of undefined
They seem surprised by it, and no wonder. This issue deserves a little background.
Imagine fetching some data from the server.
1 2 3 4 5 |
fetchUser(userID).then(user => { if (user.address.zipCode) { console.log('User did save his zipCode'); } }); |
The problem is that there is a possibility that the fetched user didn’t save his address at all. It would result in user.address being undefined. Trying to access properties of something of undefined value results in an error.
Possible solutions
If statement
When trying to access a deeply nested variable you can always check if every property in the chain is actually defined.
1 2 3 4 5 |
fetchUser(userID).then(user => { if (user.address && user.address.zipCode) { console.log('User did save his zipCode'); } }); |
It is a valid solution to the problem but can get very messy if it is a very deeply nested property.
1 |
if (user && user.address && user.address.city && user.address.city.area) |
I hope you get my point. There is another solution, though!
Try/catch
As you know, a catch block will be evaluated if any error occurs in the try block.
1 2 3 4 5 6 7 8 9 10 11 |
fetchUser(userID).then(user => { try { if (user.address.zipCode) { console.log("User did save his zipCode"); } else { console.log("User didn't save his zipCode"); } } catch (e) { console.log("User didn't save his zipCode. He didn't save his address at all!"); } }); |
It didn’t really get cleaner, did it? We can simplify it, though. I first saw this solution in a blog post by Silvan Troxler. Let me break it down for you:
1 2 3 4 5 6 7 8 9 |
function getSafe(fn) { try { return fn(); } catch (e) { return undefined; } } getSafe(() => user.address.zipCode); |
The main trick here is that JavaScript only evaluates the fn function when it is actually executed, and that means inside of the try block. If any property in the chain is undefined (except the last one), an error will occur and the function will return undefined.
1 2 3 4 5 |
fetchUser(userID).then(user => { if (getSafe(() => user.address.zipCode)) { console.log('User did save his zipCode'); } }); |
Safe navigation operator
Although it still lays in the future, it is worth mentioning.
1 2 3 4 5 |
fetchUser(userID).then(user => { if (user.address?.zipCode) { console.log('User did save his zipCode'); } }); |
The interpreter, when accessing properties one after another will stop if a property suffixed with a “?” sign is null, or undefined.
Sadly, it is still in stage 1 (as of now) and will probably still take a lot of time to be implemented into the language, if ever. There is also an open discussion about adding this feature to TypeScript. A working babel plugin exists though and you can start using it by enabling stage-1 babel preset. I prepared a code sample for you to try it out.
Summary
Undefined is one of the most important things to understand when learning JavaScript. Ignoring it might result in many issues, including errors that will stop the evaluation of your code. I hope that with the solutions provided above you will avoid such erros. Good luck!
Hey, have a look at this package, it does what you describe in a nice way with default value support:
https://www.npmjs.com/package/safethen