Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   ES6: Как сделать фабрику методов для класса? (https://javascript.ru/forum/misc/70000-es6-kak-sdelat-fabriku-metodov-dlya-klassa.html)

Shitbox2 02.08.2017 19:23

ES6: Как сделать фабрику методов для класса?
 
Вопрос на самом деле по TypeScript, но он в этом плане полностью копирует ES6.

Есть какой-то класс:
class Dog {
  bulk() {
    ...
  }
}


Нужно определять его методы не вручную, а динамически, примерно так:
function CreateMethod(params) {
  return function() {
     ...
  }
}

class Dog {
  bulk: CreateMethod({name: 'bulk', volume: 90}) //не работает
}


Как это сделать без декораторов методов?

Alexandroppolus 02.08.2017 20:53

Dog.prototype.bulk = CreateMethod({name: 'bulk', volume: 90});


поскольку TypeScript заявлен как "надмножество", а class - как "сахар", то старый добрый способ должен работать, наверно.
и да - понятно, что ссылки на super в методе bulk не будет.

Shitbox2 03.08.2017 03:25

Так можно, но нужно именно в стиле ES6
class Dog {
  bulk: CreateMethod({name: 'bulk', volume: 90})
}

Например, здесь это делается с помощью интерфейса. Хотелось бы фабрику использовать

Shitbox2 03.08.2017 15:38

Rise,
ну, это слишком жестко. Без eval'а нельзя разве?

Shitbox2 03.08.2017 17:51

Всё какие-то запрещённые методы у вас) Это всё равно, что глубокое сравнение объектов делать, сравнивая их json-строки

Shitbox2 03.08.2017 20:20

В TS можно так сделать. Будет работать. Правда, когда применяю к классу декоратор, все такие методы пропадают, но это я как-то декоратор неправильно пишу
function CreateMethod(params) {
  return function() {
     console.log(params)
  }
}

class Dog {
  bulk = CreateMethod({name: 'bulk', volume: 90})
}

Alexandroppolus 03.08.2017 20:51

Цитата:

Сообщение от Shitbox2
Правда, когда применяю к классу декоратор, все такие методы пропадают

куда пропадают?

DjDiablo 03.08.2017 23:48

Цитата:

Правда, когда применяю к классу декоратор, все такие методы пропадают
вероятно от того что все эти свойства в конструкторе создаются

Shitbox2 04.08.2017 12:27

Никуда не пропадают, это я неправильную реализацию написал. Если делать так, то всё работает:
function logClass(target: any) {

  // сохраняем ссылку на исходный конструктор
  var original = target;

  // вспомогательная функция для генерации экземпляров класса
  function construct(constructor, args) {
    var c : any = function () {
      return constructor.apply(this, args);
    }
    c.prototype = constructor.prototype;
    return new c();
  }

  // новое поведение конструктора
  var f : any = function (...args) {
    console.log("New: " + original.name); 
    return construct(original, args);
  }

  // копируем прототип, чтобы работал оператор instanceof
  f.prototype = original.prototype;

  // возвращаем новый конструктор (он переопределит исходный)
  return f;
}

Shitbox2 04.08.2017 16:48

Пример декоратора, который я привел, не относится к задаче (т.е. он не участвует в определении методов). Просто я тестировал своё решение в т.ч. со сторонними декораторами, чтобы убедиться, что всё работает как надо.

К сожалению, мой способ не работает в ES6 и это привязывает меня к TS

Alexandroppolus 04.08.2017 18:40

Цитата:

Сообщение от Shitbox2 (Сообщение 460582)
В TS можно так сделать. Будет работать. Правда, когда применяю к классу декоратор, все такие методы пропадают, но это я как-то декоратор неправильно пишу
function CreateMethod(params) {
  return function() {
     console.log(params)
  }
}

class Dog {
  bulk = CreateMethod({name: 'bulk', volume: 90})
}

если верить плейграунду, то bulk создается в конструкторе.
на каждый экземпляр - новый метод. При том что тут не нужны данные из экземпляра.
если экземпляров много, таки лучше через прототип. Конечно, выглядит совсем не по-хипстерски, зато правильнее :)

Shitbox2 06.08.2017 04:02

