In general terms, a variable is said to be empty when it has been declared, but not assigned a value. In most languages, that equates to having a value of null. In JavaScript, unset variables are assigned a default value of undefined. But that’s only part of the story. Just as falsiness entails several values, based on a variable’s type, so too can emptiness. Consider the {} or [] shorthand for objects and arrays. Depending on your code’s purpose, you may want to regard those as also being empty. In today’s article, we’re going to create a couple of functions that test a variable for emptiness using different approaches.
Defining Empty in JavaScript
Emptiness in JavaScript is not as clear cut as in more strictly typed languages, but we can probably agree on some if not most of the following criteria:
- undefined or null
- an object with no enumerable properties
- an array with no members
- a zero-length string
- a zero number such as 0 or 0.0
Those last two fall under the falsy umbrella. It’s easy enough to avoid the falsiness ambiguity by comparing variables to the boolean false, but in terms of testing for emptiness, you may have to tailor your code to be as specific as you need to be.
Hasn’t Anyone Already Written an isEmpty() Function in JavaScript?
My thoughts exactly. One of the catalysts for this article was a search that I performed for an isEmpty() function in jQuery. What I found was the jQuery.isEmptyObject( object ) function. It checks for properties on an object and those inherited from prototypes.
Underscore.js’s isEmpty()
The lesser-known Underscore.js library does have the more versatile isEmpty(object) function. Looking at the source code below, we can see that it does support falsiness for strings so that a zero-length string is considered to be empty:
_.isEmpty = function(obj) { if (obj == null) return true; if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; for (var key in obj) if (_.has(obj, key)) return false; return true; };
Oddly, there are no checks for numeric data types, so that a value of 12 would be considered empty!
James Edwards’ empty()
Freelance UK web developer James Edwards looked to the PHP function of the same name for inspiration, but modified it as to not support falsy values. In his empty() function, Booleans and numbers are never empty, whereas functions and regular expressions will be treated as empty, because they generally have no enumerable properties. DOM objects such as elements and documents are not empty, because they always have many properties:
function empty(data) { if(typeof(data) == 'number' || typeof(data) == 'boolean') { return false; } if(typeof(data) == 'undefined' || data === null) { return true; } if(typeof(data.length) != 'undefined') { return data.length == 0; } var count = 0; for(var i in data) { if(data.hasOwnProperty(i)) { count ++; } } return count == 0; }
Php.js’s empty()
The php.js library, which offers JavaScript alternatives to PHP functions, also includes an equivalent to the PHP empty() function. The writers cleverly divide the function into two parts: the first tests simple data types against an array of falsy values; the second is devoted exclusively to objects. The latter also covers arrays because these are in fact a special type of object in JavaScript:
function empty (mixed_var) { var undef, key, i, len; var emptyValues = [undef, null, false, 0, "", "0"]; for (i = 0, len = emptyValues.length; i < len; i++) { if (mixed_var === emptyValues[i]) { return true; } } if (typeof mixed_var === "object") { for (key in mixed_var) { // TODO: should we check for own properties only? //if (mixed_var.hasOwnProperty(key)) { return false; //} } return true; } return false; }
I’m not sure that I would have created a local variable to represent the undefined constant (undef) or store the emptyValues.length in the for loop, but, coding style notwithstanding, it’s a highly useful function!
Conclusion
The functions presented here today should give you a substantial head start on your quest for that perfect isEmpty() test. I have come up with a couple of variations myself that I’d like to share with you; these can be flagged to support or reject falsiness as well as inherited properties. We’ll get to those next month.