JavaScriptEmpty Functions

How to check if a JavaScript function is empty

A not so resourceful way of dealing with empty voids

Introduction

I recently came across a question on StackOverflow. It's a few years old, but struck me as bizarre enough that I decided to write about it. The gist of the question in question (pun intended) is empty functions in JavaScript, specifically: if it's possible to detect them.

The reason why I find it bizarre is because the reason(s) why you'd want to do this (E.G. having a no-op in a ternary operator) can be easily negated using simple solutions (E.G. conditional statements).

Now, say you wanted to check if a function is empty to avoid running it. The question is why? If it's to improve performance, you'd be better off letting it fail safe or checking against certain conditions (E.G. if the function returned nothing). In fact you'd be using more resources to check if the function is empty than you would letting an empty function do nothing.

Simply put, why waste resources on checking if something that might do nothing does nothing? The correct way to handle this situation is to determine what to do if it does something and how to let it fail safe if it does nothing. This is best achieved by adhering to best practices and by utilising basic tools like if conditions, typeof/instanceof checks and equality operators in a non-repetitive manner.

That said, I'm not here to talk you out of this so lets get started.

String manipulation

In JavaScript you can turn pretty much anything into a string: Booleans, Numbers, Arrays, Objects, Errors, Dates, URLs, URL Search Parameters, JSON, a DOM and XML. You can even convert a string into a string using String.prototype.toString.

Most built-in types, objects and classes implement the toString() method, including the Function object: Function.prototype.toString.

This is great because it allows us to use string manipulation to determine if there are any statements, expressions or declarations in a function and because all javascript function bodies follow the same syntax - an opening curly brace that has a matching closing curly brace - we can easily isolate the function's body using the indexOf() and lastIndexOf() methods:

  1. Use the toString() method to return the function as a string
  2. Remove new lines and trim off leading and trailing whitespace
  3. Find the index of the first instance of an opening curly brace
  4. Find the index of the last instance of a closing curly brace
  5. Remove the characters before/after those curly braces (inclusive)
  6. Trim the resulting string
  7. If it's length is greater than 0 return false, otherwise return true

To demonstrate, we'll need a few sample functions with varying indentation:

const f1 = function()
 { }
;
const f2 = function wow()
{

};
const f3 = function amazing (a) {
  };
function f4(a, b) {
  const i = 1;
  return i + 1;
};

And the function, implemented as such:

function is_fn_empty(fn) {
  // convert the function into a string
  // remove the new lines
  // trim leading and trailing whitespace
  const string_fn = fn.toString().replace(/\n+/g, "").trim();

  // get the first index of an opening curly brace
  const first_index = string_fn.indexOf("{");

  // remove that curly brace and everything before it from the body of the function
  let body = string_fn.substring(first_index + 1);

  // get the last index of a closing curly brace
  const last_index = body.lastIndexOf("}");

  // remove that curly brace and everything after it from the body of the function
  body = body.substring(0, last_index);

  // remove leading and trailing whitespace from the function body
  body = body.trim();

  // return true if the string is empty, false if not
  return !!(body.length < 1);
}

Which, when called:

console.log(is_fn_empty(f1));
console.log(is_fn_empty(f2));
console.log(is_fn_empty(f3));
console.log(is_fn_empty(f4));

Produces the following output:

true
true
true
false

We could simplify the is_fn_empty function and have it work exactly the same.

function is_fn_empty(fn) {
  // convert the function into a string
  // remove new lines from the string
  // trim leading and trailing whitespace from the string
  const string_fn = fn.toString().replace(/\n+/g, "").trim();

  // get the first and last index of an opening and a closing brace
  const [first_index, last_index] = [string_fn.indexOf("{"), string_fn.lastIndexOf("}")];

  // remove the data before/after those braces (inclusive)
  const body = string_fn.substring(0, last_index).substring(first_index + 1).trim();

  // return true if empty, false if not
  return !!(body.length < 1);
}

Regular expressions

If you're comfortable with using regular expressions, you could use quantifiers to do the same thing using regex.

For a greedy non-specific search, use /\{(.*)\}/s to isolate everything in between the first opening and last closing curly brace:

function is_fn_empty(fn) {
  // the regular expression to check against
  const re_check = /\{(.*)\}/s;

  // convert the function into a string
  // trim leading and trailing whitespace from the string
  // remove all new lines from the string
  const string_fn = fn.toString().trim().replace(/\n+/g, "");

  // try to match the regular expression against the function string
  const match = string_fn.match(re_check);

  // if a match exists
  if (match !== null) {
    // get the trimmed function body
    const body = match[1].trim();

    // return true if empty and false if not
    return !!(body.length < 1);
  }

  // otherwise fail safe and return null
  return null;
}

Otherwise, for a more specific and thorough search, use /^function(.*?\()(.*?\)|\)).*?\{(.*?)\}($|\;$)/ to isolate the function body:

function is_fn_empty(fn) {
  // the regular expression to check against
  const re_check = /^function(.*?\()(.*?\)|\)).*?\{(.*?)\}($|\;$)/;

  // convert the function into a string
  // trim leading and trailing whitespace from the string
  // remove all new lines from the string
  const string_fn = fn.toString().trim().replace(/\n+/g, "");

  // try to match the regular expression against the function string
  const match = string_fn.match(re_check);

  // if a match exists
  if (match !== null) {
    // get the trimmed function body
    const body = match[3].trim();

    // return true if empty and false if not
    return !!(body.length < 1);
  }

  // otherwise fail safe and return null
  return null;
}

To demonstrate and test this method effectively, we'll need a test function with multiple nested block statements to make sure our regular expressions aren't caught out by multiple curly braces in the function body:

function f4(a, b) {
  {
    console.log("Hello");
    {
      console.log("World");
    }
  }
  const i = 1;
  return i + 1;
};

Both functions should output the following:

console.log(is_fn_empty(f1)); // true
console.log(is_fn_empty(f2)); // true
console.log(is_fn_empty(f3)); // true
console.log(is_fn_empty(f4)); // false

Closing thoughts

Although I said I wouldn't talk you out of this, please remember that this article was only written because I wanted to try and solve a bizarre problem. I wouldn't recommend you use any of these functions in production and I reiterate that there are better ways of dealing with void functions.

Thank you for reading.