Многоуровневое наследование, супер методы, наследование дескрипторов
Всем привет :)
Этот пост отвечает на следующие вопросы: 1. Как написать наследование на чистом javascript 2. Как написать многоуровневое наследование 3. Как написать наследование дескрипторов 4. Как написать многоуровневое наследование с наследованием дескрипторов 5. Как вызывать супер методы Никаких библиотек не подключено, весь используемый код расположен ниже. В качестве "многоуровневое наследования" выступают три класса: Animal -> Mammal - Cat. В качестве супер-метода выступает метод destroy(). Данный пост отражает точку зрения автора и носит рекомендательный характер. /********************** * HELPERS * *********************/ /** * @borrows toString as toString */ Object.toString = Object.prototype.toString; /** * Returns "true" if a value is date * @param {*} v A value * @return {Boolean} */ Date.isDate = function(v) { return Object.toString.call(v) === '[object Date]'; }; /** * Returns "true" if a value is regular expression * @param {*} v A value * @return {Boolean} */ RegExp.isRegExp = function(v) { return Object.toString.call(v) === '[object RegExp]'; }; /** * Returns "true" if a value is array * @param {*} v A value * @return {Boolean} */ Array.isArray = Array.isArray || function(v) { return Object.toString.call(v) === '[object Array]'; }; /** * Creates clone of object * Not working with DOM elements * [url]http://stackoverflow.com/a/728694[/url] * [url]https://github.com/andrewplummer/Sugar/blob/master/lib/object.js#L328[/url] * @param {Object} obj * @return {Object} */ Object.clone = function(obj) { // Number, String, Boolean, Function, null, undefined if (null === obj || 'object' !== typeof obj) { return obj; } // Date and RegExp if (Date.isDate(obj) || RegExp.isRegExp(obj)) { return new obj.constructor(obj); // Array and Object } else { var copy = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj)); for (var key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = this.clone(obj[key]); } } return copy; } }; /** * Inherits a target (Class_1) by a source (Class_2) * @param {Object} target * @param {Object} source */ Object.inherit = function(target, source) { target._super = source; target.prototype = Object.create(target._super.prototype, target._super.descriptor); target.prototype.constructor = target; }; /********************** * CLASSES * *********************/ /** * Class Animal * @param {Number} legs * @constructor */ function Animal(legs) { var descriptor = Object.clone(Animal.descriptor); descriptor.legs.value = legs; Object.defineProperties(this, descriptor); } Animal.prototype.destroy = function() { console.log('Animal.destroy()'); for(var key in Animal.descriptor) { delete this[key]; } }; Animal.prototype.run = function() { console.log('Animal.run()'); }; Animal.descriptor = { legs: { value: null, enumerable: true, configurable: true } }; // ------------------------ // Inheritance Object.inherit(Mammal, Animal); /** * Class Mammal * @param {Number} legs * @param {Number} teeth * @constructor */ function Mammal(legs, teeth) { // call the Animal constructor Mammal._super.apply(this, arguments); var descriptor = Object.clone(Mammal.descriptor); descriptor.teeth.value = teeth; Object.defineProperties(this, descriptor); } Mammal.prototype.destroy = function() { console.log('Mammal.destroy()'); Mammal._super.prototype.destroy.call(this); for(var key in Mammal.descriptor) { delete this[key]; } }; Mammal.prototype.walk = function() { console.log('Mammal.walk()'); }; Mammal.descriptor = { teeth: { value: null, enumerable: true, configurable: true } }; // ------------------------ // Inheritance Object.inherit(Cat, Mammal); /** * Class Cat * @param {Number} legs * @param {Number} teeth * @param {Number} tails * @constructor */ function Cat(legs, teeth, tails) { // call the Mammal constructor Cat._super.apply(this, arguments); var descriptor = Object.clone(Cat.descriptor); descriptor.tails.value = tails; Object.defineProperties(this, descriptor); } Cat.prototype.destroy = function() { console.log('Cat.destroy()'); Cat._super.prototype.destroy.call(this); for(var key in Cat.descriptor) { delete this[key]; } }; Cat.prototype.climb = function() { console.log('Cat.climb()'); }; Cat.descriptor = { tails: { value: null, enumerable: true, configurable: true } }; /******************** * USAGE / INSTANCE ********************/ var cat = new Cat(8, 100, 3); alert(JSON.stringify(cat)); console.info('created', cat); console.log('cat instanceof Cat', cat instanceof Cat); console.log('cat instanceof Mammal', cat instanceof Mammal); console.log('cat instanceof Animal', cat instanceof Animal); cat.run(); // Animal.prototype.run cat.walk(); // Mammal.prototype.walk cat.climb(); // Cat.prototype.climb cat.destroy(); // Cat -> Mammal -> Animal alert(JSON.stringify(cat)); console.info('destroyed', cat); |
Цитата:
Цитата:
Кстати, мне последнее время нравится идея создавать объекты, не описывая конструкторы: var Animal = { destroy: function () { console.log('Animal.destroy()'); }, run: function () { console.log('Animal.run()'); } }; var Mammal = Object.create(Animal); Object.assign(Mammal, { walk: function () { console.log('Mammal.walk()'); } }); var Cat = Object.create(Mammal);как-то так… правда тут с instanceof непонятки могут возникнуть |
Цитата:
Octane, согласен, ES6 - крутая вещь |
Цитата:
Object.toString === Function.prototype.toString // true |
Цитата:
Цитата:
Немного переписал функцию наследования согласно тому, что есть в Node.js Если точнее, то вместо Class._super стало Class.super_ Цитата:
Цитата:
|
Цитата:
|
Цитата:
Единственное неудобство в instanceof, но можно воспользоваться isPrototypeOf var barsic = Object.create(Cat); if (Cat.isPrototypeOf(barsic)) { … } а еще getPrototypeOf (IE9+) есть, так что без super вполне можно обойтись Object.getPrototypeOf(Cat) → Mammal http://www.reddit.com/r/javascript/c...s_approach_is/ Не навязываю, что надо делать именно так, просто как пример интересного подхода Цитата:
|
Цитата:
Т.е. моя позиция в том, что придется описывать и то и другое. А еще у нас есть дескрипторы. Цитата:
Цитата:
Object.getPrototypeOf(object) Цитата:
|
Кстати, тут ни разу не множественное наследование, у тебя всегда один суперкласс.
Цитата:
Цитата:
var Animal = {}; var Mammal = Object.create(Animal); var Cat = Object.create(Mammal); var barsic = Object.create(Cat); alert(Animal.isPrototypeOf(barsic)); // true alert(Mammal.isPrototypeOf(barsic)); // true alert(Cat.isPrototypeOf(barsic)); // true Цитата:
Cat._super.prototype.destroy.call(this); будет Object.getPrototypeOf(Cat).destroy.call(this);разве что в большем количестве символов неудобство Цитата:
|
Цитата:
Цитата:
var Animal = {}; var Cat = Object.create(Animal); var obj = new Cat(); console.log(obj); Конечно, тут можно заявлять, что встроенные операторы языка нам не нужны, но если они есть, почему бы ими не воспользоваться? Т.е. ты ломаешь то, что заложено в язык и к чему привыкли js-разработчики. Цитата:
Вместе с тем, считаю очень удобным то, что, например, приватные свойства this._property; можно делать неперебираемыми. Цитата:
2. "либо отдельно set-метод для каждого свойства/группы свойств, что мне кажется лучше" не вижу причин, по которым нельзя сделать те же самые сет-методы в "стандартной" реализации Впрочем, сколько людей, столько и мнений :) |
честно говоря сразу хотел назвать прототипы с маленькой буквы, потом зачем-то оставил, как в примере по ссылке
|
бывает :)
Я вот теперь не могу тему переименовать. Только название получилось :D |
с чпу наверное уже ничего не сделать
|
Цитата:
|
Ща вам батя покажет класс
|
о нееет, пощади!
|
новая версия Class, к суперметодам обращаемся через $methodName. если свойство начинается или заканчивается на жетсткий побел, то оно становится не итерируемым. вот и вся суть
function Class(){ } Class.extend = function (Prototype) { Prototype.prototype = this.prototype; constructor.prototype = new Prototype(); constructor.extend = this.extend; function constructor() { if (this.constructor) { this.constructor.apply(this, arguments); } for (var key in this) if (this.hasOwnProperty(key) && /(^_)|(_$)/.test(key)) { Object.defineProperty(this, key, { value : this[key], writable : true, configurable: true, enumerable : false }); } } for (var key in constructor.prototype) if (constructor.prototype.hasOwnProperty(key)) { if (/(^_)|(_$)|(constructor)/.test(key)) { Object.defineProperty(constructor.prototype, key, { value : constructor.prototype[key], writable : true, configurable: true, enumerable : false }); } if (key in this.prototype && typeof this.prototype[key] === 'function') { Object.defineProperty(constructor.prototype, '$' + key, { value : this.prototype[key], writable : false, configurable: false, enumerable : false }); } } return constructor; }; //############################################################## // проверяем суперметоды alert('проверяем суперметоды') var Animal = Class.extend(function () { this.say = function () { alert('Animal'); } }); var Cat = Animal.extend(function () { this.say = function () { this.$say(); alert('Cat'); } }); new Cat().say(); // Animal, Cat // проверяем приватные alert('проверяем приватные') var Animal = Class.extend(function () { this.say = function () { }; this._need = function () { } }); for (var key in new Animal) alert(key) // say // проверяем instanceof alert('проверяем instanceof') alert( new Cat() instanceof Cat ) // true |
Ну а дальше вы можете расширять этот каркас всякими деструкторами и евент эмиттерами:
var EventEmitter = Class.extend(function () { this.constructor = function () { this._handlersStore = {} }; this.on = function (type, handler) { var handlersStore = this._handlersStore; var handlers = handlersStore[type] || (handlersStore[type] = []); handlers.push(handler); }; this.emit = function (type) { var self = this; var eventArgs = [].slice.call(arguments, 1); var handlersStore = this._handlersStore; var handlers = handlersStore[type] || []; handlers.forEach(function (handler) { handler.apply(self, eventArgs); }); }; }); var q = new EventEmitter(); q.on('click', function () { alert('on click!'); }); q.emit('click'); |
1. Если я правильно понял, то это
function constructor() { if (this.constructor) { this.constructor.apply(this, arguments); } } правильней написать так function constructor() { if (this.$constructor) this.$constructor.apply(this, arguments); if (this.constructor) this.constructor.apply(this, arguments); } 3. Если уж так писать циклы, обертки и т.п. то можно и миксыны сделать 4. Нет той гибкости, кот. есть при использовании ссылки на СуперКласс (на мой взгляд) 5. Кроме того, стороннему разработчику придется разбираться в твоей капусте :) |
Цитата:
|
Цитата:
Цитата:
Цитата:
Цитата:
|
Maxmaxmaximus11
var A = Class.extend(function () { this.method = function () { } }); var B = A.extend(function () { this.method = function () { this.$method == B.prototype.method; // -> fail! } }); var C = B.extend(function () { this.method = function () { this.$method == B.prototype.method; } }); |
![]() спасибо, баг нашел, короч влом фиксить, все ровно это ни кто юзать не будет. но мой способ поддерживает множественное наследование) function Class(){ } Class.extend = function (Prototype) { Prototype.prototype = this.prototype; constructor.prototype = new Prototype(); constructor.extend = this.extend; function constructor() { if (this.constructor) { this.constructor.apply(this, arguments); } for (var key in this) if (this.hasOwnProperty(key) && /(^_)|(_$)/.test(key)) { Object.defineProperty(this, key, { value : this[key], writable : true, configurable: true, enumerable : false }); } } for (var key in constructor.prototype) if (constructor.prototype.hasOwnProperty(key)) { if (/(^_)|(_$)|(constructor)/.test(key)) { Object.defineProperty(constructor.prototype, key, { value : constructor.prototype[key], writable : true, configurable: true, enumerable : false }); } if (key in this.prototype && typeof this.prototype[key] === 'function') { Object.defineProperty(constructor.prototype, '$' + key, { value : this.prototype[key], writable : false, configurable: false, enumerable : false }); } } return constructor; }; //################################# var Animal = Class.extend(function () { this.say = function () { alert('Animal') } }); var Cat = Animal.extend(function () { this.say = function () { this.$say(); alert('Cat') } }); var Ashot = Cat.extend(function () { this.say = function () { this.$say(); alert('Ashot') } }); new Ashot().say(); |
Цитата:
var obj = { method: function() { // тут этот объект вызывает свой супер метод } }; С твоим подходом проблемы начнутся, если вызвать метод в контексте другого объекта. Т.е. "цепочка" супер методов в лучшем случае не будет вызвана, в худшем нарушена. <script src="http://nervgh.github.io/js/yum/yum.js"></script> <script> /********************** * CLASSES * *********************/ /** * Class Animal * @param {Number} legs * @constructor */ function Animal(legs) {} Animal.prototype.destroy = function() { console.log('Animal.destroy()'); }; // ------------------------ // Inheritance Object.inherit(Mammal, Animal); /** * Class Mammal * @constructor */ function Mammal(legs, teeth) { // call the Animal constructor Mammal.super_.apply(this, arguments); } Mammal.prototype.destroy = function() { console.log('Mammal.destroy()'); Mammal.super_.prototype.destroy.call(this); }; // ------------------------ // Inheritance Object.inherit(Cat, Mammal); /** * Class Cat * @constructor */ function Cat(legs, teeth, tails) { // call the Mammal constructor Cat.super_.apply(this, arguments); } Cat.prototype.destroy = function() { console.log('Cat.destroy()'); Cat.super_.prototype.destroy.call(this); }; /******************** * USAGE / INSTANCE ********************/ var cat = new Cat(); cat.destroy(); // Cat -> Mammal -> Animal; console.log('--------------------'); cat.destroy.call(this); // Cat -> Mammal -> Animal; </script> Цитата:
|
ладно ладно) убедили, я просто щас другим занят)) наварганил прямо тут в текстэриа)
|
Цитата:
var A = Class.extend(function () { this.method = function () { } }); var B = A.extend(function () { this.method = function () { return this.$method === A.prototype.method; } }); var C = B.extend(function () { this.method = function () { return this.$method === B.prototype.method; } }); var b = new B(); var c = new C(); console.log(b.method()); console.log(c.method()); |
nerv_, ты написал как должно быть, а я - как происходит при использовании реализации Class.extend Maxmaxmaximus11.
|
Sweet, я говорю, что "fail" происходит правильно в твоем примере.
Или я опять тебя не понял :) |
nerv_, :) Все всё поняли.
|
Часовой пояс GMT +3, время: 10:28. |