我想测试一个 JavaScript 对象是否是 Proxy。琐碎的方法
if (obj instanceof Proxy) ...
在这里不起作用,遍历 Proxy.prototype
的原型链也不起作用,因为所有相关操作都得到了底层目标的有效支持。
是否可以测试任意对象是否是代理?
在我当前的项目中,我还需要一种方法来定义某些东西是否已经是代理,主要是因为我不想在代理上启动代理。为此,我只是在我的处理程序中添加了一个 getter,如果请求的变量是“__Proxy”,它将返回 true:
function _observe(obj) { if (obj.__isProxy === undefined) { var ret = new Proxy(obj || {}, { set: (target, key, value) => { /// 改变返回true; }, get: (target, key) => { if (key !== "__isProxy") { return target[key]; } return true; } });返回 ret; } 返回对象; }
可能不是最好的解决方案,但我认为这是一个优雅的解决方案,在序列化时也不会弹出。
在 Node.js 10 中,您可以使用 util.types.isProxy
。
例如:
const target = {};
const proxy = new Proxy(target, {});
util.types.isProxy(target); // Returns false
util.types.isProxy(proxy); // Returns true
创建一个新符号:
let isProxy = Symbol("isProxy")
在您的代理处理程序的 get
方法中,您可以检查 key
是否是您的符号,然后是 return true
:
get(target, key)
{
if (key === isProxy)
return true;
// normal get handler code here
}
然后,您可以使用以下代码检查对象是否是您的代理之一:
if (myObject[isProxy]) ...
从 http://www.2ality.com/2014/12/es6-proxies.html:
无法确定对象是否为代理(透明虚拟化)。
为 instanceof 代理添加“支持”:
我不推荐它,但如果您想添加对 instanceof
的支持,您可以在实例化任何代理之前执行以下操作:
(() => { var proxyInstances = new WeakSet() // 可选择将原始文件保存在全局范围内: originalProxy = Proxy Proxy = new Proxy(Proxy, {Construct(target, args) { var newProxy = new originalProxy(... args) proxyInstances.add(newProxy) return newProxy }, get(obj, prop) { if (prop == Symbol.hasInstance) { return (instance) => { return proxyInstances.has(instance) } } return Reflect.get( ...arguments) } }) })() // Demo: var a = new Proxy({}, {}) console.log(a instanceof Proxy) // true delete a var a = new originalProxy({}, {}) console.log(a instanceof Proxy) // false delete a
事实上,有一种解决方法可以确定对象是否是代理,它基于几个假设。首先,当页面可以启动不安全的扩展时,可以通过 C++ 扩展或浏览器中的特权网页轻松解决 node.js
环境的代理确定问题。其次,代理是相对较新的功能,因此它在旧浏览器中不存在 - 因此解决方案仅适用于现代浏览器。
JS 引擎无法克隆函数(因为它们绑定到激活上下文和其他一些原因),但根据定义,代理对象由包装处理程序组成。因此,要确定对象是否为代理,启动强制对象克隆就足够了。可以通过 postMessage 函数完成。
如果对象是代理,即使它不包含任何功能,它也将无法复制。例如,Edge 和 Chrome 在尝试发布代理对象时会产生以下错误:[object DOMException]: {code: 25, message: "DataCloneError", name: "DataCloneError"}
和 Failed to execute 'postMessage' on 'Window': [object Object] could not be cloned.
。
dispatchEvent
)并且代理的 DOM 元素不能附加到 DOM。也可能有其他独特的(如音频上下文节点)。
使用 window.postMessage() 和 try-catch 来获得提示
postMessage 不能序列化与 structured clone algorithm 不兼容的对象,例如 Proxy。
function shouldBeCloneable(o) {
const type = typeof o;
return (
o?.constructor === ({}).constructor ||
type === "undefined" ||
o === null ||
type === "boolean" ||
type === "number" ||
type === "string" ||
o instanceof Date ||
o instanceof RegExp ||
o instanceof Blob ||
o instanceof File ||
o instanceof FileList ||
o instanceof ArrayBuffer ||
o instanceof ImageData ||
o instanceof ImageBitmap ||
o instanceof Array ||
o instanceof Map ||
o instanceof Set
);
}
function isCloneable(obj) {
try {
postMessage(obj, "*");
} catch (error) {
if (error?.code === 25) return false; // DATA_CLONE_ERR
}
return true;
}
function isProxy(obj){
const _shouldBeCloneable = shouldBeCloneable(obj);
const _isCloneable = isCloneable(obj);
if(_isCloneable) return false;
if(!_shouldBeCloneable) return "maybe";
return _shouldBeCloneable && !_isCloneable;
}
console.log("proxied {}", isProxy(new Proxy({},{})));
console.log("{}", isProxy({}));
console.log("proxied []", isProxy(new Proxy([],{})));
console.log("[]", isProxy([]));
console.log("proxied function", isProxy(new Proxy(()=>{},{})));
console.log("function", isProxy(()=>{}));
console.log("proxied Map", isProxy(new Proxy(new Map(),{})));
console.log("new Map()", isProxy(new Map()));
class A{};
console.log("proxied class", isProxy(new Proxy(A,{})));
console.log("class", isProxy(A));
console.log("proxied class instance", isProxy(new Proxy(new A(),{})));
console.log("class instance", isProxy(new A()));
isProxy({a:()=>{}})
。它应该被称为“isNotCloneable”。
jsdom
或 jest
,因为它们具有假 postMessage()
功能
shouldBeCloneable({})
为假是否有原因?在我看来,您应该检查 o?.constructor === ({}).constructor
...
我发现最好的方法是创建一组弱代理对象。您可以在构建和检查代理对象时递归地执行此操作。
var myProxySet = new WeakSet();
var myObj = new Proxy({},myValidator);
myProxySet.add(myObj);
if(myProxySet.has(myObj)) {
// Working with a proxy object.
}
Matthew Brichacek 和 David Callanan 为您自己创建的 Proxy 提供了很好的答案,但如果不是这样,这里有一些补充
想象一下,您有一个无法修改的外部函数创建代理
const external_script = ()=>{
return new Proxy({a:5},{})
}
在执行任何外部代码之前,我们可以重新定义代理构造函数并使用 WeakSet 来存储代理,就像 Matthew Brichacek 所做的那样。我不使用类,因为否则 Proxy 将有一个原型,并且可以检测到 Proxy 已更改。
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
相同的方法,但使用像 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
我认为第一个更好,因为您只更改构造函数,而第二个创建代理的代理,而问题的目的是避免这种情况。
如果代理是在 iframe 中创建的,它就不起作用,因为我们只是为当前帧重新定义了代理。
似乎没有标准的方法,但是对于 Firefox 特权代码,您可以使用
Components.utils.isProxy(object);
例如:
Components.utils.isProxy([]); // false
Components.utils.isProxy(new Proxy([], {})); // true
根据 JS 语言规范,无法检测某物是否为 Proxy
。
node 确实通过本机代码提供了一种机制,但我不建议使用它 - 你不应该知道某物是否是 Proxy
。
其他建议包装或遮蔽全局 Proxy
的答案实际上不会跨领域工作(即 iframe、网络工作者、节点的 vm 模块、wasm 等)。
有两种方法可以代理对象。一个是 new Proxy
,另一个是 Proxy.revocable
。我们可以监视它们,以便将代理对象记录到秘密列表中。然后我们通过检查它是否存在于秘密列表中来确定一个对象是代理对象。
要监视函数,我们可以编写包装器或使用内置代理。后者表示使用 Proxy 来代理 new Proxy
以及 Proxy.recovable
,这里有一个 fiddle 来演示这个想法。
为了像 nodejs-v5.8.0 代理一样为 old Proxy API 提供服务,我们可以通过使用 Proxy.createFunction
代理 Proxy.create
和 Proxy.createFunction
来应用相同的想法。
vm
模块)中创建的代理,因为您无法监视其他领域中的全局变量(如 Proxy
)。
Proxy
对象时没有问题。我没有用 vm 模块测试它,但不明白为什么它不应该以同样的方式工作。
我相信我找到了一种更安全的方法来检查该项目是否为代理。此答案的灵感来自 Xabre's answer。
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
});
}
基本上我所做的是,我没有将 __isProxy
字段添加到目标,而是在代理的 getter 中添加了这个检查:if (property === Symbol.for("__isProxy")) return true;
。这样,如果您使用 for-in 循环或 Object.keys
或 Object.hasOwnProperty
,__isProxy 将不存在。
不幸的是,即使您可以设置 __isProxy
的值,由于对 getter 的检查,您将永远无法检索它。因此,您应该在设置字段时抛出错误。
您还可以使用 Symbol
来检查变量是否是代理,如果您认为它可能想要使用 __isProxy
作为不同的属性。
最后,我还为代理的目标添加了类似的功能,这也很难检索。
不定期副业成功案例分享
Symbol("is-proxy") !== Symbol("is-proxy")
,或者您使用Symbol.for