2016-07-14 45 views
6

Ho una classe che implementa l'interfaccia XMLHttpRequest. A seconda dell'URL passato a open(), posso determinare se utilizzare l'XMLHttpRequest predefinito o la mia implementazione personalizzata. La mia idea è quella di utilizzare un proxy per fare questo:È possibile cambiare la destinazione di un Proxy?

let xhr = new XHRProxy(); 
xhr.open('GET', 'http://blah'); // Decide here depending on URL 

Ho fatto alcuni test utilizzando l'ES6 Proxy, che sembra promettente, ma purtroppo l'obiettivo proxy non può essere modificato dopo la costruzione del Proxy:

var foo = { 
    name() { 
     return "foo"; 
    } 
}; 
var bar = { 
    name() { 
     return "bar"; 
    } 
} 
var handler = { 
    get(target, property, receiver) { 
     if (property === "switchToBar") { 
      // FIXME: This doesn't work because a Proxy's target is not exposed AFAIK 
      receiver.target = bar; 
      return function() {}; 
     } else { 
      return target[property]; 
     } 
    } 
} 
var proxy = new Proxy(foo, handler); 
console.log(proxy.name()); // foo 
proxy.switchToBar(); 
console.log(proxy.name()); // foo :(

Penso di poter realizzare ciò che voglio non impostando un obiettivo - invece di definire tutte le trap da delegare all'oggetto desiderato - ma spero in una soluzione più semplice.

+0

Si noti che si sta tentando di modificare il target già con la proprietà '.switchToBar', non sul metodo' .switchToBar() ' – Bergi

+0

Sì, ma non penso che sia importante per questo esempio. E per usarlo con XHR.open, in realtà voglio impostare il delegato prima di chiamare l'implementazione XHR.open. –

risposta

6

Ecco un andare a "definire tutte le trappole di delegare ad oggetto desiderato"

(function() { 
    let mutableTarget; 
    let mutableHandler; 

    function setTarget(target) { 
    if (!(target instanceof Object)) { 
     throw new Error(`Target "${target}" is not an object`); 
    } 
    mutableTarget = target; 
    } 

    function setHandler(handler) { 
    Object.keys(handler).forEach(key => { 
     const value = handler[key]; 

     if (typeof value !== 'function') { 
     throw new Error(`Trap "${key}: ${value}" is not a function`); 
     } 

     if (!Reflect[key]) { 
     throw new Error(`Trap "${key}: ${value}" is not a valid trap`); 
     } 
    }); 
    mutableHandler = handler; 
    } 

    function mutableProxyFactory() { 
    setTarget(() => {}); 
    setHandler(Reflect); 

    // Dynamically forward all the traps to the associated methods on the mutable handler 
    const handler = new Proxy({}, { 
     get(target, property) { 
     return (...args) => mutableHandler[property].apply(null, [mutableTarget, ...args.slice(1)]); 
     } 
    }); 

    return { 
     setTarget, 
     setHandler, 
     getTarget() { 
     return mutableTarget; 
     }, 
     getHandler() { 
     return mutableHandler; 
     }, 
     proxy: new Proxy(mutableTarget, handler) 
    }; 
    } 

    window.mutableProxyFactory = mutableProxyFactory; 
})(); 

const { 
    proxy, 
    setTarget 
} = mutableProxyFactory(); 

setTarget(() => 0); 
console.log(`returns: ${proxy()}`); 

setTarget({ val: 1 }); 
console.log(`val is: ${proxy.val}`); 

setTarget({ val: 2 }); 
console.log(`val is: ${proxy.val}`); 

setTarget(() => 3); 
console.log(`returns: ${proxy()}`); 

Mi sento come se ci deve essere qualche ragione questo non è supportato dalla scatola, ma io non ho abbastanza informazioni per commentare ulteriormente.

Dopo aver eseguito l'hacking su questo per un po ', ho osservato alcune cose. Sembra che il target originale con il quale viene chiamato il costruttore proxy sia trattato come parte dell'identità del proxy a prescindere. Impostando il target originale su un oggetto semplice e scambiando il target con una funzione in seguito si genera un errore, quando viene chiamato il proxy. Ci sono sicuramente alcuni spigoli a questo, quindi usare con cautela.

+0

Oh molto intelligente! Bella idea di utilizzare un proxy per il gestore, che consente di modificare il target (e il gestore), con un codice molto minimale. Hai ragione che ci sono alcuni problemi con questo tipo di hack. Mi sono imbattuto in alcuni test sulla mia implementazione (meno elegante). Ad esempio, 'Object.preventExtensions (proxy);' genererà un errore perché Proxy esegue alcune convalide utilizzando il target con cui è stato costruito anziché il target mutabile. Penso che non avere setter per obiettivi/gestori sia una svista, e anche se ci sono buone ragioni, è ancora triste. Questo funziona perlopiù. Grazie! –

2

È possibile modificare il target di un Proxy?

No, questo non è possibile. Il gestore proxy è già un'interfaccia abbastanza generica e, definendo tutte le trap per inoltrare l'operazione a un gestore diverso, ciò è facilmente ottenibile. Ecco perché non esiste un metodo aggiuntivo per cambiare il target, l'interfaccia è minimizzata. Non rendendo modificabile il bersaglio, viene preservata anche la forma del proxy (ad esempio se è callable o un array).

1

Che ne dici di questo? Invece di fare direttamente foo o bar, usiamo una scatola per il nostro obiettivo e mettiamo foo o bar nella scatola.

var foo = { 
    name() { 
     return "foo"; 
    } 
}; 
var bar = { 
    name() { 
     return "bar"; 
    } 
}; 
var handler = { 
    get(target, property, receiver) { 
     if (property === "switchToBar") { 
      target.content = bar; 
      return function() {}; 
     } else { 
      return target.content[property]; 
     } 
    } 
}; 

var box = {content: foo}; 
var proxy = new Proxy(box, handler); 

console.log(proxy.name()); // foo 
// Switch over to bar by calling the function 
proxy.switchToBar(); 
// Or, we could do the switch from out here 
box.content = bar; 
// Either way, we get the same result 
console.log(proxy.name()); // bar 

In questo caso, la nostra scatola è un oggetto con la proprietà content. In alternativa, è possibile utilizzare un array con un elemento nell'indice 0.

Problemi correlati