2012-02-01 15 views
6

Prima di essere sgridato per aver provato qualcosa di così spericolato, lascia che ti dica che non lo farei nella vita reale ed è una questione accademica.Ripristino da un errore

Supponiamo che sto scrivendo una libreria e voglio che il mio oggetto sia in grado di inventare i metodi necessari.

Per esempio, se si voleva chiamare un metodo .slice(), e non ho avuto uno poi il gestore window.onerror avrebbe sparato per me

Comunque ho giocato in giro con questo here

window.onerror = function(e) { 
    var method = /'(.*)'$/.exec(e)[1]; 
    console.log(method); // slice 
    return Array.prototype[method].call(this, arguments); // not even almost gonna work 
}; 

var myLib = function(a, b, c) { 
    if (this == window) return new myLib(a, b, c); 
    this[1] = a; this[2] = b; this[3] = c; 
    return this; 
}; 

var obj = myLib(1,2,3); 

console.log(obj.slice(1)); 

anche (forse dovrei iniziare una nuova domanda) posso cambiare il mio costruttore per prendere una quantità non specificata di argomenti?

var myLib = function(a, b, c) { 
    if (this == window) return new myLib.apply(/* what goes here? */, arguments); 
    this[1] = a; this[2] = b; this[3] = c; 
    return this; 
}; 

BTW So che posso caricare i miei oggetti con

['slice', 'push', '...'].forEach(function() { myLib.prototype[this] = [][this]; }); 

che non è quello che sto cercando

+0

