最近用到 Proxy 的地方还是挺多的(具体关注之后的文章就知道了),这里来总结一下具体的用法。
何为 Proxy?
Proxy,简单来说,就是用来代理访问一个对象的那层代理。Proxy 可以拦截对其对应对象的几乎所有请求,包括赋值、取值、执行(函数调用)、类型判断(instanceof
)等。这里直接给出 MDN 的地址吧,具体的介绍可以去那里看。
但是,虽说 Proxy 是透明的,但这种透明也只是相对的,对于 JavaScript 而言的透明,当遇到 Native Code 时,透明性就不再生效了。例如你如果 Proxy 了一个 DOM Element:
const element = document.createElement('a');
const proxy = new Proxy(element, {});
document.body.appendChild(proxy);
就会出现报错:
原因也很显而易见,DOM 不吃这个。类似的事情也会发生在 canvas
的 drawImage
之类的函数上,代理时须多多留意。
target
target
是 Proxy 代理的客体,其只要满足 typeof target === "object"
即可。target
在 Proxy 新建的时候传入,并且在每次 trap
触发时都会被传入对应的 handler
函数作为参数。
handler
handler
是声明了 Proxy 代理行为的对象。handler
为一个对象,其中的所有有效内容(指对 Proxy 而言有用的内容)均为函数。这里术语上将每一个不同的这样的函数称为 trap
,如 get
、set
等。
handler.get
handler.get(target, property, receiver)
可以拦截该 Proxy 目前被要求获得的属性。
这里我们来设想一下,对于一个普通的对象 o
,如果我们要访问它的某一个属性,会怎么访问呢?
最简单的就是 o[property]
了,这也是最常用的访问方式。但我们再考虑另外一个问题:如果 o[property]
是函数,会怎么样?
这个结论是很显然的,在某些情况下,会导致 this
的丢失。这里给出一个最简单粗暴的方法:
new Proxy(element, {
get: function (target, key) {
if (typeof target[key] === "function") {
return target[key].bind(target);
}
return target[key];
},
});
为什么说是简单粗暴呢?因为它直接将所有函数类型都和 target
强制绑定了。在大部分情况下,这样是不会出问题的,但如果使用了箭头函数的话,强行绑定 this
的逻辑就不对了。 // 这里还需要斟酌一下,如果有 dalao 发现问题还请指正(
handler.set
handler.set(target, property, value, receiver)
可以拦截该 Proxy 目前被要求设置的属性和值。这里的处理一般就很简单了,target[property] = value
就可以了,不过最后要返回 true
以表示成功。
handler.apply
handler.apply(target, _this, _arguments)
可以拦截该 Proxy 作为函数执行时的操作,并返回函数的返回值。通过 handler.apply
,我们可以直接修改原函数的实现。
Practice:拦截 Raven
基础的知识就到这里,细节可以去 MDN 看。这里我们来看一个案例:为了减少调用栈的深度,方(fang)便(zhi)我们前端逆向的操(ka)作(si),对于启用了 Raven
的站点,我们需要将其拦截。
拦截的第一步是阻止脚本加载,这一步可以用多种方法实现,不论是 ABP,又或是开发者工具的 Block
,相信都很好解决,这里就略过了。
拦截的第二步是伪造一个 Raven
,因为当前目标代码中肯定用到了 Raven
,因此需要伪造一个什么都不干的对象,但是可以被访问、赋值、调用。
答案在此:
window.Raven = new Proxy(function () {}, {
get() {
return window.Raven;
},
apply() {
return window.Raven;
},
});
是不是很简单,很优雅(
结语
ES6 的 Proxy 给前端逆向对 Object 的控制注入了灵魂,之前虽然可以通过 Object
进行操作,但相比与现在的 API 还是过于粗糙了。
Proxy 可以实现的事情还有很多,比如 Sandbox(雀魂 Plus 1.x 就是使用的基于 Proxy 的沙箱)等。前端安全不容忽视啊(光速逃