42

I'm an experienced C++/Java programmer working in Javascript for the first time. I'm using Chrome as the browser.

I've created several Javascript classes with fields and methods. When I read an object's field that doesn't exist (due to a typo on my part), the Javascript runtime doesn't throw an error or exception. Apparently such read fields are 'undefined'. For example:

var foo = new Foo();
foo.bar = 1;
var baz = foo.Bar; // baz is now undefined

I know that I can check for equality against 'undefined' as mentioned in "Detecting an undefined object property in JavaScript", but that seems tedious since I read from object fields often in my code.

Is there any way to force an error or exception to be thrown when I read an undefined property?

And why is an exception thrown when I read an undefined variable (as opposed to undefined object property)?

Community
  • 1
  • 1
stackoverflowuser2010
  • 38,621
  • 48
  • 169
  • 217
  • Have a look at JavaScript proxies. – nalply Nov 12 '16 at 15:30
  • This won't answer this question (stop reading undefined properties), but if you'd like to prevent accidentally creating a new property by a typo (e.g. if you do foo.barr = 1 this creates a new property barr). So if you'd like to stop that, you can call Object.preventExtensions(foo); and use it together with 'strict mode' - should throw a type error. – Maciej Krawczyk Oct 24 '19 at 10:27
  • I would suggest you to have a look at [my answer](https://stackoverflow.com/a/63548095/3395831) and mark it as accepted if you find it satisfactory. I was having the same problem as well and currently, that's the most straightforward, non-hacky (clean) solution to overcome this problem. – Utku Sep 06 '20 at 19:53

7 Answers7

32

This can be achieved using ES6 proxies:

function disallowUndefinedProperties(obj) {
    const handler = {
        get(target, property) {
            if (property in target) {
                return target[property];
            }

            throw new Error(`Property '${property}' is not defined`);
        }
    };

    return new Proxy(obj, handler);
}

// example
const obj = { key: 'value' };
const noUndefObj = disallowUndefinedProperties(obj);

console.log(noUndefObj.key);
console.log(noUndefObj.undefinedProperty); // throws exception
P. Meller
  • 421
  • 1
  • 4
  • 4
  • 1
    This has some unfortunate side effects, as it won't work when inspecting the object (e.g., when running through a REPL or using `console.log`). – Ian Hunter Mar 23 '18 at 17:33
  • 1
    zealint (mentionned below) mitigates this issue https://github.com/Bootstragram/zealit/blob/a30c3e933928f52972094ce69ee02264ad8ec263/zealit.js#L8 – Antoine Mar 28 '18 at 17:44
  • Wow, this is great! – Hoffmann Jun 19 '18 at 20:24
  • This looks fantastic. Are there any significant drawbacks to this approach? – Daniel Waltrip Aug 01 '18 at 22:17
  • This is great! If you'd also like to prevent creating new properties by accident, set: Object.preventExtensions(noUndefObj); and use strict mode – Maciej Krawczyk Oct 24 '19 at 10:47
  • @Daniel - I don't know, but you can enable this only on your dev build and disable it for production version. – Maciej Krawczyk Oct 24 '19 at 10:49
  • Be aware that this creates an absolutely enormous performance overhead for accessing properties -- in my tests, it is about **100 times slower** than without a proxy. – Seven Systems Jul 24 '22 at 22:28
13

This looks to me like a classic case of trying to shoehorn one language into the paradigms of another - better IMHO to change your coding style to follow how Javascript does things than try to make it conform to C++ concepts and expectations.

That said, if you want to throw an error as you suggest, you'll need to define some sort of custom getProperty function, either on the object you're trying to access or in the global scope. An implementation might look like this:

function getProperty(o, prop) {
    if (o.hasOwnProperty(prop)) return o[prop];
    else throw new ReferenceError('The property ' + prop + 
        ' is not defined on this object');
}

var o = {
    foo: 1,
    bar: false,
    baz: undefined
};

getProperty(o, 'foo'); // 1
getProperty(o, 'bar'); // false
getProperty(o, 'baz'); // undefined
getProperty(o, 'foobar'); 
// ReferenceError: The property baz is not defined on this object

But this is ugly, and now you've got this custom language construct in all of your code, making it less portable (if, for example, you wanted to copy any part of your code into another script, you'd have to copy your new function too) and less legible to other programmers. So I'd really recommend working within the Javascript paradigm and checking for undefined before accessing the properties you need (or setting up your code so that false-y values are expected and don't break things).

As to your second question, why Javascript throws an error for undefined variables but not for undefined object properties, I can't give any better answer than "Because that's what's in the language specification." Objects return undefined for undefined property names, but undefined variable references throw an error.

nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • 44
    You are completely wrong. There is nothing to "shoehorn" here. C structs and C++/C#/Java classes all behave correctly: If you try to read a field that doesn't exist, the compiler will throw an error. This is not some eccentricity of the above languages; it's also simply common sense. – stackoverflowuser2010 Jun 28 '11 at 23:46
  • 13
    @stackoverflowuser2010 - My opinion is irrelevant here, but clearly the ECMAScript/Javascript designers disagree with you. You might as well claim that Javascript doesn't follow "common sense" because it's loosely typed. – nrabinowitz Jun 29 '11 at 05:25
  • 1
    Completly agree with the "shoehorn" term and arguments. However, (specific case here!) in a .net context, you could use T4 technology to autogenerate property accessors (entity auto generation in javascript from edmx file is a good example I think). This way, the code is "less ugly" because this part of the code just becomes a black box and could solve the problem. – Charles HETIER May 21 '13 at 10:06
  • The function would be even more useful if you could pass a default value for when the property is absent. – Bart Jul 24 '13 at 12:34
  • 1
    This answer is wrong. Consider `o = { really: undefined }`. `getProperty(o, 'really')` will throw, but it shouldn't. Instead of doing a `!== undefined`, consider doing `if (prop in o)` or `if(o.hasOwnProperty(prop))`. – easoncxz May 30 '16 at 00:57
  • @nrabinowitz So what do you recommend to detect/protect against an undefined property access? I constantly find myself debugging my JS code, only to find that, say, I have used `someArray.len` instead of `someArray.length`. I'm spending huge amounts of time debugging such trivial things because I'm not getting a warning/error telling me that no such property `len` exists on `someArray`. Is there any way that I will be made aware of this? Any configurations? Any languages that transpile to JS (CoffeeScript, TypeScript, etc.)? If not, then what do you recommend to protect against such bugs? – Utku Aug 16 '20 at 23:59
  • 3
    At present, I think the best answer is to use either Flow or Typescript to enforce static type safety. These options weren’t really available when I wrote the original answer, but they offer a much better option than what the OP requested - with the right setup, you can see these errors right in your editor, rather than hitting unexpectedly at runtime. – nrabinowitz Aug 18 '20 at 17:51
  • @stackoverflowuser2010 couldn't agree more with you.. it's clearly one of the most ridiculous properties of JavaScript. Hides errors and makes them difficult to track down. One literally needs to traverse line by line to see when the interpreter saliently went crazy and ceased further processing. – Vega4 Aug 29 '21 at 09:56
10

Is there any way to force an error or exception to be thrown when I read an undefined property?

That's possible using ES6 proxies as has been said in previous responses. I have done the small node module "zealit" to avoid having to implement it every time.

If someone is interested : https://www.npmjs.com/package/zealit

const zealit = require('zealit')

const ref = { foo: true, bar: undefined }
ref.foo // true 
ref.bar // undefined 
ref.baz // undefined 

const zealed = zealit(ref)
zealed.foo // true 
zealed.bar // undefined 
zealed.baz // throws a ReferenceError 
zertyuio
  • 146
  • 1
  • 5
6

Firefox has an option javascript.options.strict (in about:config). If you enable this, warnings will be logged to the console for many common mistakes, including reading an undefined property, using = instead of == in an if, etc.

(Of course, that's not to say such code is necessarily wrong.)

adw
  • 4,901
  • 1
  • 25
  • 18
0

As of now, the "right" solution to this is to use TypeScript (or another type-checking library such as Flow). Example:

const a = [1,2,3];

if (a.len !== 3) console.log("a's length is not 3!");

On JavaScript, this code will print "a's length is not 3!". The reason is that there is no such property len on arrays (remember that you get the length of an array with a.length, not a.len). Hence, a.len will return undefined and hence, the statement will essentially be equal to:

if (undefined !== 3) console.log("a's length is not 3!");

Since undefined is indeed not equal to 3, the body of the if statement will be run.

However, on TypeScript, you will get the error Property 'len' does not exist on type 'number[]' right on the "compile time" (on your editor, before you even run the code).

Reference

Utku
  • 2,025
  • 22
  • 42
0

Is there any way to force an error or exception to be thrown when I read an undefined property?

In short, no. You can always test for whether or not you ended up with undefined by either comparing to undefined, like you said, or by attempting to access a second level attribute:

s = Foo()

s.bar = 1

s['Bar'] // returns undefined.

s['Bar']['Baz'] // Throws TypeError, s.Bar is undefined.

Additionally, undefined fails in a conditional check, so you can get away with this as a shorthand for the comparison:

if (s['Bar']) {
  // Something here only if s['Bar'] is set.
}

Be aware that this short hand could cause unexpected behavior if s['Bar'] was set, but was a 'Falsey' value, and you were only concerned with whether or not it came back undefined.

g.d.d.c
  • 46,865
  • 9
  • 101
  • 111
  • And, as the other answer indicates, the reason you get undefined for missing attributes, but an error for undeclared variables, is due to the JavaScript Language Spec. – g.d.d.c Jun 28 '11 at 19:45
-1

There is great power in consistency (like in the STL from Alexander Stepanov that Stroustrup adopted into C++). One simple reason for the inconsistent treatment of undeclared properties vs undeclared variables is probably a reflection of the thought and effort that went into evolving the different languages and another is probably the abilities of the people involved.

It also impacts the kind of applications you would entrust to any given language. You hopefully wouldn't try to write mission critical software that runs a multimillion dollar cruise liner engine management system in javascript, for example.

(Probably not a popular answer for javascript aficionados.)

Razzle
  • 479
  • 4
  • 11