Обертывание конструктора с Proxy для динамического наблюдения и работы с методами экземпляра - PullRequest
0 голосов
/ 13 марта 2020

Я только начал узнавать о Прокси и Рефлексии, и был бы признателен за некоторые указания в правильном направлении, так как я немного заблудился здесь. Моя задача - создать класс Wand, который принимает объект функций (заклинаний). Каждый вызов заклинания должен добавлять имя заклинания в массив истории, который содержит журналы для самых последних вызовов (до 3).

Новый экземпляр также должен позволять использование методов prioriIncantatem() и deletrius() (но они не должны присутствовать в самом экземпляре / классе), первый вернет текущий массив истории (до добавления prioriIncantatem к нему), второй очистит историю. Эти методы также должны быть зарегистрированы в массиве истории (prioriIncantatem будет отображаться только при следующем вызове, но все еще будет регистрироваться). Экземпляр должен наследоваться от Object.prototype, поэтому такие вещи, как .toString(), работают как обычно, что приводит к [object Object] (они также будут записаны в историю).

Экземпляр также должен позволять динамически добавлять новые методы ( заклинания), будучи в состоянии вызывать существующие методы, используя this, который будет регистрировать оба обращения к массиву истории и работать с другими методами, как если бы они все были созданы при создании.

Это должно работать так:

const w = new Wand({ alohomora: function() { console.log('unlocked!') },});

w.alohomora(); // logs 'unlocked!'
w.prioriIncantatem(); // => ['alohomora']

w.myNewSpell = function() { console.log('magic!'); }
w.myNewSpell(); // logs 'magic!'

w.prioriIncantatem(); // => ['myNewSpell', 'prioriIncantatem', 'alohomora']

w.deletrius();
w.prioriIncantatem(); // => ['deletrius']

Object.getOwnPropertyNames(w) == ["alohomora", "_history", "myNewSpell"]

Не совсем понял, как обернуть экземпляр прокси, так что после небольшого исследования я пришел к этому, что, похоже, работает с приведенным выше примером

class WandClass {
    constructor(opts = {}) {
        Object.assign(this, opts);
        this._history = [];
    }
}

const Wand = new Proxy(WandClass, {
    construct(target, args) {
        return new Proxy(new target(...args), {
            get(target, prop, receiver) {
                const value = target[prop];
                const history = target._history;
                if (typeof value == 'function') {
                    return (...args) => {
                        history.unshift(prop);
                        if (history.length > 3) history.pop();
                        return Reflect.apply(value, target, args);
                    };
                }
                else if (['prioriIncantatem', 'deletrius'].includes(prop)) {
                    return () => {
                        switch (prop) {
                            case 'prioriIncantatem':
                                console.log(history);
                                history.unshift(prop);
                                if (history.length > 3) history.pop();
                                break;
                            case 'deletrius':
                                history.length = 0;
                                history.unshift(prop);
                                break;
                        }
                    };
                }
                else { return Reflect.get(target, prop); }
            }
        });
    }
});

Тем не менее, при попытке создания следующего экземпляра код не работает должным образом:

const w = new Wand({
    alohomora: function () { console.log('unlocked!'); },
    expelliarmus: function () { console.log('disarmed!'); }
});

w.unlockThenDisarm = function () {
    this.alohomora();
    this.expelliarmus();
};

w.unlockThenDisarm(); // EXPECTED: logs 'unlocked!' then 'disarmed!', NO ISSUE HERE
w.prioriIncantatem(); // EXPECTED:  => ['expelliarmus', 'alohomora', 'unlockThenDisarm']
                      // instead got ['unlockThenDisarm']

Первоначально я думал, что существует проблема с привязкой this, но так как console.log работает должным образом, Я предположил, что это не так. Из того, что я понимаю, не сам вызов - это то, что записывает в поле истории, а действие получения свойства. Не могу понять, как перехватить сами вызовы и добавить логи c регистрации вызовов при применении ловушки вместо получения ... Я попытался добавить ловушку напрямую, вот так

get(target, prop, receiver){
// ...
},
apply(target, thisArg, args) { debugger; }

, но отладчик никогда не запускается ...

...