2011-09-06 18 views
8

El length property of functions dice cuánto tiempo la lista de argumentos 'espera' es:cómo establecer la longitud de una función

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

Sin embargo, es un método de sólo lectura:

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

¿Existe una forma de establecer programáticamente esa longitud? Lo más cerca que he llegado hasta ahora es utilizar el constructor Función:

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

Sin embargo, el uso de la función es esencialmente la misma que eval y todos sabemos lo mal que es. ¿Qué otras opciones tengo aquí?

+0

Simplemente interesante, ¿cuáles son las razones que lo hacen pensar siquiera en este tema? – shabunc

+2

¿Por qué quieres establecer la longitud? ¿Sabe que cuando llama a una función puede pasar menos que los argumentos "esperados", o más? (Si pasa más, la función puede acceder a ellos a través de su objeto 'arguments'.) – nnnnnn

+0

Fuera de interés, ¿por qué quiere hacer esto? – Russell

Respuesta

5

Por ahora, aquí está la mejor solución que podría pensar.

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

Ejemplo de uso:

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

lengthSix = makeFunc(6, realFn); 

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

lo personal, siempre encogerse cada vez que use copiar y pegar en la programación, por lo que estaría muy feliz de escuchar de cualquier opción mejor.

actualización

pensé en un método que podría funcionar para cualquier tamaño arbitrario, a diferencia del ejemplo anterior que está limitada por la cantidad de veces que desea copiar y pegar. Esencialmente, crea dinámicamente una función (usando new Function) que devolverá una función del tamaño correcto que luego se aproxima a cualquier función que le pase. Sí, eso te lastima la cabeza. De todos modos, pensé que lo compararía con el anterior ...

http://jsperf.com/functions-with-custom-length (aquí también se puede ver el código "malo").

El método malvado es cientos de veces más lento que el método pirata copypasta, así que ahí lo tienes.

+0

Esta es una de las razones por las que necesitamos macros en JavaScript. –

1

De acuerdo con ECMA Script standard, revision 5.1 en la página 103, el parámetro .length en un objeto Function no se puede escribir, por lo que se establece cuando la función se declara y no se puede cambiar (si se implementa por especificación).

Por lo tanto, la única forma de crear una función con un .length bajo demanda es tener un conjunto de funciones de varias longitudes (como sugiere nickf), crear un objeto Function (como ya lo mencionó) o use eval() con una cadena creada dinámicamente. No sé qué problema estás tratando de resolver, pero personalmente no encuentro nada de malo con el uso de eval() si conoces la fuente del código con el que lo estás usando y tienes una forma de verificarlo o saber lo que tendrá o no tendrá en él. En este caso, generar programáticamente una cierta cantidad de parámetros en una cadena antes de llamar a eval() no supone ningún riesgo de seguridad del que tenga conocimiento.

Si esto es todo su propio código, puede crear una nueva propiedad en la función .dynLength que sea mutable y configurarlo para que lo use su código.

+0

Gracias por eso, sin embargo, el problema * real * con el uso de 'Function' o' eval' es el alcance en el que se ejecuta. No es equivalente a escribir ese código en el alcance actual, que no funciona en mi caso. – nickf

+1

@nickf: ¿cuál es el verdadero problema que intentas resolver? ¿Por qué dinámicamente necesita crear una función con un valor '.length' en particular? – jfriend00

+0

es una biblioteca que envuelve las funciones arbitrarias para hacer cosas como anotación y espionaje (por ejemplo, contar el número de llamadas, almacenar los argumentos, etc.). Como el código que lo usa * puede * estar controlando la propiedad de longitud de la función, me gustaría asegurarme de que no cambie después de haber sido envuelto. – nickf

3

estoy haciendo algo parecido a lo que está pidiendo el uso de más o menos lo siguiente:

/* 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]; 
} 

Este qué usar un constructor de funciones pero de una manera estrictamente limitada y sólo alguna vez una vez por tamaño de función para crear un contenedor de funciones (que se almacena en caché para ese tamaño) después de eso utilizo wrapper.bind (real_function) para proporcionar la implementación como una función expresión/objeto.

Las ventajas son que cualquier tamaño es compatible y no estamos codificando las definiciones de función pero las implementaciones de funciones reales nunca se hacen de una manera 'eval' y la cadena que se pasa al constructor de funciones siempre es la misma.

/* ---- 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 

Estoy seguro de que hay un montón de razones que esto es malo, también (empezando por el uso de las funciones de vinculación significado creados de esta manera no se puede enlazar a un objeto), pero es útil en situaciones donde uno necesita ser capaz de crear una función de longitud arbitraria de forma dinámica.

1

Hay un módulo NPM util-arity, que hace lo que quiere:

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

arity(3,() => {}).length; //» 3 
4

Resulta que la propiedad length en funciones se configurable, lo que significa que puede utilizar .defineProperty para cambiar el valor de la longitud de Una función. Ejemplo:

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

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

Esto funciona en la última versión de Chrome y Firefox, pero no funciona en Safari (v9.1.1).

+0

Esto también funciona en V8 moderno (Probé con el Nodo 6.x). ¡Gracias! – doublerebel

Cuestiones relacionadas