Учитывая, что это всё используется в Ангуляре (кто-то использует TS по-другому? ;-), а сервисы там преимущественно синглтоны, то копировать свойства в конструкторе нормально... Не понимаю, зачем ангуляровцы, вообще, ООП используют при таком раскладе

Shitbox2 11.08.2017 15:29

Что происходит, когда мы возвращаем значение из конструктора?
var f : any = function (...args) {
    console.log("New: " + original.name);
    return new original(...args);
}


В документации по ES6/TypeScript таких примеров нет

Alexandroppolus 11.08.2017 15:37

Цитата:

Сообщение от Shitbox2
Что происходит, когда мы возвращаем значение из конструктора?

если это значение - объект, то результатом всего вызова new будет это значение.

Shitbox2 11.08.2017 16:07

А где об этом можно почитать? Это поведение ES6 или TypeScript добавляет?

Alexandroppolus 11.08.2017 16:14

Цитата:

Сообщение от Shitbox2
А где об этом можно почитать?

в самом что ни на есть МДНе
https://developer.mozilla.org/ru/doc.../Operators/new

это стандартное js-ное поведение, существует от сотворения мира.

Shitbox2 11.08.2017 16:26

И как это я упустил этот момент) Спасибо!

Shitbox2 11.08.2017 16:57

Еще вопрос, чтобы новую тему не создавать. Пример из статьи: https://www.sitepen.com/blog/2015/10...pt-decorators/

Реализация декоратора @readonly
function readonly<TFunction extends Function>(Target: TFunction): TFunction {
    let newConstructor = function () {
        Target.apply(this);
        Object.freeze(this);
    };
 
    newConstructor.prototype = Object.create(Target.prototype);
    newConstructor.prototype.constructor = Target;
 
    return <any> newConstructor;
}

Зачем здесь делают Target.apply(this);? По-моему, это просто вызовет конструктор без аргументов. Смысл?

Ну и
newConstructor.prototype = Object.create(Target.prototype);
newConstructor.prototype.constructor = Target;

так ли нужны?

Alexandroppolus 11.08.2017 17:40

Цитата:

Сообщение от Shitbox2
Зачем здесь делают Target.apply(this);? По-моему, это просто вызовет конструктор без аргументов. Смысл?

аргументы незаслуженно забыты.
Target.apply(this, arguments);

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

Цитата:

Сообщение от Shitbox2
Ну и
newConstructor.prototype = Object.create(Target.prototype);
newConstructor.prototype.constructor = Target;

так ли нужны?

Возможно (однако нельзя сказать наверняка), стоило обойтись таким кодом:
newConstructor.prototype = Target.prototype;

Shitbox2 11.08.2017 17:52

Цитата:

По сути, тут создается новый класс, который в точности как Target, только объекты после создания замораживаются. Потому конструктор вызывать надо.
Т.е. это эквивалентно super(args)?

Alexandroppolus 11.08.2017 18:02

Да.

Alexandroppolus 11.08.2017 18:09

Цитата:

Сообщение от Shitbox2
Ну и
newConstructor.prototype = Object.create(Target.prototype);
newConstructor.prototype.constructor = Target;

так ли нужны?

тут правильнее всего так:
newConstructor.prototype = Object.create(Target.prototype);
newConstructor.prototype.constructor = newConstructor;
быть может, это и подразумевалось, только автор опечатался

Shitbox2 11.08.2017 18:20

Всё равно декоратор не работает нормально. Если делаю так, то всё ОК
function decorator(target) {
  return target
}

@decorator
class MyClass {
  public constructor( @Inject(HttpClient) protected http: HttpClient) {}
}


Но как только вместо target возвращаю в декораторе новый конструктор, DI перестает работать (сервисы не инжектятся) :(

P.S. Сделал планк https://embed.plnkr.co/opnY3y/

Shitbox2 13.08.2017 17:28

Читал документацию по декораторам и смотрел как они в ES транспилятся - ничего сложного)

На stackoverflow ответили: https://stackoverflow.com/questions/...46606#45646606

Тоже пробовал что-то подобное, но оказывается нужно было newCtor делать именованной, а в конце все-таки использовать newConstructor.prototype = Object.create(target.prototype), а не newConstructor.prototype = target.prototype;

Видимо, Ангуляр как-то по-особенному свои зависимости инжектит. Вот в его исходниках так и не разобрался - слишком муторный код


Часовой пояс GMT +3, время: 03:33.