Skip to content

Instantly share code, notes, and snippets.

@ugultopu
Created January 26, 2021 23:23
Show Gist options
  • Save ugultopu/f35b107f82521dbd361a342d20b19094 to your computer and use it in GitHub Desktop.
Save ugultopu/f35b107f82521dbd361a342d20b19094 to your computer and use it in GitHub Desktop.
Demonstration of the benefits of the strict mode in JavaScript by an example

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment