men1n2
men1n2

Reputation: 87

Why 'Cannot read property 'x' of undefined' is not evaluated to false?

I have encountered the error 'Cannot read property 'x' of undefined' multiple times, and I know that I can manage to work around it by doing the famous

if (foo && foo.bar)

But the question is WHY ? Why do we need to check that object exists to test that his properties are defined or not ? Wouldn't be easier for everyone (approximately, we can safely exclude the guys building JavaScript engines) to test only the target property, and the JavaScript engine do the 'foo &&' behind the scenes ?

Many tears were shed, and more nights were spent sleepless debugging this error, so why the JS engine implementors chose to do it this way ? Is it a part of the specification of ECMAScript (Spec website)? Is it to laugh at newbies ? Is it a conspiration from StackOverflow to make more people visiting their website and ask the same question over and over ?

Ok, it is a part of the ECMAScript specs : The Reference Specification Type (Normally. I am not that expert in reading formal, suit-and-tie-wearing-man-toned documents, but it should be it).

It is a language construct, but why can't they make it easier for the miserable developers using it ?

This question is about learning more about how JavaScript works, so it would be nice if you can explain things in depth.

And for the sake of the completeness of the question, a working example : jsfiddle.net/vr6bgwop/

Upvotes: 0

Views: 533

Answers (4)

Zirak
Zirak

Reputation: 39848

Let's look at some semi-related constructs of the language:

  • Accessing an object's property which doesn't exist (e.g. var foo = {}; foo.a) results in undefined
  • Passing less (or more) arguments than the function expects is legal; accessing a parameter that wasn't passed results in undefined
  • Doing math operations on non-numbers results in NaN, which propagates through the computation (spot the object: a * b - c ^ d, you can't can you?)

And so forth. One can argue that the following all of the examples above should throw an error. Yet they don't, and it's not to save CPU cycles or memory, it's because of how the language works, and a history of fuck-ups. That last one is really, really important.

It's how the language works: Objects and primitives

But let's talk about your example. Why even throw an error?

Contrary to popular belief, not everything in JS is an object: Most things can be turned into an object (see another answer of mine if you're interested), but there are two very important primitives which cannot, namely null and undefined.

Let's look at that for a bit: If you could say that null and undefined can be turned into objects, then there'd be no problem accessing their properties, right?

Well, languages like Ruby do that. In Ruby, truly everything is an object, so you can do this: nil.nil? (see example). Trying to access a non-existing property (or slot? was that how they called it?) raises an exception like it does on any other objects (why are they raising exceptions!? can't they just return undefined?)

But most other languages don't. Famously, null values and friends are there to represent that which is nothing, Not an Object in languages which are familiar with that concept. In Java and C# the NullReferenceException is famous. In C++ it's worse: de-referencing a null causes a segfault! Imagine debugging that!

Therefore, considering the history of the language and its having both objects and primitives, it makes sense to throw an error if you can't treat a primitive like an object.

Imagine that world

Let's say that you're correct: The distinction is stupid, it should be implemented. You raise a bug report, TC39 (the ones in charge of specifying javascript) agree, it's implemented everywhere. Content, you go to your favourite editor, write this smack in the middle of things:

foo.bar.baz = 4

And for some reason, something else in a pretty unrelated part of your system which gets passed foo.bar.baz in some obscure fashion throws weird errors, about false not having a toFixed function.

wtf? How did this happen? How do you approach debugging it?

After several hours of debugging and working your way back through the call stack, you smack your forehead in disbelief. "I'm an idiot", you say to yourself, "how could I have forgotten to initialize foo.bar?"

That's why I also personally think that NaN propagation is a disaster.

But why did you get so worked up?

But finally, let's clear something out of the way: Why did this error cause you hours of debugging? Since this is an exception, it clearly shows you the line and column it happened in, and you obviously know this rule, so the distance from error to finding the right spot is nearly instantaneous. That's the point you should work on, language idiosyncrasies aside.

Aside: Where it is in the spec

You were close to where this behaviour is specified. Luckily for you (and sadly for me...) I've read a lot of that formal, suit-and-tie-wearing-man-toned document, and knew how to find this.

Searching for Property Accessors, you get to the Evaluation section. Note step 7 of the first algo, and step 4 of the second:

Let bv be RequireObjectCoercible(baseValue).

In the RequireObjectCoercible table, we see that undefined and null throw a TypeError.

Upvotes: 7

Cerbrus
Cerbrus

Reputation: 72967

Imagine the overhead of checking all parents of z in a.b.c.d....x.y.z, every time you need to access z.

That's why you explicitly have to check for missing properties, yourself. There is no way to automatically check it without significantly increasing memory / CPU usage.


That said, if you were to automatically return, for example, undefined if one of the parents isn't set, then you'd have no way of knowing whether z is undefined, or if one of it's parents is.

How would you then make sure it's parents are properly set? By manually checking each parent.

Upvotes: 1

Marcos Pérez Gude
Marcos Pérez Gude

Reputation: 22158

Javascript don't allows you to access non-existing properties equal all programming languages. It's a feature that forces you to develop with good practices. There are a lot of methods to avoid your problem in an if statement, for example initialize objects with null or undefined values:

 var foo = {
      bar: null,
      beer: undefined
 }

 if(foo.beer) { 
      // you'll never notice with an undefined error, so undefined is the value!
 }

And please, don't be conspiranoic.

Upvotes: 0

Niels Keurentjes
Niels Keurentjes

Reputation: 41968

Wouldn't be easier for everyone (approximately, we can safely exclude the guys building JavaScript engines) to test only the target property, and the JavaScript engine do the 'foo &&' behind the scenes ?

Of course they could. And it would be much slower due to the extra tests required, also in all the cases where the developer bloody well knows that foo cannot be null:

foo = { bar: (i == 50) };
if(foo.bar) {...}

Developer laziness is never an excuse for slowing down a language. In your case, you are evaluating 2 conditions: whether foo is even set, and if it is, whether its property bar is set. If you test 2 conditions, you write both conditions.

Upvotes: 0

Related Questions