Декораторы Javascript: как сохранить функцию с этой областью?

Я хотел бы создать декоратор для хранения функции с этой областью в массиве для каждого экземпляра, созданного для класса. Я не могу найти подходящие слова для объяснения, поэтому, скажем, у меня есть следующий код:

class Foo
{
    constructor()
    {
        this.MyValue = "Foo";
    }

    @StoreFunction()
    TestA()
    {
        console.log("Foo MyValue:", this.MyValue);
    }
}
class Bar extends Foo
{
    constructor()
    {
        this.MyValue = "Bar";
    }

    @StoreFunction()
    TestB()
    {
        console.log("Bar MyValue:", this.MyValue);
    }
}

function StoreFunction()
{
    return function(target, key, descriptor)
    {
        // How would i go about saving the function there so i can call it later??
        return descriptor;
    }
}

Я знаю, что декораторы не обрабатываются во время создания экземпляра класса. Итак, я попытался сделать следующее в StoreFunction.

var StoredFunctions = [];

function StoreFunction()
{
    return function(target, key, descriptor)
    {
        if(target._StoredFunctions)
        {
            target._StoredFunctions = [];
        }
        // Save the function's name
        target._StoredFunctions.push(key);

        return descriptor;
    }
}

Затем привязать их в конструкторе.

class Foo
{
    constructor()
    {
        this.MyValue = "Foo";
        this.BindFunctions()
    }

    BindFunctions()
    {
        if(this._StoredFunctions)
        {
            this._StoredFunctions.forEach( method => {
                StoredFunctions.push(this[method].bind(this));
            });
        }
    }
}

Но опять же this._StoredFunctions не хранит StoredFunctions должным образом, поскольку они являются статическими. Я не совсем уверен, как заставить его работать правильно.

Пожалуйста, дайте мне знать, если вопрос не ясен, мне трудно объяснить проблему.


person DDD    schedule 16.05.2017    source источник
comment
Какого конечного результата вы хотите достичь? Меня особенно смущает фраза «открыть функцию», поскольку методы не скрыты в объектах, поэтому их не нужно раскрывать.   -  person T.J. Crowder    schedule 16.05.2017
comment
Когда вы говорите выставить функцию, вы пытаетесь поместить их в глобально доступный массив для последующего использования? Почему бы просто не использовать объект и его метод позже?   -  person evolutionxbox    schedule 16.05.2017
comment
Я знаю, что это не скрытые объекты. Я хотел бы поместить их в массив и привязать их к событиям клавиатуры или любым другим событиям, которые я запускаю. Допустим, я создаю функцию с помощью этого декоратора, затем я могу создать экземпляр этого класса и вызывать все эти функции для каждого созданного экземпляра.   -  person DDD    schedule 16.05.2017
comment
@T.J.Crowder обновил мой вопрос.   -  person DDD    schedule 16.05.2017


Ответы (2)


Вы можете вставить автоматический подкласс, который может выполнять необходимую работу по «разоблачению» (или иным образом делать что-то с) отмеченными функциями, см. комментарии:

// `exposable` sets the class up for `exposed` on methods
function exposable() {
    return function decorator(target, name) {
        // Create our subclass with the same name
        const o = {[name]: class extends target {
            constructor(...args) {
                super(...args);
                // "Expose" the bound methods
                this.exposed = {};
                Object.getOwnPropertyNames(target.prototype).forEach(name => {
                    const method = target.prototype[name];
                    if (method.exposed) {
                        this.exposed[name] = method.bind(this);
                    }
                });
            }
        }};
        return o[name];
    };
}

// `expose` marks a method to be exposed in the constructor
function exposed(state) {
    return function decorator(target, name, config) {
        config.value.exposed = true;
        return config;
    };
}

// Example
@exposable()
class Foo {
    constructor(name) {
        this.name = name;
    }

    @exposed()
    testA() {
        console.log("testA says " + this.name);
    }

    testB() {
        console.log("testB says " + this.name);
    }
}

const f = new Foo("Fred");
f.exposed.testA(); // Says "testA says Fred" because it's bound