suona come stai pensando di javascript [ "pollyfills" o "shim"] (http://remysharp.com/2010/10/08/what-is-a-polyfill/) –

+1

per inciso di solito è più elegante e affidabile catturare le eccezioni con un blocco try-catch. –

risposta

4

Come stavi chiedendo una questione accademica, suppongo compatibilità del browser non è un problema. Se davvero non lo è, mi piacerebbe introdurre i proxy dell'armonia per questo. onerror non è una buona pratica in quanto è solo un evento sollevato se da qualche parte si verifica un errore. Dovrebbe, se mai, essere utilizzato solo come ultima risorsa. (So ​​che hai detto che non lo usi comunque, ma lo onerror non è molto adatto per gli sviluppatori.)

In pratica, i proxy consentono di intercettare la maggior parte delle operazioni fondamentali in JavaScript, in particolare ottenendo qualsiasi proprietà che sia utile qui. In questo caso, è possibile intercettare la procedura per ottenere .slice.

Nota che i proxy sono "buchi neri" per impostazione predefinita. Non corrispondono a nessun oggetto (ad esempio, l'impostazione di una proprietà su un proxy chiama semplicemente la trappola set (interceptor), la memorizzazione effettiva che devi fare tu stesso). Ma esiste un "forwarding handler" che indirizza tutto attraverso un oggetto normale (o un'istanza ovviamente), in modo che il proxy si comporti come un oggetto normale. Estendendo il gestore (in questo caso, la parte get), è possibile instradare facilmente i metodi Array.prototype nel modo seguente.

Così, ogni volta qualsiasi proprietà (con il nome name) è di essere recuperato, il percorso di codice è il seguente:

  1. Prova ritorno inst[name].
  2. Altrimenti, provare a restituire una funzione che applica Array.prototype[name] all'istanza con gli argomenti specificati a questa funzione.
  3. In caso contrario, è sufficiente restituire undefined.

Se si vuole giocare con i proxy, è possibile utilizzare una versione recente di V8, ad esempio in una nightly build di Chromium (assicurarsi per l'esecuzione come chrome --js-flags="--harmony"). Di nuovo, i proxy non sono disponibili per l'utilizzo "normale" perché sono relativamente nuovi, cambiano molte parti fondamentali di JavaScript e in realtà non sono ancora stati specificati ufficialmente (ancora bozze).

Questo è un semplice diagramma di come va (inst è in realtà il proxy in cui è stata inserita l'istanza). Si noti che illustra solo ottenendo una proprietà; tutte le altre operazioni vengono semplicemente passate dal proxy a causa del gestore di inoltro non modificato.

proxy diagram

codice La delega potrebbe essere la seguente:

function Test(a, b, c) { 
    this[0] = a; 
    this[1] = b; 
    this[2] = c; 

    this.length = 3; // needed for .slice to work 
} 

Test.prototype.foo = "bar"; 

Test = (function(old) { // replace function with another function 
         // that returns an interceptor proxy instead 
         // of the actual instance 
    return function() { 
    var bind = Function.prototype.bind, 
     slice = Array.prototype.slice, 

     args = slice.call(arguments), 

     // to pass all arguments along with a new call: 
     inst = new(bind.apply(old, [null].concat(args))), 
     //      ^is ignored because of `new` 
     //       which forces `this` 

     handler = new Proxy.Handler(inst); // create a forwarding handler 
              // for the instance 

    handler.get = function(receiver, name) { // overwrite `get` handler 
     if(name in inst) { // just return a property on the instance 
     return inst[name]; 
     } 

     if(name in Array.prototype) { // otherwise try returning a function 
            // that calls the appropriate method 
            // on the instance 
     return function() { 
      return Array.prototype[name].apply(inst, arguments); 
     }; 
     } 
    }; 

    return Proxy.create(handler, Test.prototype); 
    }; 
})(Test); 

var test = new Test(123, 456, 789), 
    sliced = test.slice(1); 

console.log(sliced);    // [456, 789] 
console.log("2" in test);   // true 
console.log("2" in sliced);  // false 
console.log(test instanceof Test); // true 
            // (due to second argument to Proxy.create) 
console.log(test.foo);    // "bar" 

Il gestore di inoltro è disponibile presso the official harmony wiki.

Proxy.Handler = function(target) { 
    this.target = target; 
}; 

Proxy.Handler.prototype = { 
    // Object.getOwnPropertyDescriptor(proxy, name) -> pd | undefined 
    getOwnPropertyDescriptor: function(name) { 
    var desc = Object.getOwnPropertyDescriptor(this.target, name); 
    if (desc !== undefined) { desc.configurable = true; } 
    return desc; 
    }, 

    // Object.getPropertyDescriptor(proxy, name) -> pd | undefined 
    getPropertyDescriptor: function(name) { 
    var desc = Object.getPropertyDescriptor(this.target, name); 
    if (desc !== undefined) { desc.configurable = true; } 
    return desc; 
    }, 

    // Object.getOwnPropertyNames(proxy) -> [ string ] 
    getOwnPropertyNames: function() { 
    return Object.getOwnPropertyNames(this.target); 
    }, 

    // Object.getPropertyNames(proxy) -> [ string ] 
    getPropertyNames: function() { 
    return Object.getPropertyNames(this.target); 
    }, 

    // Object.defineProperty(proxy, name, pd) -> undefined 
    defineProperty: function(name, desc) { 
    return Object.defineProperty(this.target, name, desc); 
    }, 

    // delete proxy[name] -> boolean 
    delete: function(name) { return delete this.target[name]; }, 

    // Object.{freeze|seal|preventExtensions}(proxy) -> proxy 
    fix: function() { 
    // As long as target is not frozen, the proxy won't allow itself to be fixed 
    if (!Object.isFrozen(this.target)) { 
     return undefined; 
    } 
    var props = {}; 
    Object.getOwnPropertyNames(this.target).forEach(function(name) { 
     props[name] = Object.getOwnPropertyDescriptor(this.target, name); 
    }.bind(this)); 
    return props; 
    }, 

    // == derived traps == 

    // name in proxy -> boolean 
    has: function(name) { return name in this.target; }, 

    // ({}).hasOwnProperty.call(proxy, name) -> boolean 
    hasOwn: function(name) { return ({}).hasOwnProperty.call(this.target, name); }, 

    // proxy[name] -> any 
    get: function(receiver, name) { return this.target[name]; }, 

    // proxy[name] = value 
    set: function(receiver, name, value) { 
    this.target[name] = value; 
    return true; 
    }, 

    // for (var name in proxy) { ... } 
    enumerate: function() { 
    var result = []; 
    for (var name in this.target) { result.push(name); }; 
    return result; 
    }, 

    // Object.keys(proxy) -> [ string ] 
    keys: function() { return Object.keys(this.target); } 
}; 
Problemi correlati