Лучшие практики для наследования на основе прототипов/прототипов в новом JavaScript (ES6) и TypeScript?

Вот несколько старых вопросов, в которых обсуждается прототипное наследование и делегирование Javascript, например:

Мне интересно, какова текущая (2018) рекомендация по использованию прототипов/прототипного наследования в Javascript.

Насколько я понимаю, более новые версии JavaScript (ES6) и TypeScript больше ориентированы на традиционное наследование на основе классов. (Сам я еще не использовал ES6 или TS на практике.) Верно ли это наблюдение?

На самом деле этот код на основе классов действительно прост и понятен:

class A { a: "a" }
class B extends A { b: "b" }
let a = new A(), b = new B();

EDIT 2: в TypeScript это будет:

class A { a = "a" }
class B extends A { b = "b" }
let a = new A(), b = new B();

EDIT: на самом деле синтаксис ES6 сложнее:

class A { constructor() { this.a = "a"; } }
class B extends A { constructor() { super(); b = "b"; } }
let a = new A(), b = new B();

Для использования прототипов есть больше вариантов, и на самом деле я пока не нашел ни одного столь же простого и "хорошего".

EDIT: Чего я хочу добиться, так это того, что я создаю b как экземпляр B с прототипом A таким образом, чтобы при динамическом изменении свойства A изменение также влияло на b :

Простой подход:

var A = { a: "a" }
var B = Object.create(A, {b: {value: "b"}});
var a = Object.create(A), // direct instance of A
    b = Object.create(B); // indirect instance of A
console.log(b.a); // "a"
A.a = "a++"; // change the base prototype (will affect B, a, and b)
console.log(b.a); // "a++"

Было бы намного лучше, если бы второй аргумент также мог быть простым объектом с парами ключ-значение, а не дескрипторами свойств (см. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create)

В большинстве случаев используется функция конструктора, например. в https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

function A() { this.a = "a"; }
function B() { this.b = "b"; }
B.prototype = new A();
var a = new A(), b = new B();
console.log(b.a); // "a"
A.a = "a++";
console.log(b.a); // return "a" instead of "a++" as b.a is overwritten in constructor

Кроме того, это не так приятно, так как здесь вы не можете изменить A.a таким образом, чтобы b.a также изменился, что является IMO ключевым моментом в прототипном наследовании. Так может это?

function A() {}
A.prototype.a = "a";
function B() {}
B.prototype = Object.create(A.prototype);
B.prototype.b = "b";
var a = new A(), b = new B();
function A() { this.a = "a"; }
function B() { this.b = "b"; }
B.prototype = new A();
var a = new A(), b = new B();
console.log(b.a); // "a"
A.a = "a++";
console.log(b.a); // still "a" instead of "a++"

Не дает ожидаемого результата. И, ну, вы не хотите писать это, не так ли?

Конечно, вы можете поместить создание в функцию конструктора, как описано https://stackoverflow.com/a/16872315/1480587 но я думаю, что это все еще не так красиво и просто для синтаксиса класса. На самом деле, я ищу что-то вроде этого (похоже на объявление объекта Kotlin ):

object A { a: "a" }
object B extends A { b: "b" }
let a = new A(), b = new B();

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

Особенно, если вы хотите использовать некоторую инкапсуляцию и иметь члены частного объекта, невидимые для клонированных объектов?

Предоставляет ли TypeScript хорошее решение?

Перейти на Котлин?

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


person Peter T.    schedule 01.03.2018    source источник
comment
На самом деле, в ES6 class — это просто синтаксический сахар для прототипов, попробуйте class X{} и console.log(typeof X).   -  person georg    schedule 01.03.2018
comment
Подходит ли здесь TypeScript? Да! Классы ES6. Добро пожаловать в современный JS. Вам будет трудно заставить типы TS работать на вас без классов.   -  person Estus Flask    schedule 01.03.2018
comment
Я только что отредактировал свой вопрос, чтобы прояснить, что я ищу. Из ваших комментариев кажется, что настоящее наследование на основе прототипов не используется. Что на самом деле грустно, так как я думаю, что это одна из действительно приятных вещей в JS. Ну, было бы, если бы у нас был хороший синтаксис для этого. @georg: ИМО, функции конструктора в JS на самом деле не помогают с правильным наследованием прототипов, поэтому я понимаю, что класс ES6 в этом случае не помогает.   -  person Peter T.    schedule 01.03.2018


Ответы (4)


Простой подход Object.create

Тогда используйте это. Кажется, для того, что вы хотите сделать, достаточно двух объектов, один из которых наследуется друг от друга. Вам не нужны никакие конструкторы для инициализации, и, следовательно, нет синтаксиса class.


Кстати, для упрощения я бы не стал использовать второй аргумент для Object.create, когда вам не нужны пользовательские дескрипторы свойств. Просто сделать

var B = Object.create(A);
B.b = "b";

или, в одном выражении,

var B = Object.assign(Object.create(A), {
  b: "b",
});
person Bergi    schedule 01.03.2018

Может быть, я играю с простой вспомогательной функцией, например:

/**
 * Create a new object with `prototype` as its prototype.
 * The (optional) properties will be copied to the newly created object
 * This is similar to Object.create(), however the properties passed is a plain
 * object, not a object with property descriptors,
 * see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
 *
 * @param prototype (object)
 * @param properties (object)
 * @return {prototype}
 */
function clonePrototype(prototype, properties) {
    var pDef = {};
    for(var key in (properties||{})) {
        if (properties.hasOwnProperty(key)) { pDef[key] = {value: properties[key]}; }
    }
    return Object.create(prototype, pDef);
}

Таким образом, я могу сделать то, что хотел:

var a = { a: "a" };
var b = clonePrototype(a, { b: "b" });
console.log(b.a); // "a"
a.a = "a++";
console.log(b.a); // "a++"

.... комментарии и предложения приветствуются.

person Peter T.    schedule 01.03.2018

В качестве альтернативы подчеркивание и lodash предоставляют _.create(), которые помогают создать новый объект с инициализированным прототипом:

var a = { a: "a" };
var b = _.create(a, { b: "b" });
console.log(b.a); // "a"
a.a = "a++";
console.log(b.a); // "a++"
person Peter T.    schedule 02.03.2018

Я только что нашел то, что искал: в ES6 "литералы объектов расширены для поддержки настройки прототип в процессе сборки." Это действительно просто и удобно!!

var obj = {
    // Sets the prototype. "__proto__" or '__proto__' would also work.
    __proto__: theProtoObj,
    // Computed property name does not set prototype or trigger early error for
    // duplicate __proto__ properties.
    ['__proto__']: somethingElse,
    // ...
};
person Peter T.    schedule 18.03.2018