Is it possible to dynamically create functions with specific arguments without using eval or the Function constructor?

235 views Asked by At

Very similar to this Python question but for JavaScript.

I'm using a library that relies on nodejs-depd which uses new Function() to dynamically wrap functions in deprecation messages:

function wrapfunction(fn, message) {
  if (typeof fn !== "function") {
    throw new TypeError("argument fn must be a function");
  }

  var args = createArgumentsString(fn.length);
  var stack = getStack();
  var site = callSiteLocation(stack[1]);

  site.name = fn.name;

  var deprecatedFn = new Function(
    "fn",
    "log",
    "deprecate",
    "message",
    "site",
    '"use strict"\n' +
      "return function (" +
      args +
      ") {" +
      "log.call(deprecate, message, site)\n" +
      "return fn.apply(this, arguments)\n" +
      "}"
  )(fn, log, this, message, site);

  return deprecatedFn;
}

This produces various legitimate concerns about security. But it also produces functions that match the original - if args are arg0, arg1, the new function will be

function (arg0, arg1) {
  log.call(deprecate, message, site)
  return fn.apply(this, arguments)
}

BTW, 'no, this isn't possible' is a fine answer. I just want to find out if this is possible.

1

There are 1 answers

4
Bergi On BEST ANSWER

Dynamically create a function with a specific parameter declaration

It is not possible to dynamically create a function with a specific parameter declaration without eval/new Function, but it also not advisable to depend on that - the code (fn.toString()) is considered an implementation detail.

Dynamically create a function with a specific arity

Yes, it is possible to dynamically create a function with a specific arity, if all you care about in the "signature" is the arity of the function.

The arity (.length) of a function is a non-writable property, but it's still configurable:

function wrapfunction(fn, message) {
  if (typeof fn !== "function") {
    throw new TypeError("argument fn must be a function");
  }

  var stack = getStack();
  var site = callSiteLocation(stack[1]);

  site.name = fn.name;
  var deprecate = this;

  var deprecatedFnOld = function() {
     log.call(deprecate, message, site);
     return fn.apply(this, arguments);
  };
  Object.defineProperty(deprecatedFnOld, 'length', {value: fn.length});
  Object.defineProperty(deprecatedFnOld, 'name', {value: 'deprecated '+fn.name});

  return deprecatedFnOld;
}