A common yet still problematic pattern in JavaScript is testing a variable
against null, presumably to
determine whether the variable has been filled in with an appropriate value.
For example:
var Controller = {
process: function(items) {
if (items !== null) { // Bad
items.sort();
items.forEach(function(item) {
// do something
});
}
}
};Here, the process() method is
clearly expecting that items will be an
array, as indicated by the use of sort()
and forEach(). The intention of this code
is clear: don’t continue processing unless the items argument contains an array. The problem with
this approach is that the comparison against null doesn’t actually prevent future errors. The
value of items could be 1, or a string,
or some random object. All of these are technically not equal to null and would therefore cause the process() method to fail once sort() executes.
Comparing a variable against only null typically
doesn’t give you enough information about the value to determine whether
it’s safe to proceed. Fortunately, JavaScript gives you a number of ways to
determine the true value of a variable.
There are five primitive types in JavaScript: string, number, boolean, null, and undefined. If you are expecting a value to be a
string, number, boolean, or undefined,
then the typeof operator is your best option. The typeof operator works on a variable and returns
a string indicating the type of value:
Basic syntax for typeof is
as follows:
typeof variable
You may also see typeof used in
this manner:
typeof(variable)
Although this is valid JavaScript syntax, this pattern makes
typeof appear to be a function instead
of an operator. For this reason, the pattern without parentheses is
recommended.
Using typeof for detecting these
four primitive value types is the safest way to code defensively. Here are
some examples:
// detect a string
if (typeof name === "string") {
anotherName = name.substring(3);
}
// detect a number
if (typeof count === "number") {
updateCount(count);
}
// detect a boolean
if (typeof found === "boolean" && found) {
message("Found!");
}
// detect undefined
if (typeof MyApp === "undefined") {
MyApp = {
// code
};
}The typeof operator is also
unique in that it can be used on an undeclared variable without throwing
an error. Both undeclared variables and variables whose value is undefined return “undefined” when typeof is used.
The last primitive type, null, is
the one that you normally shouldn’t be testing for. As stated earlier,
comparing simply against null generally
doesn’t give you enough information about whether the value is expected.
There is one exception: if one of the expected values is actually null, then it is okay to test for null directly.
The comparison should be done using either === or !== against null. For example:
// If you must test for null, this is the way to do it
var element = document.getElementById("my-div");
if (element !== null) {
element.className = "found";
}It is entirely possible for document.getElementById() to return null if the given DOM element isn’t found. The
method will return either null or an
element. Because null is one of the
expected outcomes, it’s okay to test for it using !==.
Running typeof null returns “object”, making this an
inefficient way to test for null values.
If you must test for null, use the
identically equal operator (===) or the
not identically equal (!==)
operator.
Reference values are also known as objects. In
JavaScript, any value that isn’t a primitive is definitely a reference.
There are several built-in reference types such as Object, Array, Date,
and Error, just to name a few. The
typeof operator is of little use for reference values, because it returns
“object” for any type of object:
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof new Date()); // "object"
console.log(typeof new RegExp()); // "object"Another downside to using typeof for objects is
that typeof returns “object” for
null values as well:
console.log(typeof null); // "object"
This quirk, which has been recognized as a serious bug in the
specification, prevents accurate detection of null using typeof.
The instanceof operator
is the best way to detect values of a particular reference
type. Basic syntax for instanceof
is:
value instanceof constructor
Here are some examples:
// detect a Date
if (value instanceof Date) {
console.log(value.getFullYear());
}
// detect a RegExp
if (value instanceof RegExp) {
if (value.test(anotherValue)) {
console.log("Matches");
}
}
// detect an Error
if (value instanceof Error) {
throw value;
}An interesting feature of instanceof is that it not only checks the
constructor used to create the object but also checks the prototype chain.
The prototype chain contains information about the inheritance pattern
used to define the object. For instance, every object inherits from
Object by default, so every object
returns true for value instanceof
Object. For example:
var now = new Date(); console.log(now instanceof Object); // true console.log(now instanceof Date); // true
Due to this behavior, it’s typically not good enough to use value instanceof Object when you’re expecting a
particular type of object.
The instanceof operator also
works with custom types that you’ve defined for yourself. For
instance:
function Person(name) {
this.name = name;
}
var me = new Person("Nicholas");
console.log(me instanceof Object); // true
console.log(me instanceof Person); // trueThis example creates a custom Person type. The me variable is an instance of Person, so me
instanceof Person is true. As mentioned previously, all objects
are also considered instances of Object, so me
instanceof Object is also true.
The instanceof operator is the
only good way to detect custom types in JavaScript. It’s also good to use
for almost all built-in JavaScript types. There is, however, one serious
limitation.
Suppose that an object from one browser frame (frame A) is passed
into another (frame B). Both frames have the constructor function Person defined. If the object from frame A is an
instance of Person in frame A, then the
following rules apply:
// true frameAPersonInstance instanceof frameAPerson // false frameAPersonInstance instanceof frameBPerson
Because each frame has its own copy of Person, it is considered an instance of only
that frame’s copy of Person, even
though the two definitions may be identical.
This issue is a problem not just for custom types but also for two
very important built-in types: functions and arrays. For these two types,
you don’t want to use instanceof at
all.
Functions are technically reference types in JavaScript and also
technically have a Function constructor
of which each function is an instance. For example:
function myFunc() {}
// Bad
console.log(myFunc instanceof Function); // trueHowever, this approach doesn’t work across frames due to each
frame having its own Function
constructor. Fortunately, the typeof
operator also works with functions, returning “function”:
function myFunc() {}
// Good
console.log(typeof myFunc === "function"); // trueUsing typeof is the best way to
detect functions, because it also works across frames.
There is one limitation to typeof’s function detection. In Internet
Explorer 8 and earlier, any functions that are part of the DOM (such as
document.getElementById()) return
“object” instead of “function” when used with typeof. For instance:
// Internet Explorer 8 and earlier console.log(typeof document.getElementById); // "object" console.log(typeof document.createElement); // "object" console.log(typeof document.getElementsByTagName); // "object"
This quirk arises due to how the browser implements the DOM. In short, these early versions
of Internet Explorer didn’t implement the DOM as native JavaScript
functions, which caused the native typeof operator to identify the functions as
objects. Because the DOM is so well defined, developers typically test
for DOM functionality using the in
operator, understanding that the presence of the object member means
that it’s a function, as in:
// detect DOM method
if ("querySelectorAll" in document) {
images = document.querySelectorAll("img");
}This code checks to see whether querySelectorAll is defined in document, and if so, goes on to use that
function. Though not ideal, this is the safest way to check for the
presence of DOM methods if you need to support Internet Explorer 8 and
earlier. In all other cases, the typeof operator is the best way to detect
functions in JavaScript.
Passing arrays back and forth between frames was one of the original
cross-frame issues in JavaScript. Developers quickly discovered that
instanceof Array didn’t always
produce appropriate results in these cases. As mentioned previously,
each frame has its own Array
constructor, so an instance from one frame isn’t recognized in another.
Douglas Crockford first recommended performing some
duck typing, testing for the presence of
the sort() method:
// Duck typing arrays
function isArray(value) {
return typeof value.sort === "function";
}This detection relies on the fact that arrays are the only object
types with a sort() method. Of
course, this version of isArray()
will also return true when any object with a sort() method is passed in.
There was quite a lot of investigation into accurately detecting array types in JavaScript; ultimately, Juriy Zaytsev (also known as Kangax) proposed an elegant solution to this problem:
function isArray(value) {
return Object.prototype.toString.call(value) === "[object Array]";
}Kangax found that calling the native toString()
method on a given value produced a standard string in all
browsers. For arrays, the string is “[object Array]”, and this call worked
regardless of the frame from which the array originated. Kangax’s approach
quickly became popular and is now implemented in most JavaScript
libraries.
This approach is generally useful for identifying native objects
as opposed to developer-defined objects. For example, the native
JSON object returns “[object JSON]”
using this technique.
Since that time, ECMAScript 5 has introduced Array.isArray()
formally into JavaScript. The sole purpose of this method is to
accurately determine whether a value is an array. As with Kangax’s
function, Array.isArray() works with
values that are passed across frames, so many JavaScript libraries now
implement methods similar to this:
function isArray(value) {
if (typeof Array.isArray === "function") {
return Array.isArray(value);
} else {
return Object.prototype.toString.call(value) === "[object Array]";
}
}The Array.isArray() method is
implemented in Internet Explorer 9+, Firefox 4+, Safari
5+, Opera 10.5+, and Chrome.
Another time when when developers typically use null (and also undefined) is when trying to determine whether a
property is present in an object. For example:
// Bad: Checking falsyness
if (object[propertyName]) {
// do something
}
// Bad: Compare against null
if (object[propertyName] != null) {
// do something
}
// Bad: Compare against undefined
if (object[propertyName] != undefined) {
// do something
}Each of these examples is actually checking the value of the
property with the given name rather than the existence of the property
with the given name, which can result in errors when you’re dealing with
falsy values such as 0, "" (empty string), false, null,
and undefined. After all, these are all
valid values for properties. For example, if the property is keeping track
of a number, the value might very well be zero. In that case, the first
example will likely cause a bug. Likewise, if the property value could be
null or undefined, all three examples can cause
bugs.
The best way to detect the presence of a property is to use
the in operator. The
in operator simply checks for the
presence of the named property without reading its value, avoiding
ambiguity with statements such as those earlier in this section. If the
property either exists on the instance or is inherited from the object’s
prototype, the in operator returns
true. For example:
var object = {
count: 0,
related: null
};
// Good
if ("count" in object) {
// this executes
}
// Bad: Checking falsy values
if (object["count"]) {
// this doesn't execute
}
// Good
if ("related" in object) {
// this executes
}
// Bad: Checking against null
if (object["related"] != null) {
// doesn't execute
}If you only want to check for the existence of the property on the
object instance, then use the hasOwnProperty() method. All
JavaScript objects that inherit from Object have this
method, which returns true when the property exists on
the instance (if the property only exists on the prototype, in which case
it returns false). Keep in mind that DOM objects in
Internet Explorer 8 and earlier do not inherit from
Object and therefore do not have this property. That
means you’ll need to check for the existence of
hasOwnProperty() before using it on potential DOM
objects (if you know the object isn’t from the DOM, you can omit this
step).
// Good for all non-DOM objects
if (object.hasOwnProperty("related")) {
//this executes
}
// Good when you're not sure
if ("hasOwnProperty" in object && object.hasOwnProperty("related")) {
//this executes
}Because of Internet Explorer 8 and earlier, I tend to use the
in operator whenever possible and only use
hasOwnProperty() when I need to be sure of an instance
property.
Whenever you want to check for the existence of the property, make
sure to use the in operator or
hasOwnProperty(). Doing so can avoid a lot of
bugs.
Of course, if you want to specifically check for the values of
null or undefined, use the guidelines in Chapter 1.