Correctly checking property exists in JavaScript
While maintaining some code recently I found myself writing out of pure habit Object.hasOwnProperty.call(obj, 'prop')
as is expected in almost all OSS I've contributed to, and quickly editing that to just !!obj.prop
which I chose because it worked more succinctly in my use case but I generally cringe whenever I read code using that technique. It had me wondering if the OSS project owners who prefer hasOwnProperty
over !!
actually understand the differences, because I've seen pull requests rejected simply because they used !!
and the repo maintainer didn't like the syntax.
I'm going to demonstrate to you 3 ways to check for properties in JavaScript Objects and explain how each can be useful in their own unique way and why hasOwnProperty
isn't always the correct option.
Using type coercion
Regarded widely as evil, JavaScript allows us to coerce any value to a boolean using bang bang (!!
) which can be troublesome. For the same functionality that allows coercion we have the dilemma that with a truthy value we get the expected true
boolean with the type coercion technique, however falsey values will be coerced to a false
boolean appropriately, but this isn't useful when simply checking for the existence of an objects property which would also have the expected false
, yet the property exists with a falsey value.
var Obj = { foo: 'bar' };
function _hasFoo() {
return !!this.foo;
}
_hasFoo.call(Obj, 'foo'); // true
Obj.foo = 0;
_hasFoo.call(Obj, 'foo'); // false
Obj.foo = false;
_hasFoo.call(Obj, 'foo'); // false
Obj.foo = undefined;
_hasFoo.call(Obj, 'foo'); // false
delete Obj.foo;
_hasFoo.call(Obj, 'foo'); // false
Obj.foo = true;
_hasFoo.call(Obj, 'foo'); // true
As you can see in the tests the type coercion technique will not appropriately report that the property exists, so type coercion should never be used in that manner. Instead, consider using type coercion only to test for property existence as well as testing the value is set (as a truthy value).
var RedisDAL = Object.create({
canConnect: function(){
return !!this.endpoint;
},
endpoint: ''
});
RedisDAL.canConnect(); // false
RedisDAL.endpoint = 'myclustername.xxx.0001.region.cache.amazonaws.com:port';
RedisDAL.canConnect(); // true
As demonstrated, type coercion can be a succinct solution, if you intend to test for more than just property existence.
The hasOwnProperty method
The accepted standard way to check for property existence in JavaScript, let me demonstrate;
var RedisDAL = {
canConnect: function(){
return Object.hasOwnProperty.call(this, 'endpoint');
},
endpoint: ''
};
RedisDAL.canConnect(); // true
RedisDAL.endpoint = false;
RedisDAL.canConnect(); // true
delete RedisDAL.endpoint;
RedisDAL.canConnect(); // false
RedisDAL.endpoint = undefined;
RedisDAL.canConnect(); // true
Whether the value is truthy or otherwise, using hasOwnProperty
will consistently report concisely if the object has the property defined.
There is a gotcha, hasOwnProperty
cannot read when the property is inherited through the prototypal chain which can be demonstrated using Object.create
below;
var RedisDAL = Object.create({
canConnect: function(){
return Object.hasOwnProperty.call(this, 'endpoint');
},
endpoint: ''
});
RedisDAL.canConnect(); // false
RedisDAL.endpoint = false;
RedisDAL.canConnect(); // true
delete RedisDAL.endpoint;
RedisDAL.canConnect(); // false
RedisDAL.endpoint = undefined;
RedisDAL.canConnect(); // true
As you can see with the same tests performed the first test reports false
because it could not read the property which RedisDAL
inherited through its prototype.
With the in operator
The only way to be 100% sure that the object and its prototypal inherited properties are checked is using the in
operator like so;
var RedisDAL = {
canConnect: function(){
return 'endpoint' in this;
},
endpoint: ''
};
RedisDAL.canConnect(); // true
RedisDAL.endpoint = false;
RedisDAL.canConnect(); // true
delete RedisDAL.endpoint;
RedisDAL.canConnect(); // false
RedisDAL.endpoint = undefined;
RedisDAL.canConnect(); // true
And now using Object.create
to demonstrate inherited properties also;
var RedisDAL = Object.create({
canConnect: function(){
return 'endpoint' in this;
},
endpoint: ''
});
RedisDAL.canConnect(); // true
RedisDAL.endpoint = false;
RedisDAL.canConnect(); // true
delete RedisDAL.endpoint;
RedisDAL.canConnect(); // false
RedisDAL.endpoint = undefined;
RedisDAL.canConnect(); // true
Now we can see that both test scenarios are consistently reporting their results, but this may not be the desired use case. You may not wish to validate inherited properties for good reason.
In short
It should be up to you as a developer to choose the appropriate technique for your particular use case as none are the same and each have specific pro's and con's.
If you document your code well the choice to use one over the others, then there should be no reason to have a Pull Request denied. All 3 are as valid as one another regardless of a maintainers personal opinions or code standards (lack-there-of) and styles.
Member discussion