2011-09-06 9 views
6

Consideriamo Ho il seguente codicefunzioni confezionamento e function.length

/*...*/ 
var _fun = fun; 
fun = function() { 
    /*...*/ 
    _fun.apply(this, arguments); 
} 

ho appena perso i dati sulla .length_fun perché ho cercato di avvolgerlo con una logica di intercettazione.

La seguente non funziona

var f = function(a,b) { }; 
console.log(f.length); // 2 
f.length = 4; 
console.log(f.length); // 2 

Il annotated ES5.1 specification states che .length è definito come segue

Object.defineProperty(fun, "length", { 
    value: /*...*/, 
    writable: false, 
    configurable: false, 
    enumerable: false 
} 

Dato che la logica interna fun richiede .length per essere precisi, come posso intercettare e sovrascrivere questa funzione senza distruggere i dati .length?

Ho la sensazione che avrò bisogno di usare eval e il dodgy Function.prototype.toString per costruire una nuova stringa con lo stesso numero di argomenti. Voglio evitare questo.

+0

Hai davvero bisogno che la lunghezza sia impostata correttamente? Si consiglia di dare un'occhiata a https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind, anche molte librerie simulano questo comportamento. – Prusse

+0

@Prusse come si risolve il problema? – Raynos

+0

solo un suggerimento, non può capire chiaramente il tuo obiettivo, mi dispiace. – Prusse

risposta

3

So che preferiresti un altro modo, ma tutto quello che posso pensare è di hackerare insieme qualcosa con il costruttore Function. Disordinato, per non dire altro, ma sembra funzionare:

var replaceFn = (function(){ 
    var args = 'abcdefghijklmnopqrstuvwxyz'.split(''); 
    return function replaceFn(oldFn, newFn) { 
     var argSig = args.slice(0, oldFn.length).join(','); 
     return Function(
      'argSig, newFn', 
      'return function(' 
       + argSig + 
      '){return newFn.apply(this, arguments)}' 
     )(argSig, newFn); 
    }; 
}()); 

// Usage: 
var _fun = fun; 

fun = replaceFn(fun, function() { 
    /* ... */ 
    _fun.apply(this, arguments); 
}); 
+0

disordinato, ma comunque bello :) –

2

lunghezza Faking correttamente e coerentemente è l'ultima frontiera in javascript e che è praticamente l'inizio e la fine di esso. In una lingua in cui puoi simulare tutto, la lunghezza è ancora un po 'magica. ES6 fornirà e possiamo simularlo ora in misura maggiore e minore a seconda del motore e della versione in cui ti trovi. Per la compatibilità Web generale è un modo diverso. Proxy/noSuchMethod è in Mozilla da un po 'di tempo. Proxy e WeakMaps sono diventati utilizzabili in V8 in Chromium e nel nodo (che richiede l'attivazione di flag) che forniscono lo strumento necessario per simulare correttamente la lunghezza.

In dettaglio sulla "lunghezza": http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

La soluzione finale: http://wiki.ecmascript.org/doku.php?id=harmony:proxies + http://wiki.ecmascript.org/doku.php?id=harmony:weak_maps

+0

Puoi spiegare come il proxy può aiutare a simulare la lunghezza. E come potrebbero aiutare le mappe deboli? Inoltre, il riferimento per "lunghezza" si riferisce agli array non alle funzioni – Raynos

+0

Con un proxy è possibile creare un getter di ricerca per gli indici e replicare tutto il comportamento richiesto in modo che corrisponda a quanto fa la proprietà della lunghezza nativa. WeakMaps sono ciò che utilizzeresti per fornire accessor per le proprietà inesistenti che il proxy ostacola prima che vengano eseguiti i controlli di tipo/non definito. Quando qualcosa per accedere al getter per 401042 sul tuo array di falsi, devi prima definire quella proprietà usando WeakMap e quindi modificare il valore della tua proprietà di lunghezza falsa. Tutte le proprietà di lunghezza nativa sono puntatori interni a una struttura di dati effettiva al di fuori del dominio di JavaScripts e quindi possono mostrare un comportamento "magico". –

+0

Cambiare la lunghezza delle funzioni è apparentemente meno utile degli array, ma è ancora magicamente imbevuto, e in questo caso si comporta semplicemente in modo imprevedibile tra i browser. Mozilla cambia le dimensioni e apparentemente nient'altro che ho visto. V8 restituisce il numero specificato quando lo si imposta, ma in realtà non modifica il valore e restituirà il numero effettivo di parametri, indipendentemente da ciò che si fa. La lunghezza dovrebbe essere una mappatura diretta alla memoria che trattiene qualunque cosa rappresenti. –

2

uso la seguente funzione per questo scopo; è molto veloce per funzioni con un numero ragionevole di parametri, più flessibile della risposta accettata e funziona per funzioni con più di 26 parametri.

function fakeFunctionLength(fn, length) { 
    var fns = [ 
     function() { return fn.apply(this, arguments); }, 
     function (a) { return fn.apply(this, arguments); }, 
     function (a, b) { return fn.apply(this, arguments); }, 
     function (a, b, c) { return fn.apply(this, arguments); }, 
     function (a, b, c, d) { return fn.apply(this, arguments); }, 
     function (a, b, c, d, e) { return fn.apply(this, arguments); }, 
     function (a, b, c, d, e, f) { return fn.apply(this, arguments); } 
    ], argstring; 

    if (length < fns.length) { 
     return fns[length]; 
    } 

    argstring = ''; 
    while (--length) { 
     argstring += ',_' + length; 
    } 
    return new Function('fn', 
     'return function (_' + argstring + ') {' + 
      'return fn.apply(this, arguments);' + 
     '};')(fn); 
} 
0

Hai solo bisogno di imboccare la strada eval/Function se è necessario supportare le funzioni con qualsiasi numero di parametri. Se è possibile impostare un limite superiore ragionevole (il mio esempio è 5) quindi è possibile effettuare le seguenti operazioni:

var wrapFunction = function(func, code, where){ 
    var f; 
    switch (where) { 
    case 'after': 
     f = function(t,a,r){ r = func.apply(t,a); code.apply(t,a); return r; } 
    break; 
    case 'around': 
     f = function(t,a){ return code.call(t,func,a); } 
    break; 
    default: 
    case 'before': 
     f = function(t,a){ code.apply(t,a); return func.apply(t,a); } 
    break; 
    } 
    switch (func.length) { 
    case 0: return function(){return f(this, arguments);}; break; 
    case 1: return function(a){return f(this, arguments);}; break; 
    case 2: return function(a,b){return f(this, arguments);}; break; 
    case 3: return function(a,b,c){return f(this, arguments);}; break; 
    case 4: return function(a,b,c,d){return f(this, arguments);}; break; 
    case 5: return function(a,b,c,d,e){return f(this, arguments);}; break; 
    default: 
     console.warn('Too many arguments to wrap successfully.'); 
    break; 
    } 
} 

Questo metodo di confezionamento di codice è anche estendibile con la creazione di diversi where interruttori. Ho implementato before e after perché sono i più utili per il mio progetto — e around solo perché mi ricorda il lisp. Utilizzando questo set è possibile anche consegnare il wrapping (var f) a un codice esterno che consente di sviluppare un sistema simile a un plugin per le parole chiave where, il che significa che è possibile estendere o sovrascrivere facilmente ciò che è supportato da wrapFunction.

Ovviamente è possibile modificare il modo in cui il codice viene effettivamente incapsulato, ma la chiave è in realtà solo utilizzando una tecnica simile a 999 e AdrianLang, senza preoccuparsi di costruire stringhe e passare a new Function.