Javascript: как перейти от управления прототипами к концепции классов? - PullRequest
0 голосов
/ 06 мая 2020

У меня будет новый вопрос. Раньше я использовал проект vanilla javascript. В этом проекте мы создали «объект», изменив прототип. Этот код не был оптимальным, я знаю об этом, но он работал.

Технологии развиваются, и люди тоже. Теперь я хотел бы использовать понятие класса и Typescript. Я создал целый набор классов, соответствующих старым, но с новыми функциями и более чистым кодом. Я хотел бы знать, смогу ли я сделать существующий когексист существующим с новым кодом (не касаясь кода).

Вот мой старый код: https://codepen.io/Answerrer/pen/YzyeNpe

function declareClass(classModel, version) {

  if (typeof classModel !== 'function') {
    throw classModel + ' cannot be used as a class as it is not a function';
  }

  if(window.myClasses === undefined) {
    window.myClasses = {};
  }

  window.myClasses[classModel.name] = classModel;

  classModel.prototype._version   = version || '1.0';
  classModel.getVersion =  function() {
    return this.prototype._version;
  };
};

function declareChildClass(subClass, superClass, version) {

  declareClass(subClass, version);

  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
  if (superClass) {
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

  subClass.prototype._super = superClass.prototype;
};

(function() {

  let proto = a.prototype;
  window.declareClass(a, 'A1')

  function a(id, properties) {
    this.id = id;
    this.properties = properties;
  }

  proto.echo = function() {
    console.log('ID [' + this.id + ']');
    console.log('Properties');
    console.log(this.properties);
  }


})();

(function() {
  window.declareChildClass(b, window.myClasses.a, 'B2')

  function b(id) {
    window.myClasses.a.call(this, 'B' + id, {})
  }
})();

var a = new window.myClasses.a('TestA', {data: 'test'}),
    b = new window.myClasses.b('X');

a.echo();
b.echo();

Вот код класса A, который я перекодировал. Я хотел бы использовать этот класс, не изменяя код объекта B. Код функций declareClass и declareChildClass можно изменить.

export default class A {
    id:string;
    properties:{};

    constructor(id:string, properties:{}) {
        this.id = id;
        this.properties = properties
    }

    echo() {
        console.log('ID [' + this.id + ']');
        console.log('Properties');
        console.log(this.properties);
    }
}

Если у кого-то есть идея, меня интересует. Если этого недостаточно, я постараюсь дать больше информации.

Большое спасибо.

Ответы [ 2 ]

1 голос
/ 07 мая 2020

Ваши предложения (@hackape) заставили меня продвинуться вперед в ношении ребенка. Мой «адаптер», который позволяет навести мост между новым и старым кодом, находится в Typescript / ES6 (я не должен снова начинать с плохо написанного кода). В результате некоторые вещи нелогичны, могут вызывать беспокойство, а Typescript запрещает определенные вещи. Не менять старый код вообще было немного утопично и, я думаю, слишком сложно.

Итак, вот код для моего «адаптера»:

export class ClassesAdapter {
  public declaredClasses = new Map<string, Function>();

  /**
   * Declare a new class
   * @param classModel Class (function)
   * @param version Class version
   */
  declareClass(classModel: Function, version: string): void {
    if (classModel instanceof Function) {
      this.declaredClasses.set(classModel.name, classModel);
      classModel.prototype._version = version || '1.0';
      classModel.prototype.getVersion = function(): string {
        return this._version;
      };
    } else {
      throw classModel + ' cannot be used as a class as it is not a function';
    }
  }

  /**
   * Declare child class (extends)
   * @param subClass Child class (function)
   * @param superClass Super class (function)
   * @param version Child version
   */
  declareChildClass(subClass: Function, superClass: Function, version: string): void {
    this.declareClass(subClass, version);

    this.assignSuperClass(superClass, subClass);
  }

  assignSuperClass(superClass: Function, subClass: Function): void {
    if (typeof superClass !== 'function' && superClass !== null) {
      throw new TypeError('Super expression must either be null or a function, not ' +
        typeof superClass);
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    if (superClass) {
      if (Object.setPrototypeOf) {
        Object.setPrototypeOf(subClass, superClass);
      } else {
        subClass.prototype.__proto__ = superClass;
      }
    }
    subClass.prototype._super = superClass.prototype;
  }

  getClass(className: string): Function | undefined {
    let f = undefined;
    if (this.declaredClasses) {
      f = this.declaredClasses.get(className);
    }
    return f;
  }
}

declare global {
  interface Window {
    classMnr?: ClassesAdapter;
  }
}

window.classMnr = new ClassesAdapter();

Поэтому в моем old Javascript Мне просто нужно сделать следующую замену (мой старый код):

classMnr.declareChildClass(B, classMnr.A, '1.0');

На (новый код):

window.classMnr.declareChildClass(B, window.classMnr.getClass('A'), '1');

И (старый код):

var a = new window.myClasses.a('TestA', {data: 'test'}),

По (новый код):

const AClass = window.qlcClassMnr.getClass('A');
const AObject = new AClass('TestA', {data: 'test'});
0 голосов
/ 07 мая 2020

Итак, я протестировал замену вашей старой функции прототипа ES5 a на новую ES6 class a. Это вызывает ошибку:

Uncaught TypeError: конструктор класса a не может быть вызван без 'new'

Разверните этот фрагмент, чтобы проверить:

function declareClass(classModel, version) {

  if (typeof classModel !== 'function') {
    throw classModel + ' cannot be used as a class as it is not a function';
  }

  if(window.myClasses === undefined) {
    window.myClasses = {};
  }

  window.myClasses[classModel.name] = classModel;

  classModel.prototype._version   = version || '1.0';
  classModel.getVersion =  function() {
    return this.prototype._version;
  };
};

function declareChildClass(subClass, superClass, version) {

  declareClass(subClass, version);

  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
  if (superClass) {
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

  subClass.prototype._super = superClass.prototype;
};

(function() {

  class a {
    constructor(id, properties) {
        this.id = id;
        this.properties = properties
    }

    echo() {
        console.log('ID [' + this.id + ']');
        console.log('Properties');
        console.log(this.properties);
    }
  }

  window.declareClass(a, 'A1')

})();

(function() {
  window.declareChildClass(b, window.myClasses.a, 'B2')

  function b(id) {
    window.myClasses.a.call(this, 'B' + id, {})
  }
})();

var a = new window.myClasses.a('TestA', {data: 'test'}),
    b = new window.myClasses.b('X');

a.echo();
b.echo();

Он происходит из этой строки: window.myClasses.a.call(this, 'B' + id, {}).

Поскольку myClasses.a является классом ES6, он не поддерживает вызов без new ключевые слова. К сожалению, внутри vanilla JS нет решения этой проблемы.

Но поскольку вы используете TS, вы можете установить цель транспиляции на ES5, чтобы принудительно преобразовать блестящий синтаксис class обратно в базовую функцию прототипа старой школы. Результат будет:

var a = /** @class */ (function () {
    function a(id, properties) {
        this.id = id;
        this.properties = properties;
    }
    a.prototype.echo = function () {
        console.log('ID [' + this.id + ']');
        console.log('Properties');
        console.log(this.properties);
    };
    return a;
}());

И эта ошибка исчезла, разверните этот фрагмент для проверки:

function declareClass(classModel, version) {

  if (typeof classModel !== 'function') {
    throw classModel + ' cannot be used as a class as it is not a function';
  }

  if(window.myClasses === undefined) {
    window.myClasses = {};
  }

  window.myClasses[classModel.name] = classModel;

  classModel.prototype._version   = version || '1.0';
  classModel.getVersion =  function() {
    return this.prototype._version;
  };
};

function declareChildClass(subClass, superClass, version) {

  declareClass(subClass, version);

  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
  }
  subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });
  if (superClass) {
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

  subClass.prototype._super = superClass.prototype;
};

(function() {

  "use strict";
  var a = /** @class */ (function () {
      function a(id, properties) {
          this.id = id;
          this.properties = properties;
      }
      a.prototype.echo = function () {
          console.log('ID [' + this.id + ']');
          console.log('Properties');
          console.log(this.properties);
      };
      return a;
  }());


  window.declareClass(a, 'A1')

})();

(function() {
  window.declareChildClass(b, window.myClasses.a, 'B2')

  function b(id) {
    window.myClasses.a.call(this, 'B' + id, {})
  }
})();

var a = new window.myClasses.a('TestA', {data: 'test'}),
    b = new window.myClasses.b('X');

a.echo();
b.echo();

Заключение, да, вы можете смешать код TS со старым кодом JS и заставить их работать вместе. Но вам нужно немного изменить код TS, чтобы привести его в соответствие со старой парадигмой, например, вы не используете модуль в старом коде, а только глобальные переменные. Вы можете также отказаться от модуля в TS.

class a {
    id:string;
    properties:{};

    constructor(id:string, properties:{}) {
        this.id = id;
        this.properties = properties
    }

    echo() {
        console.log('ID [' + this.id + ']');
        console.log('Properties');
        console.log(this.properties);
    }
}

;(window as any).declareClass(a, 'A1');

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

bridgeCode.ts

import a from './myClassA.ts';

;(window as any).declareClass(a, 'A1');
...