Reputation: 87
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
Reputation: 39848
Let's look at some semi-related constructs of the language:
var foo = {}; foo.a
) results in undefined
undefined
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.
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.
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 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.
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
beRequireObjectCoercible(baseValue)
.
In the RequireObjectCoercible
table, we see that undefined
and null
throw a TypeError
.
Upvotes: 7
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
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
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