I'm working in a large Javascript codebase at the moment littered with code resorting to Exceptions for flow control
function getChecklistUrl() {
try {
return dataLayerObject.config.checklist;
} catch (e) {
try {
console.error('dataLayer', e);
} catch (ignore) {}
}
}
I might favor conditional logic such as this implementation of the same function
function getChecklistUrl() {
if(typeof dataLayerObject == 'object' &&
'config' in dataLayerObject &&
typeof dataLayerObject.config == 'object' &&
'checklist' in dataLayerObject.config &&
typeof dataLayerObject.config.checklist == 'object') {
return dataLayerObject.config.checklist;
}
return null;
}
While the later feels longwinded, a helper function could certainly be written to reduce the boilerplate for such checks.
Is the former then idiomatic for Javascript? Is the later brittle (across browsers / scenarios) and better left to a try
/ catch
anyway? Or is the former simply evidence of laziness?
Edit
these objects are presumed to be 'plain' objects, such as var obj = {}
so I don't believe I care about the prototype chain here.
The proper way to check for object properties in javascript is the Object.hasOwnProperty() method.
example:
var Person = {
first_name: 'Fred',
last_name: 'Flintstone'
};
if ( 'object' === typeof Person && Person.hasOwnProperty('first_name' ) {
window.alert('the property exists!');
}
EDIT
for checking for nested object properties, you can try something like this:
function checkNested(obj /*, level1, level2, ... levelN*/) {
var args = Array.prototype.slice.call(arguments, 1);
for (var i = 0; i < args.length; i++) {
if (!obj || !obj.hasOwnProperty(args[i])) {
return false;
}
obj = obj[args[i]];
}
return true;
}
var test = {level1:{level2:{level3:'level3'}} };
checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false
First of all, you don't need to check for both property in object
&& typeof obj[property] == 'object'
, you can only use the typeof
to take care of both checks. The reason is that, if that obj.property
doesn't exist, typeof
will return undefined
.
Therefore, you can modularize your approach by writing a small utility function that checks if something is an object:
function isObject(o) {
return typeof o == 'object' && o !== null; // take care of the corner case that typeof null == 'object'
}
Then just use it on your necessary object chain to find a property by checking that all of its owning objects exists:
function getChecklistUrl() {
if(isObject(dataLayerObject) &&
isObject(dataLayerObject.config) &&
isObject(dataLayerObject.config.checklist)) {
return dataLayerObject.config.checklist;
}
return null;
}
var dataLayerObject = {
config: {
checklist: ['array of stuff']
}
}
function isObject(o) {
return typeof o == 'object' && o !== null;
}
function getChecklistUrl() {
if (isObject(dataLayerObject) &&
isObject(dataLayerObject.config) &&
isObject(dataLayerObject.config.checklist)) {
return dataLayerObject.config.checklist;
}
return null;
}
console.log(getChecklistUrl()[0]);
This makes the code more organized and easier to read, IMHO.
You can also do something like a getter for your objects that take a dot-separated string and return you the property or null if the property doesn't exist:
function getObjProperty(obj, propString) {
if(!isObject(obj) || !propString || typeof propString != 'string') {
return null; // make sure obj is an object and propString is a non-empty string
}
var props = propString.split('.');
for (var i = 0, l = props.length; i < l; i++) {
var p = props[i];
if(!isObject(obj[p])) { return null; } // if current property isn't an object, return null
obj = obj[p]; // otherwise update the object to the next one down the property chain
}
return obj;
}
You would use it like: getObjProperty(dataLayerObject, 'config.checklist');
var dataLayerObject = {
config: {
checklist: ['array of stuff']
}
};
function isObject(o) {
return typeof o == 'object' && o !== null;
}
function getObjProperty(obj, propString) {
if (!isObject(obj) || !propString || typeof propString != 'string') {
return null;
}
var props = propString.split('.');
for (var i = 0, l = props.length; i < l; i++) {
var p = props[i];
if (!isObject(obj[p])) { //
return null;
} // if current property isn't an object, return null
obj = obj[p]; // otherwise update the object to the next one down the property chain
}
return obj;
}
console.log(getObjProperty(dataLayerObject, 'config.checklist'));
Or you can achieve this with a fairly straightforward recursive method
NOTE:
The examples above check the prototype chain as well. If you don't want this, you should use hasOwnProperty
when checking if a property is an object to also check that the property exists on the testing object.
Prefix's answer shows one variation of such an approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With