I had a very sneaky bug that I spent a fair bit of time debugging (using console.log
initially). When I finally used the real debugger (node inspect my-script.js
), I was able to understand where the problem was. The problem was at the following, can you detect the problem?
/**
* - Create a shallow clone of `object`
* - Set the prototype of the clone to the prototype of the original object object
* - Recursively remove _own_ properties of the clone that:
* - Are `null` or `undefined`
* - Have 0 length
* - Have been listed in the `properties` parameter
* - Return the clone
*
* @param {Object} object - The object to remove the properties from.
* @param {String[]} properties - Names of properties to remove.
*/
function removeProperties(object, properties) {
const prototype = Object.getPrototypeOf(object),
objectType = prototype.constructor;
clone = {...object};
// console.log('clone is');
// console.log(clone);
Object.setPrototypeOf(clone, prototype);
// console.log('BEGIN - Called removeProperties with:');
// console.log(object);
// console.log('END - Called removeProperties with:');
// console.log('objectType is:');
// console.log(objectType);
for (const property of Object.keys(clone)) {
const value = clone[property];
// console.log('value is:');
// console.log(value);
// console.log('objectType is:');
// console.log(objectType);
if (value == undefined) delete clone[property];
else if (value.length === 0) delete clone[property];
else if (properties.includes(property)) delete clone[property];
else if (value instanceof objectType) {
// console.log('In instanceof condition');
clone[property] = removeProperties(value, properties);
}
// else if (value instanceof Array && value[0] instanceof objectType) {
// clone[property] =
// value.map(element => removeProperties(element, properties));
// }
}
// console.log('BEGIN - Clone before return is:');
// console.log(clone);
// console.log('END - Clone before return is:');
return clone;
}
I included the whole function so that where the problem is not so obvious, just like it was when I was trying to debug it. The answer is:
const prototype = Object.getPrototypeOf(object),
objectType = prototype.constructor;
clone = {...object};
Since there is a semicolon at the end of objectType
, this code is essentially (in fact) the following:
const prototype = Object.getPrototypeOf(object),
objectType = prototype.constructor;
clone = {...object};
That is, the line that starts with clone
is an assignment expression. It is not a variable declaration statement. In "sloppy mode" (that is, non-strict mode) in an assignment, if the variable that is being assigned to cannot be found, a new variable is implicitly created in the global context and the value is assigned to it. In strict mode, simply a ReferenceError
is thrown with the message "clone is not defined". This caused a very sneaky bug that I was able to debug only after I launched the program with a debugger. I wasn't able to understand the cause of the bug just by printing to the console.
So:
- Always use strict mode.
- Launch the debugger sooner if things start to get messy. Don't try to discover the problem for a long time just by printing things to the console.