Вы сказали, что мы можем полагаться на наличие общего базового класса (Foo). Если это так, мы можем переместить логику в сам Foo:

// `expose` marks a method to be exposed in the constructor
function exposed(state) {
    return function decorator(target, name, config) {
        config.value.exposed = true;
        return config;
    };
}

// Example
class Foo {
    constructor(name) {
        this.name = name;
        this.exposed = Object.create(null);
        let proto = Object.getPrototypeOf(this);
        while (proto && proto !== Object.prototype) {
            Object.getOwnPropertyNames(proto).forEach(name => {
                if (!this.exposed[name]) {
                    const method = this[name];
                    if (typeof method === "function" && method.exposed) {
                        // Expose it
                        this.exposed[name] = method.bind(this);
                    }
                }
            });
            proto = Object.getPrototypeOf(proto);
        }
    }

    @exposed()
    testA() {
        console.log("testA says " + this.name);
    }
}

class Bar extends Foo {

    @exposed()
    testB() {
        console.log("testB says " + this.name);
    }
}

const f = new Foo("Fred");
f.exposed.testA(); // Says "testA says Fred" because it's bound
const b = new Bar("Barney");
b.exposed.testA(); // Says "testA says Barney" because it's bound
b.exposed.testB(); // Says "testB says Barney" because it's bound
person T.J. Crowder    schedule 16.05.2017
comment
Есть ли способ оставить декоратор @exposable() из класса, поэтому мне нужно только поместить декоратор в функцию? - person DDD; 16.05.2017
comment
@Azarus: я не могу сразу подумать об одном, если вместо этого вы не украсите constructor, что в основном одно и то же. (Если, конечно, вы не хотите вручную кодировать вызов установки в каждом конструкторе.) - person T.J. Crowder; 16.05.2017
comment
Хм, а если я создам функцию в родительском классе, которая будет работать с каждым методом класса и будет смотреть, есть ли у функции свойство .exposed? Затем привязать функции соответственно? - person DDD; 16.05.2017
comment
Конечно, если все они будут иметь суперклассы, конструктор суперкласса сможет это сделать. Это устранило бы большую часть описанной выше сложности. - person T.J. Crowder; 16.05.2017
comment
Таким образом, @exposed должен привязать только одну переменную к функции, а не привязывать фактическую функцию к какому-либо массиву. Итак, при инициализации я получаю список функций в каждом случае. - person DDD; 16.05.2017
comment
@Azarus: Да (экземпляр или его прототип или прототип его прототипа и т. д.). В вашем примере у Foo не было суперкласса, поэтому я не знал, что он будет у всех. - person T.J. Crowder; 16.05.2017
comment
Да, Фу был суперклассом, а ребенок был Баром - person DDD; 16.05.2017
comment
@Azarus: я просто не понимал, что все должно происходить от Foo. Я добавил пример использования подкласса Foo. - person T.J. Crowder; 16.05.2017
comment
Отлично спасибо. Я действительно не знаю, почему я застрял на этом :) - person DDD; 16.05.2017

Т.Дж. Краудер дал правильный ответ, я предлагаю чистое и удобочитаемое решение, которое я придумал на основе Т.Дж. Краудер.

var StoredFunctions = {};
class Foo
{
    constructor()
    {
        this.MyValue = "Foo";
        this.SetupStore();
    }

    SetupStore()
    {
        let Methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
        for(let Method of Methods)
        {
            if(typeof(this[Method]) == "function" && this[Method]._StoredFunction)
            {
                StoredFunctions[Method] = this[Method].bind(this);
            }

        }
    }

    @StoreFunction()
    TestA()
    {
        console.log("Foo MyValue:", this.MyValue);
    }
}

class Bar extends Foo
{
    constructor()
    {
        this.MyValue = "Bar";
    }

    @StoreFunction()
    TestB()
    {
        console.log("Bar MyValue:", this.MyValue);
    }
}



function StoreFunction(StoreInfo)
{
    return function(target, key, descriptor)
    {
        descriptor.value._StoredFunction = true; // Mark the function to be stored on initialization
        return descriptor;
    }
}
person DDD    schedule 16.05.2017