Rich Harris在twitter关于Proxy的讨论
前言
最近在学习Proxy,准备做个小东西,应该会在下一篇文章跟大家见面(先打波广告 :) )。在使用Proxy的过程中,遇到了Uncaught TypeError: Illegal invocation
报错。刚好搜到Rich Harris六月份在twitter发了一条关于Proxy的推特,仔细看了看评论,觉得有很多值得学习的地方,所以写这篇文章记录一下。
原文如下:
JS lazyweb: i need to make a Proxy of a URL object (for reasons that don't currently matter). i thought the whole point of proxies was that they're identical to the proxied object, minus whatever behaviour you've added, but that's not what i'm seeing. what am i doing wrong?
// attempt 1 url = new URL(location.href); proxy = new Proxy(url, {}); proxy.pathname; // Uncaught TypeError: Illegal invocation // attempt 2 url = new URL(location.href); proxy = new Proxy(url, { get: (target, property, receiver) => { return Reflect.get(target, property, target); } }); proxy.pathname; // works proxy.toString(); // Uncaught TypeError: Illegal invocation // attempt 3 url = new URL(location.href); proxy = new Proxy(url, { get: (target, property) => { return typeof target[property] === 'function' ? (...args) => target[property](...args) : target[property]; } }); proxy.pathname; // works proxy.toString(); // works proxy.toString === proxy.toString; // false
大概意思就是Rich Harris要创建一个URL对象的代理。他认为除去添加的行为外,代理的意义在于它们跟代理的对象相同,但是从attempt看,并非如此。所以是哪里出错了呢?
Illegal invocation
调用关键字this
而未引用其最初执行的对象时将引发“非法调用”。换句话说,就是原始的“上下文”丢失了。
结合Proxy来看,就是在代理的情况下,目标对象内部的this
关键字指向了Proxy代理对象,导致与目标对象的行为不一致。有些原生对象(Date
、Set
、Map
等)的内部属性,只有通过正确的this
才能拿到。
const target = {
foo() {
return {
isTarget: this === target,
isProxy: this === proxy
};
}
};
const proxy = new Proxy(target, {});
console.log(target.foo()); // { isTarget: true, isProxy: false }
console.log(proxy.foo()); // { isTarget: false, isProxy: true }
attempt 1
从上文可知attempt 1报错的问题所在,不再过多解释。
attempt 2
attempt 2通过Reflect.get(target, property, target)
解决了pathname
属性的获取报错问题。
Reflect.get(target, propertyKey[, receiver])
target
:需要取值的目标对象
propertyKey
:需要获取的值的键值
receiver
:如果target
对象中指定了getter
,receiver
则为getter
调用时的this
值。
而handler.get()
中的receiver
是Proxy或者继承Proxy的对象。
所以Reflect.get(target, property, target)
将this
重新指向为target
目标对象,就解决了属性的获取问题。
但是新的问题出现了,proxy.toString()
调用报错。而同时url.toString === proxy.toString
为true
。
别急,我们先看看attempt 3。
attempt 3
attempt 3是通过判断获取的属性是否为函数:是的话,返回一个箭头函数,箭头函数的返回值是target
对应的函数;否则,返回target
对应的属性。
proxy.toString
可以正常调用了,但是问题也显而易见,每次调用都会返回一个新的函数,所以proxy.toString === proxy.toString
始终为false
。
问题
所以Rich Harris真正关心的是在url.toString === proxy.toString
为true
的情况下,为什么proxy.toString
会调用失败?反而需要重新去绑定this
关键字的指向来让proxy.toString
正常执行,但不管通过(...args) => target[property](...args)
还是target[property].bind(target)
也好,都无法解决proxy.toString === proxy.toString
和url.toString === proxy.toString
为false
。
也许可以通过WeakMap
缓存或者在外部写具名函数来解决proxy.toString === proxy.toString
的问题,但是url.toString === proxy.toString
始终还是存在问题。
url.toString === proxy.toString
我觉得有两位的回复是解答了关于url.toString === proxy.toString
的问题。
Bradley Farias的回答大致意思是Proxy代理了对象,但是也破坏了类似于身份检查的执行,Keith Cirkel也表示url.toString
会确定调用对象是否为一个实际的URL对象,而不是其他类型的对象。
总结
最后Rich Harris打算使用Object.defineProperty
取代Proxy来实现他所设想的功能(拦截hash
属性来确保在SSR期间不应被访问)。
而Matt Robb提出如果代理的是原型链,而不是顶层对象的话,一切将正常运行。
如果大家知道Matt Robb提出的具体实现方法,麻烦在评论区告诉我一下,或者还有其他更好的方法,欢迎提出来讨论!期待大家的评论!
在回复当中,还有一些有趣的解决方案和方法,在这里摘抄记录一下:
WrappedURL
Andrew Courtice提出通过重新封装URL类,来实现类似于Proxy的效果。
class WrappedURL extends URL {
toString() {
return super.toString();
}
}
proxify
在回复中,Preet Shihn提出的proxify方法也是得到Rich Harris的点赞和认可。不仅解决了proxy.toString
的调用问题,还实现了嵌套对象的嵌套代理。
const proxify = (something, root) => new Proxy(something, {
get(target, prop, receiver) {
if ((typeof target[prop] === 'function') || (typeof target[prop] === 'object')) {
return proxify(target[prop], something);
}
return Reflect.get(target, prop);
},
apply(target, thisArg, argumentsList) {
return Reflect.apply(target, root || thisArg, argumentsList);
}
});
const url = new URL('https://twitter.com/Rich_Harris/status/1539375179394056192');
const proxy = proxify(url);
console.log(proxy.pathname); // works
console.log(proxy.toString()); // works
Vue3
最后
刚学Proxy,可能部分概念描述不是很准确,请多多包涵!以上内容如果有说得不对的地方,请各位老哥在评论区指出,我会尽快做出修改。
如果觉得小老弟写得还行,不介意的话,来个三连(点赞、收藏、关注)。
你的认可,是我不断前进的动力。
本人两年多前端开发经验,熟悉Vue相关技术栈。
如果各位老哥的公司里有坑位,麻烦踢踢我,带带老弟。
转载自:https://juejin.cn/post/7138070814791827469