开发者

How to determine if a function is empty

开发者 https://www.devze.com 2023-01-28 00:12 出处:网络
I want to be able to check whether a given function is empty or not. That is, there is nothing in its body, eg:

I want to be able to check whether a given function is empty or not. That is, there is nothing in its body, eg:

function foo() {}
function iAmEmpty(a) {
    // ye开发者_开发问答p, empty
}

With some initial playing around, I've got something which I think might be ok, by using toString() and some regexes.

function foo(a, b, c) {}

/^function[^{]+\{\s*\}/m.test(foo.toString());  // true

function bar(a, b, c) { var d; }

/^function[^{]+\{\s*\}/m.test(bar.toString());  // false

I was just wondering if there was a better approach? Are there any problems with the above you can see?


This isn't advisable. There is no standard determining precisely what a function's toString() method should return, so even if you get this working in current browsers, future browsers may justifiably change their implementation and break your code.

Kangax has written briefly about this: http://perfectionkills.com/those-tricky-functions/


Arrow functions...

As I am sure you are aware, javascript supports arrow functions which are really succinct but unfortunately don't work with your neat regex.

I quickly converted your nice regex into its own function which takes a function as an input and returns whether or not it is empty for simplicity later. Just to demonstrate how arrow functions can be widely used, I put it in one:

isEmpty = f => /^function[^{]+\{\s*\}/m.test(f.toString())

Now, we can easily test an empty function:

function eF() {}

which as we would expect with isEmpty(eF) returns true.

And once more with an actual function:

function retOne() {return 1;}

which again as expected with isEmpty(retOne) returns false.

However, the issue I encountered was with arrow functions so to initialize an empty one again, we have a shorter syntax of the original:

eF = () => {}

and the 'stringified'version of that is quite different to the one before:

"() => {}"

so of course in this case the call isEmpty(eF) returns false when we want true. I'm not sure if you require to test if all functions (i.e. including arrow functions) are empty but if you do, your regexwill need modifying...

I am not great at writing them myself, but have attempted a couple and one further thing that you might want to consider is the lenient nature of the arrow functions especially this part of the documentation:

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// equivalent to: (param1, param2, …, paramN) => { return expression; }

// Parentheses are optional when there's only one parameter name:
(singleParam) => { statements }
singleParam => { statements }

which shows how the curly brackets {...} are not always necessary. So this function:

retOne = () => 1

is valid and could make forming a new regex more difficult. One workaround I thought of is to just remove all curly brackets from f.toString() using:

str.replace(/[{}]/g, '').

and then work with a regex test from there.

Hopefully this is something helpful to consider if you want arrow functions to also be able to be tested.


The best thing you can try, to fit the maximum possibilities (as this is pretty hard to achieve), is to add acorn or esprima (works with arrow functions too) libraries and process the JavaScript function. It will tokenize it for you to parse, so you can process it to your likings, checking if there's actually zero code inside, or there's only variable declarations without any calculation nor return, etc...

Is pretty straightforward to implement:

function check(f) {
  console.log("");
  console.log("Checking", f);
  var syntax = esprima.parse(f);
  if (syntax.body.length != 1) {
    console.log("Weird process");
  } else {
    function processBody(body) {
      if (!body || body.length == 0) {
        console.log("Body seems empty. YAY!");
      } else {
        for (var i = 0, command; command = body[i]; i++) {
          if (command.type != "VariableDeclaration") {
            console.log("Body has a", command.type, ", so is not empty.");
            return;
          }
        }
        console.log("Body has only variable declarations, so is empty! (or not, whatever fit your needs)");
      }
    }
    function process(dec) {
      if (dec.type != "FunctionDeclaration") {
        console.log("Weird declaration");
      } else {
        console.log("Function", dec.id.name, "has", dec.params.length, "params");
        processBody(dec.body.body);
      }
    }
    process(syntax.body[0]);
  }
}

check("function hasReturn(arg1, arg2) {var a = arg1 + arg2;return a;}");
check("function hasArgumentsButNoBody(arg1, arg2) {}");
check("function isCompletelyEmptyWithSpacesAndTabsAndLinefeeds() {   \t    \t   \r\n \r\n  }");
check("function hasOnlyVariables() {var a, b = 2; var c = 1 + b;}");
<script src="https://cdnjs.cloudflare.com/ajax/libs/esprima/2.7.3/esprima.min.js"></script>

This will not run the functions, just parse them, so is safe to run with non-secure functions.


I don't see the use for this, but you could make it simpler by anchoring the pattern to the end of the string.

  /[^{\s]\s*\}$/.test(String(bar))


Function.prototype.hasBody = function() {
    return !/{\s*}$/.test(this.toString());
};


It's simple just check the function contain and then check the contain if it's empty or not .
check this Plunker
here's full working code:

function foo(a, b, c) {}

function bar(a, b, c) {
  var d;
}

function isEmpty(value) {
  return (value == null || value.length === 0);
}

function check(test) {
  var entire = test.toString();
  var body = entire.slice(entire.indexOf("{") + 1, entire.lastIndexOf("}"));
  return body;

}
console.log(isEmpty(check(foo)), isEmpty(check(bar))); //return true false
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号