javascriptevalwrapperarity

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


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.


Solution

  • 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;
    }