最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to programmatically set the length of a function - Stack Overflow

programmeradmin3浏览0评论

The length property of functions tells how long the 'expected' argument list is:

console.log((function () {}).length);  /* 0 */
console.log((function (a) {}).length); /* 1 */
console.log((function (a, b) {}).length); /* 2 etc. */

However, it is a readonly method:

f = function (a) {};
alert(f.length); // 1
f.length = 3;
alert(f.length); // 1

Is there a way to programmatically set that length? The closest I've come so far is to use the Function constructor:

f = new Function("a,b,c", "/* function body here */");
f.length; // 3

However, using Function is essentially the same as eval and we all know how bad that is. What other options do I have here?

The length property of functions tells how long the 'expected' argument list is:

console.log((function () {}).length);  /* 0 */
console.log((function (a) {}).length); /* 1 */
console.log((function (a, b) {}).length); /* 2 etc. */

However, it is a readonly method:

f = function (a) {};
alert(f.length); // 1
f.length = 3;
alert(f.length); // 1

Is there a way to programmatically set that length? The closest I've come so far is to use the Function constructor:

f = new Function("a,b,c", "/* function body here */");
f.length; // 3

However, using Function is essentially the same as eval and we all know how bad that is. What other options do I have here?

Share Improve this question asked Sep 6, 2011 at 8:01 nickfnickf 546k198 gold badges658 silver badges726 bronze badges 7
  • 1 Just interesting, what are the reasons that makes you even to think about this issue? – shabunc Commented Sep 6, 2011 at 8:06
  • 3 Why do you want to set the length? You know that when you actually call a function you can pass fewer than the "expected" arguments, or more? (If you pass more the function can access them via its arguments object.) – nnnnnn Commented Sep 6, 2011 at 8:07
  • 1 Out of interest, why do you want to do this? – Russell Commented Sep 6, 2011 at 8:10
  • 1 I'm writing a mocking framework, and I'd like to make sure that I make as few modifications as possible to the functions which are being spied upon. – nickf Commented Sep 6, 2011 at 8:14
  • 2 Another reason for wanting to do this is writing combinators for variadic functions. For example, it is easy to make a reversed(f) combinator to make a function that receives arguments in the reverse order but it is not trivial to make the returned function have the same length as the original. – hugomg Commented Dec 11, 2011 at 18:37
 |  Show 2 more comments

5 Answers 5

Reset to default 12

It turns out the length property on functions is configurable, which means you can use .defineProperty to change the value of length on a function. Example:

function hi() {}
hi.length === 0; // Expected

Object.defineProperty(hi, "length", { value: 5 })
hi.length === 5; // Intriguing

This works in the latest version of Chrome and Firefox, but it does not work in Safari (v9.1.1).

For now, here's the best solution I could think of.

makeFunc = function (length, fn) {
    switch (length) {
    case 0 : return function () { return fn.apply(this, arguments); };
    case 1 : return function (a) { return fn.apply(this, arguments); };
    case 2 : return function (a,b) { return fn.apply(this, arguments); };
    case 3 : return function (a,b,c) { return fn.apply(this, arguments); };
    case 4 : return function (a,b,c,d) { return fn.apply(this, arguments); };
    case 5 : return function (a,b,c,d,e) { return fn.apply(this, arguments); };
    case 6 : return function (a,b,c,d,e,f) { return fn.apply(this, arguments); };
    case 7 : return function (a,b,c,d,e,f,g) { return fn.apply(this, arguments); };
    case 8 : return function (a,b,c,d,e,f,g,h) { return fn.apply(this, arguments); };
    case 9 : return function (a,b,c,d,e,f,g,h,i) { return fn.apply(this, arguments); };
    default : return function (a,b,c,d,e,f,g,h,i,j) { return fn.apply(this, arguments); };
    }
};

Example usage:

var realFn = function () {
    return "blah";
};

lengthSix = makeFunc(6, realFn);

lengthSix.length; // 6
lengthSix(); // "blah"

Personally, I always cringe whenever I use copy and paste when programming, so I'd be very happy to hear of any better options.

Update

I thought of a method which could work for any arbitrary size, unlike the example above which is limited by how many times you want to copy-and-paste. Essentially, it dynamically creates a function (using new Function) which will return a function of the right size which then just proxies through to whatever function you pass to it. Yeah that does hurt your head. Anyway, I thought I'd benchmark it against the above...

http://jsperf.com/functions-with-custom-length (you can see the 'evil' code there too).

The evil method is many hundreds of times slower than the hacky copypasta method, so there you go.

I am doing something like what you're asking for using roughly the following:

/* Make a new function with a given size */
function SizedFunc(num_args) {
  if(SizedFunc.sizedFuncs === undefined) SizedFunc.sizedFuncs = {};
  if(SizedFunc.sizedFuncs[num_args] === undefined) {
    var argNames = [];
    for(var i = 0; i < num_args; ++i) {
      argNames.push('arg' + i);
    }
    SizedFunc.sizedFuncs[num_args] = new Function(argNames, 'return this.apply(null, arguments);');
  }
  return SizedFunc.sizedFuncs[num_args];
}

This does use a Function constructor but in a strictly limited way and only ever once per function size to create a function wrapper (which is cached for that size) after that I use wrapper.bind(real_function) to provide the implementation as a function expression/object.

Advantages are that any size is supported and we aren't hard coding the function definitions but actual function implementations are never done in an 'eval' like way and the string passed to the Function constructor is always the same.

/* ---- example ---- */ 
var a = SizedFunc(4).bind(function() { console.log.apply(null, arguments); });
var b = SizedFunc(4).bind(function(a, b, c, d) { console.log(a + b, c + d); });

console.log(typeof a);  // -> function
console.log(typeof b);  // -> function
console.log(a.length);  // -> 4
console.log(b.length);  // -> 4
a(1, 2, 3, 4)           // -> 1 2 3 4
a(1, 2, 3, 4, 5);       // -> 1 2 3 4 5
b(1, 2, 3, 4)           // -> 3 7

I'm sure there are plenty of reasons this is bad, too (starting with the use of bind meaning functions created this way can't be bound to an object) but it is useful in situations where one needs to be able to create a function of arbitrary length dynamically.

According to the ECMA Script standard, revision 5.1 on page 103, the .length parameter on a Function object is not writable so it is set when the function is declared and not changable (if implemented per spec).

Thus, the only way to create a function with a particular .length upon demand is to either have a bunch of functions lying around of various length (as nickf suggests), create a Function object (as you've already mentioned) or use eval() with a dynamically created string. I don't know what problem yu're actually trying to solve, but I personally find nothing wrong with using eval() if you know the source of the code you're using it with and have a way of either checking it or knowing what it will or won't have in it. In this case, programmatically generating a certain number of parameters in a string before calling eval() on it poses no security risk I'm aware of.

If this is all your own code, you can just create a new property on the function .dynLength that is mutable and set it to whatever you want and have your code use that.

There’s an npm module util-arity which does what you want:

const arity = require('util-arity');

arity(3, () => {}).length;  //» 3
发布评论

评论列表(0)

  1. 暂无评论