2011-09-06 15 views
8

Il length property of functions dice per quanto tempo la lista degli argomenti 'atteso' è:come impostare la lunghezza di una funzione

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

Tuttavia, si tratta di un metodo di sola lettura:

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

C'è un modo per impostare in modo programmatico tale lunghezza? Il più vicino Sono venuto finora è quello di utilizzare il costruttore Function:

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

Tuttavia, utilizzando funzione è essenzialmente lo stesso di eval e sappiamo tutti quanto male che è. Quali altre opzioni ho qui?

+0

Semplicemente interessante, quali sono le ragioni che ti spingono a pensare a questo problema? – shabunc

+2

Perché vuoi impostare la lunghezza? Sapete che quando chiamate effettivamente una funzione potete passare meno degli argomenti "previsti", o più? (Se passi di più, la funzione può accedervi tramite il suo oggetto 'arguments'.) – nnnnnn

+0

Fuori interesse, perché vuoi farlo? – Russell

risposta

5

Per ora, ecco la soluzione migliore che potessi pensare.

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

Esempio utilizzo:

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

lengthSix = makeFunc(6, realFn); 

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

Personalmente, ho sempre rabbrividire ogni volta che uso copiare e incollare in fase di programmazione, quindi sarei molto felice di sentire di tutte le opzioni migliori.

Aggiornamento

ho pensato ad un metodo che potrebbe funzionare per qualsiasi dimensione arbitraria, a differenza l'esempio precedente che è limitata dal numero di volte che si desidera copiare-e-incolla. Essenzialmente, crea dinamicamente una funzione (usando new Function) che restituisce una funzione della giusta dimensione che quindi passa semplicemente a qualsiasi funzione che si passa ad essa. Sì, questo ti fa male alla testa. Ad ogni modo, ho pensato di confrontarlo con quanto sopra ...

http://jsperf.com/functions-with-custom-length (puoi vedere anche il codice "diabolico").

Il metodo malvagio è molte centinaia di volte più lento del metodo hacker copypasta, quindi eccovi.

+0

Questo è uno dei motivi per cui abbiamo bisogno di macro in JavaScript. –

1

Secondo lo ECMA Script standard, revision 5.1 a pagina 103, il parametro .length su un oggetto Function non è scrivibile, pertanto viene impostato quando la funzione è dichiarata e non modificabile (se implementata per specifica).

Quindi, l'unico modo per creare una funzione con un particolare .length su richiesta è di avere un sacco di funzioni che si trovano in giro di varia lunghezza (come suggerisce nickf), creare un oggetto Function (come già accennato) oppure utilizzare eval() con una stringa creata dinamicamente. Non so quale problema stai effettivamente cercando di risolvere, ma personalmente non trovo nulla di sbagliato nell'usare eval() se conosci la fonte del codice con cui lo stai usando e hai modo di verificarlo o conoscerlo che cosa avrà o non avrà in esso. In questo caso, generare un certo numero di parametri in una stringa al livello di programmazione prima di chiamare eval() su di esso non pone alcun rischio per la sicurezza di cui sono a conoscenza.

Se questo è tutto il tuo codice, puoi semplicemente creare una nuova proprietà sulla funzione .dynLength che è modificabile e impostarla su quello che vuoi e utilizzare il tuo codice.

+0

Grazie per questo, tuttavia il problema * reale * di usare 'Funzione' o' eval' è lo scopo in cui viene eseguito. Non è equivalente alla scrittura di quel codice nell'ambito corrente, che non funziona nel mio caso. – nickf

+1

@nickf - qual è il vero problema che stai cercando di risolvere? Perché hai bisogno di creare dinamicamente una funzione con un particolare valore '.length'? – jfriend00

+0

è una libreria che avvolge funzioni arbitrarie per fare cose come lo stubbing e lo spionaggio (es .: contare il numero di chiamate, memorizzare gli argomenti, ecc.). Dal momento che il codice che lo usa * potrebbe * verificare la proprietà della lunghezza della funzione, mi piacerebbe assicurarmi che rimanga lo stesso dopo essere stato spostato. – nickf

3

sto facendo qualcosa di simile a quello che stai chiedendo di utilizzare o meno la seguente:

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

Questo fa utilizzare un costruttore di funzione, ma in modo strettamente limitata e sempre e solo una volta per ogni dimensione funzione per creare un wrapper di funzione (che viene memorizzato nella cache per quella dimensione) dopo di che uso wrapper.bind (real_function) per fornire l'implementazione come espressione/oggetto della funzione.

I vantaggi sono che qualsiasi dimensione è supportata e non stiamo codificando le definizioni di funzione, ma le implementazioni di funzioni reali non vengono mai eseguite in un modo simile a "eval" e la stringa passata al costruttore Function è sempre la stessa.

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

Sono sicuro che ci sono un sacco di ragioni per cui questo è male, troppo (iniziando con l'utilizzo di funzioni significato bind creati in questo modo non può essere vincolato ad un oggetto), ma è utile in situazioni in cui uno deve essere in grado di creare dinamicamente una funzione di lunghezza arbitraria.

1

C'è un modulo npm util-arity che fa quello che si vuole:

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

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

Si scopre la proprietà length sulle funzioni è configurable, che significa che è possibile utilizzare .defineProperty per modificare il valore di lunghezza su una funzione. Esempio:

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

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

Questo funziona nella sua ultima versione di Chrome e Firefox, ma non funziona in Safari (v9.1.1).

+0

Questo funziona anche nel moderno V8 (ho testato con il nodo 6.x). Grazie! – doublerebel

Problemi correlati