Как изменить прототип класса во время выполнения внутри этого класса (с классами ES6) - PullRequest
0 голосов
/ 03 мая 2018

У меня есть следующий класс 'X', и я хочу добавить некоторые вещи к его prototype динамически, как это (это работает ):

    class X{
        someUnrelatedFunction(){};	
    }
    
    //adding some stuff at runtime, in real situation, the ['a','b','c'] aren't "known" in advance like here.

    ['a','b','c'].forEach(elem => {
    	X.prototype[elem] = () =>{
      	    console.log('called with : ' + elem);
      };
    })

    //tests:

    x = new X();
    x.c();//works, outputs 'called with : c';

Но в самой декларации class. Я хотел бы сделать это, чтобы сделать вещи немного более читабельными, то есть я хотел бы, чтобы инициализация «прототипа» принадлежала самому классу.

Прямо сейчас я делаю это в «конструкторе», как здесь:

class X{
	constructor(){
  	//we don't want to do that again:
  	if(typeof X.prototype.inited === 'undefined'){
    	X.prototype.inited = true;
    	console.log('-constructor: initing prototype');
      ['a','b','c'].forEach(elem => {
        X.prototype[elem] = () =>{
          console.log('called with : ' + elem);
        };
      })      
  	}else{
    	console.log('-constructor: no need to re-init prototype');
    }	
  }
}


x1 = new X();
x1.a();

x2 = new X();
x2.b();

скрипка: https://jsfiddle.net/nypL29f3/4/

Но это кажется мне сложным, плюс внутри класса я на самом деле ссылаюсь на него как бы "снаружи", т.е. я использую "X". Если я когда-либо изменил имя класса, мне также нужно будет изменить эту часть кода.

Итак, вопрос - как сделать это более читабельным и правильным способом?

FWIW, «реальный» сценарий состоит в том, что я играю с ES6, создавая бесполезные скрипты, подобные этому:

https://jsfiddle.net/kpion/mqyrewnx/9/

Здесь мои сомнения были по поводу строки # 78 // это был мой вопрос - кажется, мне нужно сделать это вне класса YaLog.

Редактировать : просто кстати - если вы пришли сюда с похожим вопросом - все нижеприведенные ответы отвечают на вопрос определенным образом, и все стоит прочитать. Спасибо всем! :)

Ответы [ 3 ]

0 голосов
/ 03 мая 2018

Я бы хотел, чтобы инициализация 'prototype' принадлежала самому классу.

Это невозможно. Тело класса может содержать только определения методов, а не произвольный код.

Если число методов известно, и только их имена являются динамическими, вы можете использовать вычисляемые имена свойств, но в вашем примере это не так.

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

Прямо сейчас я делаю это в constructor

Не. Это неэффективно, подвержено ошибкам и ужасно нечитаемо.

0 голосов
/ 03 мая 2018

Такого рода модификации прототипа класса никогда не выполняются внутри конструктора - главным образом потому, что конструктор класса отвечает за управление текущим экземпляром класса, а не всеми экземплярами класса. Предполагается, что конструктор объявляет новые методы ('log', 'info' и т. Д.) В экземпляре класса this, это менее эффективно, чем объявление методов в классе prototype, и может быть желательным или нет для дальнейшего наследования класса.

Это то, для чего предназначены декораторы. Они предоставляют удобный синтаксис для конструктора и прототипа класса расширения или модификации.

Декоратор может изменить существующий класс prototype:

function withLogger(Class) {
  ['log','info','error','warn','group','groupEnd'].forEach(func => {
    Class.prototype[func] = function() {
      return this.callConsoleMethod(func, ...arguments);
    };
  });

  return Class;
}

Или возвращать новый класс, который расширяет существующий класс неразрушающим способом, это позволяет ссылаться на оригинальные члены с super при дублировании их в классе-обертке.

Декораторы имеют аккуратный синтаксис в ECMAScript Next / TypeScript:

@withLogger
class YaLog {
  ...
}

Поскольку декоратор в основном является вспомогательной функцией, его можно применить непосредственно в ES6 чуть менее выразительным образом:

const YaLog = withLogger(class YaLog {
  ...
});
0 голосов
/ 03 мая 2018

Вы можете использовать наследование. Создайте новый класс Y и добавьте в него методы из вашего массива. Затем вы можете расширить оригинальный класс (или любой другой) с помощью Y:

class Y {}
const methods = ['a', 'b', 'c']
methods.forEach(elem => {
  Y.prototype[elem] = () => {
    console.log('called with : ' + elem)
  }
})

class X extends Y {
  someUnrelatedFunction() {}
}

const x1 = new X();
x1.a();

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

function extendWithMethods(methodNames, generalHandler) {
  class Y {}
  methodNames.forEach(elem => {
    Y.prototype[elem] = () => generalHandler(elem)
  })
  return Y
}

class X extends extendWithMethods(['a', 'b', 'c'], methodName => {
  console.log('called with : ' + methodName)
}) {
  someUnrelatedFunction() {}
}

const x1 = new X()
x1.a()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...