Passing Proxy object as thisArgument to apply throws TypeError: Illegal Invocation

Passing Proxy object as thisArgument to apply throws TypeError: Illegal Invocation

我正在尝试捕获对 Storage 的呼叫。据我所知,有两种方法可以调用 setItemgetItem:

    sessionStorage.setItem("foo", "bar");
    let item = sessionStorage.getItem("foo");

    Storage.prototype.setItem.call(sessionStorage, "foo", "bar");
    let item2 = Storage.prototype.getItem.call(sessionStorage, "foo");

在代码段中使用 sessionStorage 会引发安全错误,因此 here's the code in JS Fiddle

TLDR:如果我这样做,我可以处理所有情况,但它看起来很老套。有没有 better/cleaner 方法来实现我的目标? (注意:我无法控制来电者,这就是我涵盖这两种情况的原因):

try {
    console.log("\n\n");
    let ss = new Proxy(sessionStorage, {
        get: function (getTarget, p) {
            if (p === "__this") {
                // kinda hacky, but allows us to unwrap Proxy for binding
                return getTarget;
            }
            console.log("sessionStorage.get proxy called")
            return new Proxy(Reflect.get(getTarget, p), {
                apply(applyTarget, thisArg, argArray) {
                    console.log("sessionStorage.get.apply called");
                    Reflect.apply(applyTarget, getTarget, argArray);
                }
            })
        },
    });

    SP = new Proxy(Object.create(Storage.prototype), {
        get: function (getTarget, p) {
            console.log("Storage.get proxy called")
            return new Proxy(Reflect.get(getTarget, p), {
                apply(applyTarget, thisArg, argArray) {
                    console.log("Storage.get.apply called");
                    try {
                        return Reflect.apply(applyTarget, thisArg, argArray);
                    } catch (e) {
                        // unpack proxy if we're double-wrapped (both target and thisArg are Proxy)
                        return Reflect.apply(applyTarget, thisArg.__this, argArray);
                    }
                }
            })
        },
    });
    SPW = {};
    Object.defineProperty(SPW, 'prototype', {
        value: SP,
        configurable: false,
    });
    si = SPW.prototype.setItem;
    gi = SPW.prototype.getItem;
    si.call(ss, "foo", "3");
    console.log(`Storage.prototype.getItem: ${gi.call(ss, "foo")}`);
    console.log(`Storage.prototype Worked`)
} catch (e) {
    console.log(`Storage.prototype Caught ${e.stack}`);
}

JSFiddle.

结果:

Storage.get proxy called
Storage.get proxy called
Storage.get.apply called
Storage.get.apply called
Storage.prototype.getItem: 3
Storage.prototype Worked

除了包含一个“隐藏的”__this属性 以便调用者可以“解开”Proxy 对象并获取对原文sessionStorage。有更好的方法吗?

背景

如果有帮助,这里是仅包装 sessionStorageStorage 而非两者的示例:

换行sessionStorage

对于 apply 陷阱,我必须传递 getTarget 而不是 thisArg 因为后者是 Proxy 对象,如果我传递它一个 Illegal Invocation 抛出错误。

    try {
        let ss = new Proxy(sessionStorage, {
            get: function (getTarget, p) {
                console.log("sessionStorage.get called")
                return new Proxy(Reflect.get(getTarget, p), {
                    apply(applyTarget, thisArg, argArray) {
                        console.log("sessionStorage.get.apply called");
                        Reflect.apply(applyTarget, getTarget, argArray);
                    }
                })
            },
        });
        ss.setItem("foo", "1");
        console.log(`Proxy.sessionStorage.getItem: ${ss.getItem("foo")}`);
        console.log(`Proxy.sessionStorage Worked`)
    } catch (e) {
        console.log(`Proxy.sessionStorage Caught ${e.stack}`);
    }

JS Fiddle

结果:

sessionStorage.get called
sessionStorage.get.apply called
sessionStorage.get called
sessionStorage.get.apply called
sessionStorage.get called
sessionStorage.get.apply called
Proxy.sessionStorage.getItem: undefined
Proxy.sessionStorage Worked

换行Storage.prototype

这里我必须首先创建一个带有单独 prototype 的新对象,因为 prototype 属性 描述符 Storageconfigurable 设置为 false。完成后,我基本上必须通过传递“展开的”getTarget 而不是 thisArg.[=45 指向的 Proxy 实例来做与前一个案例相同的事情=]

    try {
        console.log("\n\n");
        SP = new Proxy(Object.create(Storage.prototype), {
            get: function (getTarget, p) {
                console.log("Storage.get proxy called")
                return new Proxy(Reflect.get(getTarget, p), {
                    apply(applyTarget, thisArg, argArray) {
                        console.log("Storage.get.apply called");
                        try {
                            return Reflect.apply(applyTarget, thisArg, argArray);
                        } catch (e) {
                            console.log("apply failed when passing thisArg");
                            return Reflect.apply(applyTarget, getTarget, argArray);
                        }
                    }
                })
            },
        });
        SPW = {};
        Object.defineProperty(SPW, 'prototype', {
            value: SP,
            configurable: false,
        });
        si = SPW.prototype.setItem;
        gi = SPW.prototype.getItem;
        si.call(sessionStorage, "foo", "2");
        console.log(`Storage.prototype.getItem: ${gi.call(sessionStorage, "foo")}`);
        console.log(`Storage.prototype Worked`)
    } catch (e) {
        console.log(`Storage.prototype Caught ${e.stack}`);
    }

JS Fiddle

结果:

Storage.get proxy called
Storage.get proxy called
Storage.get.apply called
Storage.get.apply called
Storage.prototype.getItem: 2
Storage.prototype Worked

我认为你把这件事想得太复杂了。没有理由在这里涉及代理,只是猴子修补这两个方法:

const proto = Storage.prototype;
const originalSet = proto.setItem;
const originalGet = proto.getItem;
Object.assign(proto, {
    setItem(key, value) {
        console.log(`Setting ${JSON.stringify(key)} to ${JSON.stringify(value)} on a ${this.constructor.name}`);
        return originalSet.call(this, key, value);
    },
    getItem(key) {
        console.log(`Getting ${JSON.stringify(key)} from a ${this.constructor.name}`);
        return originalGet.call(this, key);
    },
});