Как достичь псевдоклассического наследования прямо в объявлении класса?

Примечание:

Как говорят ответы, код, предложенный в вопросе, действительно НЕ действительно наследует (в противном случае это становится ответом, а не вопросом..) из-за некоторых проблем, описанных в вопросе и в моем Комментарии. Он работает как ожидаемый подделка наследования (и даже не прототипа).


  • Резюме

    Короче говоря, сделайте его похожим на то, что мы пишем общий язык OO, а не javascript, но сохраняем правильность наследования.

  • История

    Object.create - это хороший способ достижения прототипного наследования, но он немного запутан в умении напечатал и новом.

    Существуют различные способы написания javascript-кода, как мы пишем другие языки OO с псевдоклассическим шаблоном. Поскольку он псевдо -классический, мы должны правильно относиться к базовому прототипному наследованию javascript.

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

    • Я не могу избавиться от return в объявлении класса или наследование не будет работать.

    • У меня нет способа, кроме pass this в объявлении класса, чтобы вернуть возвращающее сообщение, что есть this.

    • Я также хочу избавиться от function (instance, _super) {, но у меня пока нет хорошей идеи.

    • Статические (собственные свойства) класса не наследуются.

    Решение будет более синтаксическим сахаром, чем существующие рамки, применим хороший шаблон.


Функция _extends:

function _extends(baseType) {
    return function (definition) {
        var caller=arguments.callee.caller;
        var instance=this;

        if(!(instance instanceof baseType)) {
            (caller.prototype=new baseType()).constructor=caller;
            instance=new caller();
        }

        var _super=function () {
            baseType.apply(instance, arguments);
        };

        definition(instance, _super);
        return instance;
    };
}

Класс Abc:

function Abc(key, value) {
    return _extends(Object).call(this, function (instance, _super) {
        instance.What=function () {
            alert('What');
        };

        instance.getValue=function () {
            return 333+Number(value);
        };

        instance.Value=instance.getValue();
        instance.Key=key;
    });
}

Класс Xyz:

function Xyz(key, value) {
    return _extends(Abc).call(this, function (instance, _super) {
        _super(key, value);

        instance.That=function () {
            alert('That');
        };
    });
}

Пример кода:

var x=new Xyz('x', '123');
alert([x.Key, x.Value].join(': ')+'; isAbc: '+(x instanceof Abc));

var y=new Xyz('y', '456');
alert([y.Key, y.Value].join(': ')+'; isAbc: '+(y instanceof Abc));

var it=new Abc('it', '789');
alert([it.Key, it.Value].join(': ')+'; isAbc: '+(it instanceof Abc));
alert([it.Key, it.Value].join(': ')+'; isXyz: '+(it instanceof Xyz));

x.What();
y.That();

it.What();
it.That(); // will throw; it is not Xyz and does not have That method

Ответ 1

Нет. Нет. Этого не будет. Вы делаете наследование в JavaScript все неправильно. Ваш код дает мне мигрень.

Создание псевдоклассического шаблона наследования в JavaScript

Если вам нужно что-то похожее на классы в JavaScript, тогда есть много библиотек, которые предоставляют вам это. Например, используя augment, вы можете перестроить свой код следующим образом:

var augment = require("augment");

var ABC = augment(Object, function () {
    this.constructor = function (key, value) {
        this.key = key;
        this.value = value;
    };

    this.what = function () {
        alert("what");
    };
});

var XYZ = augment(ABC, function (base) {
    this.constructor = function (key, value) {
        base.constructor.call(this, key, value);
    };

    this.that = function () {
        alert("that");
    };
});

Я не знаю о вас, но для меня это очень похоже на классическое наследование на С++ или Java. Если это решает вашу проблему, отлично! Если это не так, продолжайте чтение.

Изоморфизм прототипа класса

Во многом прототипы похожи на классы. На самом деле прототипы и классы настолько похожи, что мы можем использовать прототипы для моделирования классов. Сначала рассмотрим как работает прототипное наследование:

Вышеприведенное изображение было взято из следующего answer. Я предлагаю вам внимательно прочитать его. Диаграмма показывает нам:

  • Каждый конструктор имеет свойство prototype, которое указывает на объект-прототип функции-конструктора.
  • Каждый прототип имеет свойство constructor, которое указывает на конструкторную функцию объекта-прототипа.
  • Мы создаем экземпляр из функции-конструктора. Однако экземпляр фактически наследует от prototype, а не конструктора.

Это очень полезная информация. Традиционно мы всегда создавали конструкторную функцию, а затем устанавливаем ее свойства prototype. Однако эта информация показывает нам, что мы можем сначала создать объект-прототип, а затем определить вместо него свойство constructor.

Например, традиционно мы можем написать:

function ABC(key, value) {
    this.key = key;
    this.value = value;
}

ABC.prototype.what = function() {
    alert("what");
};

Однако используя наше новообретенное знание, мы можем написать то же самое, что:

var ABC = CLASS({
    constructor: function (key, value) {
        this.key = key;
        this.value = value;
    },
    what: function () {
        alert("what");
    }
});

function CLASS(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

Как вы видите, инкапсуляция легко достигается в JavaScript. Все, что вам нужно сделать, это думать боком. Наследование, однако, является другой проблемой. Вам нужно сделать немного больше работы для достижения наследования.

Наследование и супер

Посмотрите, как augment достигает наследования:

function augment(body) {
    var base = typeof this === "function" ? this.prototype : this;
    var prototype = Object.create(base);
    body.apply(prototype, arrayFrom(arguments, 1).concat(base));
    if (!ownPropertyOf(prototype, "constructor")) return prototype;
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

Обратите внимание, что последние три строки такие же, как у CLASS из предыдущего раздела:

function CLASS(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

Это говорит нам, что, когда у нас есть объект-прототип, нам нужно только получить его свойство конструктора и вернуть его.

Первые три строки дополнения используются для:

  • Получить прототип базового класса.
  • Создайте прототип производного класса, используя Object.create.
  • Заполните прототип производного класса с указанными свойствами.

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

Охватывание True Prototypal Inheritance

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

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

Однако, как только вы поймете истинное прототипное наследование, вы никогда не захотите вернуться к классическому наследованию. Я тоже пробовал до реализовать классическое наследование сверху прототипное наследование как новичок. Теперь, когда я понимаю, как работает истинное прототипное наследование, я пишу код следующим образом:

function extend(self, body) {
    var base = typeof self === "function" ? self.prototype : self;
    var prototype = Object.create(base, {new: {value: create}});
    return body.call(prototype, base), prototype;

    function create() {
        var self = Object.create(prototype);
        return prototype.hasOwnProperty("constructor") &&
            prototype.constructor.apply(self, arguments), self;
    }
}

Вышеупомянутая функция extend очень похожа на augment, Однако вместо того, чтобы возвращать функцию-конструктор, он возвращает объект-прототип. Это на самом деле очень аккуратный трюк, который позволяет наследовать статические свойства. Вы можете создать класс, используя extend следующим образом:

var Abc = extend(Object, function () {
    this.constructor = function (key, value) {
        this.value = 333 + Number(value);
        this.key = key;
    };

    this.what = function () {
        alert("what");
    };
});

Наследование так же просто:

var Xyz = extend(Abc, function (base) {
    this.constructor = function (key, value) {
        base.constructor.call(this, key, value);
    };

    this.that = function () {
        alert("that");
    };
});

Помните, однако, что extend не возвращает функцию конструктора. Он возвращает объект-прототип. Это означает, что вы не можете использовать ключевое слово new для создания экземпляра класса. Вместо этого вам нужно использовать new как метод, следующим образом:

var x = Xyz.new("x", "123");
var y = Xyz.new("y", "456");
var it = Abc.new("it", "789");

На самом деле это хорошо. Ключевое слово new считается вредоносным, и я настоятельно рекомендую вам прекратить использование. Например, это невозможно использовать apply с new ключевое слово. Однако можно использовать apply с помощью метода new следующим образом:

var it = Abc.new.apply(null, ["it", "789"]);

Так как Abc и Xyz не являются конструкторскими функциями, мы не можем использовать instanceof для проверки того, является ли объект экземпляром Abc или Xyz. Однако это не проблема, потому что у JavaScript есть метод под названием isPrototypeOf, который проверяет, является ли объект является прототипом другого объекта:

alert(x.key + ": " + x.value + "; isAbc: " + Abc.isPrototypeOf(x));
alert(y.key + ": " + y.value + "; isAbc: " + Abc.isPrototypeOf(y));

alert(it.key + ": " + it.value + "; isAbc: " + Abc.isPrototypeOf(it));
alert(it.key + ": " + it.value + "; isXyz: " + Xyz.isPrototypeOf(it));

Фактически isPrototypeOf более мощный, чем instanceof, потому что он позволяет нам проверять, распространяется ли один класс другого класса:

alert(Abc.isPrototypeOf(Xyz)); // true

Помимо этого незначительного изменения все остальное работает так же, как и раньше:

x.what();
y.that();

it.what();
it.that(); // will throw; it is not Xyz and does not have that method

Смотрите демонстрацию для себя: http://jsfiddle.net/Jee96/

Что еще предлагает истинное прототипное наследование? Одно из самых больших преимуществ истинного прототипного наследования заключается в том, что нет никакого различия между нормальными свойствами и статическими свойствами, позволяющими писать такой код:

var Xyz = extend(Abc, function (base) {
    this.empty = this.new();

    this.constructor = function (key, value) {
        base.constructor.call(this, key, value);
    };

    this.that = function () {
        alert("that");
    };
});

Обратите внимание, что мы можем создавать экземпляры класса из самого класса, вызывая this.new. Если this.constructor еще не определено, он возвращает новый неинициализированный экземпляр. В противном случае он возвращает новый инициализированный экземпляр.

Кроме того, поскольку Xyz является объектом прототипа, мы можем напрямую обращаться к Xyz.empty (т.е. empty является статическим свойством Xyz). Это также означает, что статические свойства автоматически унаследованы и ничем не отличаются от обычных свойств.

Наконец, поскольку класс доступен из определения класса как this, вы можете создать вложенные классы, которые наследуют от класса, в который они вложены, используя extend следующим образом:

var ClassA = extend(Object, function () {
    var ClassB = extend(this, function () {
        // class definition
    });

    // rest of the class definition

    alert(this.isPrototypeOf(ClassB)); // true
});

Смотрите демонстрацию для себя: http://jsfiddle.net/Jee96/1/

Ответ 3

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

Как я прокомментировал; если у вас возникли проблемы с синтаксисом JavaScript, тогда typescript может быть хорошей альтернативой.

Вот вспомогательная функция, которую я использую для наследования и переопределения (вызов супер) с использованием JavaScript (без Object.create)

var goog={};//inherits from closure library base
  //http://docs.closure-library.googlecode.com/git/closure_goog_base.js.source.html#line1466
  // with modifications for _super
goog.inherits = function(childCtor, parentCtor) {
  function tempCtor() {};
  tempCtor.prototype = parentCtor.prototype;
  childCtor.prototype = new tempCtor();
  childCtor.prototype.constructor = childCtor;
  // modified _super
  childCtor.prototype._super = parentCtor.prototype;
};

// Parent class dev
var Parent = function(){};
Parent.prototype.sayHi=function(){
  console.log("hi from Parent");
}
// end class
// Child class dev
var Child = function(){}
goog.inherits(Child,Parent);
Child.prototype.sayHi=function(){
  //_super only works on parent.prototype
  //this is where functions are usually defined
  //have to add this. to call _super as well
  this._super.sayHi();
  console.log("hi from Child");
}
// end Child

//code to test
var c = new Child();
c.sayHi();//hi from Parent and hi from Child

Даже если вы найдете способ написать вспомогательные функции и сделать функции конструктора JS похожими на классы Java, вы должны понять прототип.

Ответ 4

Вы всегда можете попробовать jOOP, хотя для этого требуется jQuery.

https://github.com/KodingSykosis/jOOP

var myClass = $.cls({
    main: function() {
        $('body').append('My App is loaded <br/>');
    }
});

var mySecondClass = $.cls({
    main: function() {
        this._super();
        $('body').append('My Second App is loaded <br/>');
    }
}, myClass);

var app = new mySecondClass();

http://jsfiddle.net/kodingsykosis/PrQWu/

Ответ 5

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

http://cdpn.io/Jqhpc

var Parent = function Parent () {
  this.fname = 'Bob';
  this.lname = 'Jones';
};
Parent.prototype.getFname = function getFname () {
  return this.fname;
};
Parent.prototype.getLname = function getLname () {
  return this.lname;
};

var Child = function Child () {
  this.fname = 'Jim';
};
Child.prototype = Parent.prototype;

var child = new Child();
document.write(child.getFname()); //=> Jim