Part 3
In Part 1, I discussed the difference between the null and undefined types in javascript. In Part 2, I talked about some techniques for properly checking to see if a variable, object, property or method is safe to access. In this 3rd and final part, I'm going to write a pair of functions that aid in safely referencing objects in javascript: the safeRef(..) function and the varExists(..) function.
The safeRef(..) function
{
if (typeof objType == 'string')
{ return (typeof obj == objType); }
else if (typeof objType == 'object' && objType != null)
{ return (obj instanceof objType); }
else
{ return (typeof obj != 'undefined' && obj != null); }
}
Utilizing this function is pretty straight-forward.
{
this.name = name;
this.eat = function() { alert(this.name + ' is eating!'); };
}
var c = new Cat('Felix');
if (safeRef(c.name)) // ensures that c.name is an existing property
{ alert('The cat\'s name is ' + c.name); }
if (safeRef(c.eat, 'function') // ensures that c.eat exists and is a function
{ c.eat(); }
if (safeRef(c, Cat)) // ensures that c is of type Cat
{ alert('c is of type Cat'); }
This is a pretty good start, but suppose you have a custom object that may have another custom object as one of its properties, as in:
{
b: { x: 5, y: 10 },
c: { j: 15, k: 20 },
d: { l: 0, m: 1 }
};
Trying to execute if (safeRef(a.b.whatever)) will result in an error. To solve this problem, I've created another function designed to work hand-in-hand with safeRef. It's called varExists(..).
{
if (typeof str != 'string')
{ return false; }
var props = str.split('.');
var obj = window[props[0]];
var i, propsLen = props.length;
for (i = 1; i < propsLen; i++)
{
if (safeRef(obj[props<em>]))
{ obj = obj[props<em>]; }
else
{ return false; }
}
return true;
}
The function, varExists(..) takes a string representation of the object/properties/methods you're trying to access and determines whether or not they can be accessed without causing a runtime error. Once you know that, you can use safeRef(..) to determine explicitely if the object/properties are of the type you're interested in.
{
b: { x: 5, y: 10 },
c: { j: 15, k: 20 },
d: { l: 0, m: 1 }
};
if (varExists('a.b.whatever'))
{ alert(a.b.whatever); }
if (varExists('a.b.x'))
{ alert(a.b.x); }
// better yet…
if (varExists('a.b.x') && safeRef(a.b.x, 'number'))
{ alert('a.b.x is a number: ' + a.b.x); }
Now, we have nice tools at our disposal. If we know for a fact that an object/property/method exists, we can simply use safeRef(..) to verify that it is safe to reference the object and that it is of the type we expect.
If we're not sure that a desired object/property/method exists, we can use varExists(..) to find out first, and then use safeRef(..) to make sure it's the proper type.
Final thoughts:
As a final though, I'd like to say something that is probably going to surprise you. Whenever possible, I recommend avoiding to use of safeRef(..) and varExists(..). Yep, you read that right; I recommend avoiding them.
Why? Because I feel that most javascript programmers are far too lazy and careless when dealing with the variant types that are so intricate to the javascript language. I, personally, feel that we need to start treating javascript more like the strongly-typed languages we're used to (Java, C++, C#, etc…). In most languages, the compiler would never let you get away with trying to reference an undeclared variable.
In Javascript, you should never let yourself reference an undeclared variable.
A compiler would never let you mistakenly try to invoke a method of an object if that method doesn't exist.
In Javascript, you should know about the objects you're using. And you should know what properties and methods they support, and you shouldn't try to access anything that would be invalid.
Of course, this also requires quite a bit of care when writing your custom objects. But it's care that I feel is well worth the effort. For example, don't arbitrarily expose properties of your object if the improper modification of those properties could cause a problem. Instead, write explicit getters and setters and make sure that the passed-in values are valid.
But I digress… I could write another whole blog entry about this, but I'll spare you
Hopefully you've learned something from this article. If nothing else, that javascript is more complicated than a lot of people realize. But hopefully you've also learned something valuable about what the null and undefined types mean to the javascript interpreter and about how to ensure safe referencing of variables, objects, properties and methods.
Comments/suggestions welcome.
3 Responses
Brilliant tutorial, mate! I'm finally working with javascript again for the first time in ten years since hacking around as a teenager; it's the accurate, well-written posts like this one that are filling in so many gaps for me.
Nice post.
One remark to your statements "[..] frequently using the typeof operator exclusively to check for the existence of a variable [..] is probably a sign of bad program design" and "[indicates] that your objects [..] are too tightly coupled":
I don't agree here, because I suggest using this check as good practice especially in loosley coupled objects to verify pre-conditions. For instance in setter methods with variant parameters this makes very much sense.
Till,
Granted, the concept has its proper place, and the scenario you point to makes sense. My point is just that if you don't know what properties/methods exist on an object, and you constantly find yourself "guessing" or "testing the waters" by using typeof to determine if a method or property exists, you might want to rethink the design a tad. In my experience, when programming like that is overused, it leads to unwieldy and difficult to maintain code.