Je voudrais tester si un objet JavaScript est un Proxy . L'approche triviale
if (obj instanceof Proxy) ...
ne fonctionne pas ici, ni parcourir la chaîne de prototypes pour Proxy.prototype
, car toutes les opérations pertinentes sont effectivement gérées par la cible sous-jacente.
Est-il possible de tester si un objet arbitraire est un proxy?
Dans mon projet actuel, j'avais également besoin d'un moyen de définir si quelque chose était déjà un proxy, principalement parce que je ne voulais pas démarrer un proxy sur un proxy. Pour cela, j'ai simplement ajouté un getter à mon gestionnaire, qui renverrait true si la variable demandée était "__Proxy":
function _observe(obj) {
if (obj.__isProxy === undefined) {
var ret = new Proxy(obj || {}, {
set: (target, key, value) => {
/// act on the change
return true;
},
get: (target, key) => {
if (key !== "__isProxy") {
return target[key];
}
return true;
}
});
return ret;
}
return obj;
}
Ce n'est peut-être pas la meilleure solution, mais je pense que c'est une solution élégante, qui n'apparaît pas non plus lors de la sérialisation.
De http://www.2ality.com/2014/12/es6-proxies.html :
Il est impossible de déterminer si un objet est un proxy ou non (virtualisation transparente).
Dans Node.js 10, vous pouvez utiliser util.types.isProxy
.
Par exemple:
const target = {};
const proxy = new Proxy(target, {});
util.types.isProxy(target); // Returns false
util.types.isProxy(proxy); // Returns true
Créer un nouveau symbole:
let isProxy = Symbol("isProxy")
Dans la méthode get
de votre gestionnaire de proxy, vous pouvez vérifier si la key
est votre symbole, puis return true
:
get(target, key)
{
if (key === isProxy)
return true;
// normal get handler code here
}
Vous pouvez ensuite vérifier si un objet est l'un de vos mandataires en utilisant le code suivant:
if (myObject[isProxy]) ...
En fait, il existe une solution de contournement pour déterminer si l'objet est un proxy, qui repose sur plusieurs hypothèses. Premièrement, la détermination du proxy peut être facilement résolue pour l'environnement node.js
via des extensions C++ ou une page Web à privilèges dans le navigateur, lorsque cette page peut lancer des extensions non sécurisées. Deuxièmement, le proxy est une nouvelle fonctionnalité relative, il n’existe donc pas dans les anciens navigateurs. La solution ne fonctionne donc que dans les navigateurs modernes.
Le moteur JS ne peut pas cloner de fonctions (car ils ont des liaisons avec le contexte d'activation et d'autres raisons), mais l'objet Proxy, par définition, est constitué de gestionnaires d'encapsuleurs. Donc, pour déterminer si l'objet est un proxy, il suffit d'initier le clonage forcé. Dans peut être fait via postMessage function.
Si objet est un proxy, il ne pourra pas être copié même s'il ne contient aucune fonction. Par exemple, Edge et Chrome génèrent les erreurs suivantes lors de la tentative de publication d'un objet proxy: [object DOMException]: {code: 25, message: "DataCloneError", name: "DataCloneError"}
et Failed to execute 'postMessage' on 'Window': [object Object] could not be cloned.
.
La meilleure méthode que j'ai trouvée consiste à créer un ensemble faible d'objets proxy. Vous pouvez le faire de manière récursive lorsque vous construisez et vérifiez vos objets soumis au proxy.
var myProxySet = new WeakSet();
var myObj = new Proxy({},myValidator);
myProxySet.add(myObj);
if(myProxySet.has(myObj)) {
// Working with a proxy object.
}
Il semble qu'il n'y ait pas de moyen standard, mais pour le code privilégié Firefox, vous pouvez utiliser
Components.utils.isProxy(object);
Par exemple:
Components.utils.isProxy([]); // false
Components.utils.isProxy(new Proxy([], {})); // true
Matthew Brichacek et David Callanan donnent de bonnes réponses pour Proxy que vous créez vous-même, mais si ce n'est pas le cas, voici quelques ajouts.
Imaginez que vous ayez une fonction externe créant un proxy que vous ne pouvez pas modifier
const external_script = ()=>{
return new Proxy({a:5},{})
}
Avant toute exécution de code externe, nous pouvons redéfinir le constructeur de proxy et utiliser un WeakSet pour stocker le proxy comme le fait Matthew Brichacek . Je n'utilise pas de classe car sinon Proxy aura un prototype et il sera détectable que Proxy ait été modifié.
const proxy_set = new WeakSet()
window.Proxy = new Proxy(Proxy,{
construct(target, args) {
const proxy = new target(...args)
proxy_set.add(proxy)
return proxy
}
})
const a = external_script()
console.log(proxy_set.has(a)) //true
Même méthode mais avec Symbol comme David Callanan
const is_proxy = Symbol('is_proxy')
const old_Proxy = Proxy
const handler = {
has (target, key) {
return (is_proxy === key) || (key in target)
}
}
window.Proxy = new Proxy(Proxy,{
construct(target, args) {
return new old_Proxy(new target(...args), handler)
}
})
const a = external_script()
console.log(is_proxy in a) //true
Je pense que le premier est préférable car vous ne changez que le constructeur, tandis que le second crée un proxy d'un proxy alors que le but de la question était d'éviter cela.
Cela ne fonctionne pas si le proxy est créé dans une iframe, car nous avons uniquement redéfini le proxy pour le cadre actuel.
Je pense avoir trouvé un moyen plus sûr de vérifier si l'élément est un proxy. Cette réponse a été inspirée par la réponse de Xabre .
function getProxy(target, property) {
if (property === Symbol.for("__isProxy")) return true;
if (property === Symbol.for("__target")) return target;
return target[property];
}
function setProxy(target, property, value) {
if (property === Symbol.for("__isProxy")) throw new Error("You cannot set the value of '__isProxy'");
if (property === Symbol.for("__target")) throw new Error("You cannot set the value of '__target'");
if (target[property !== value]) target[property] = value;
return true;
}
function isProxy(proxy) {
return proxy == null ? false : !!proxy[Symbol.for("__isProxy")];
}
function getTarget(proxy) {
return isProxy(proxy) ? proxy[Symbol.for("__target")] : proxy;
}
function updateProxy(values, property) {
values[property] = new Proxy(getTarget(values[property]), {
set: setProxy,
get: getProxy
});
}
En gros, au lieu d’ajouter le champ __isProxy
à la cible, j’ai ajouté le contrôle suivant: if (property === Symbol.for("__isProxy")) return true;
dans le getter du proxy. De cette façon, si vous utilisez une boucle for-in ou Object.keys
ou Object.hasOwnProperty
, __isProxy n'existera pas.
Malheureusement, même si vous pouvez définir la valeur __isProxy
, vous ne pourrez jamais la récupérer, à cause du contrôle effectué sur le getter. Par conséquent, vous devez générer une erreur lorsque le champ est défini.
Vous pouvez également utiliser une variable Symbol
pour vérifier si une variable est un proxy, si vous pensez que vous souhaitez probablement utiliser __isProxy
en tant que propriété différente.
Enfin, j'ai également ajouté une fonctionnalité similaire pour la cible du proxy, qui peut également être aussi difficile à récupérer.