Понимание ООП на джаваскрипте (ES5), часть 2

Original author: Sorella
  • Translation
  • Tutorial


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

Для полноты статьи и единого стиля, перевод начинается с вопросов наследования, несмотря на то, что они уже были упомянуты в конце первой части. Далее рассматриваются разнообразные задачи наследования так, как их рассмотрел автор. Надо сказать, что автор широко использует новые конструкции ES5 (объяснив это в конце), которые работают не во всех браузерах и заслоняют от понимания реализацию их на низком уровне языка, на котором они изначально применялись. Для настоящего понимания наследования следует обратиться к более глубокому разбору реализаций или к реализациям методов-обёрток из ES5: Object.create, Object.defineProperty, Function.bind, get и set literals, Object.getOwnPropertyNames, Object.defineProperty, Object.getOwnPropertyDescriptor, Object.getPrototypeOf. Часть их разбирается в статье (Object.create, get и set, Object.defineProperty, bind), но не всегда в порядке появления. Таким образом, статья стремится преподнести не реализацию наследования вообще, а ту реализацию, которую успели формализовать в рабочем черновике стандарта EcmaScript 5. Это лучше, чем ничего, но несколько меньше, чем полное понимание реализаций наследования.

Зато, данная часть статьи в нескольких (4) крупных примерах кода демонстрирует чистейшее прототипное наследование, которому не требуется привлекать понятие конструктора (хотя он там, в .create(), незримо присутствует), о котором много говорят и которое исключительно редко в чистом виде встречается.
Краткое содержание первой части
1. Объекты
  1.1 Что есть объекты? (список свойств)
  1.2 Создание свойств (Object.defineProperty)
  1.3 Описатели свойств (Object.defineProperty)
  1.4 Разбор синтаксиса (bracket notation: object['property'])
  1.5 Доступ к свойствам (через скобочную нотацию)
  1.6 Удаление свойств (оператор delete)
  1.7 Геттеры и сеттеры (методы доступа и записи)
  1.8 Списки свойств (getOwnPropertyNames, keys)
  1.9 Литералы (базовые операторы) объекта
2. Методы
  2.1 Динамический this
  2.2 Как реализован this
    2.2.1 Если вызывается как метод объекта
    2.2.2 При обычном вызове функции (this === global)
    2.2.3 При явном указании контекста (.apply, .call)
  2.3 Привязывание методов к контексту (.bind)
Cодержание части 2
3. Прототипное наследование
  3.1 Прототипы
  3.2 Как работает [[Prototype]]
  3.3 Переопределение свойства
  3.4 Миксины (примеси)
  3.5 Доступ к экранированным ('перезаписанным') свойствам
План части 3
4. Конструкторы
  4.1 Магия оператора new
  4.2 Наследование с конструкторами
5. Соглашения и совместимость
  5.1 Создание объектов
  5.2 Определение свойств
  5.3 Списки свойств
  5.4 Методы связывания
  5.5 Получение [⁣[Prototype]⁣]
  5.6 Библиотеки обратной совместимости
6. Синтаксические обёртки
7. Что читать дальше
8. Благодарности
Примечания

3. Прототипное наследование


До сих пор мы рассматривали, как определяются методы в объектах и как их повторно используют в других объектах при явном указании контекста, но это — всё же не лучший путь использования и расширения объектов.

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

Прототипное наследование идёт дальше и может избирательно расширять методы, описывать общее поведение и использовать другие занятные приёмы, которых мы коснёмся. Печалит лишь то, что модель наследования в JS немного ограничена, и для обхода трудностей эти приёмы будут временами избыточны выносить мозг.

3.1. Прототипы


Идея наследования в джаваскрипте крутится вокруг клонирования методов объекта и дополения его собственным поведением. Объект, который клонируется, называется прототипом (не путать со свойством prototype у функций).

Прототип — обычный объект, которому довелось расширять методы другого объекта — он выступает как родитель объекта. (Это — несколько смещённое понятие относительно общепринятого, когда родителем называют функцию-конструктор, содержащую этот прототип. По возможности, перевод старается не использовать понятие родителя применительно к прототипу — прим.перев.)

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

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


Она описывается в JS таким кодом: (хардкорно новый синтаксис, для ES5. Напомним, что аргументы в defineProperty — это объект, его имя и присваиваемый специальный объект --прим.перев)
продолжение скрипта из примеров к 1-й части статьи
// () → String
function get_full_name(){ //возвращает полное имя объекта
    return this.first_name + ' ' + this.last_name;
}
// (new_name:String) → undefined
function set_full_name(new_name){ //Вычисляем части имени из полного
    var names = new_name.trim().split(/\s+/);
    this.first_name = names['0'] ||'';
    this.last_name  = names['1'] ||'';
}
//===============================================
var person = Object.create(null); //пустой объект ради правильного аргумента
             //Впрочем, достаточно и {} --прим.перев.
Object.defineProperty(person, 'name'
  ,{get: get_full_name // используем предыдущие геттеры/сеттеры
    ,set: set_full_name
    ,configurable: true
    ,enumerable: true});
person.greet = function(person){
  return this.name + ': Ну что, привет ' + person + '.';
};

// Присоединяем метод к новому объекту mikhail, добавляя person в [[Prototype]]
var mikhail = Object.create(person);
  mikhail.first_name = 'Михаил';
  mikhail.last_name = 'Белый';
  mikhail.age = 19;
  mikhail.gender = 'Male';

//===Тестируем сделанное===:
console.log(mikhail.name); // => 'Михаил Белый' - .name видно за счёт прототипа person

mikhail.name = 'Michael White'; // Присваивание в name должно запустить сеттер

//Теперь first_name и last_name показывают новые значения
console.log(mikhail.first_name); // => 'Michael' - действительно
console.log(mikhail.last_name); // => 'White'

// .greet тоже унаследовано из person.
console.log(mikhail.greet('тебе') ); // => 'Michael White: Ну что, привет тебе.'

// Убедимся, что видим собственные свойства у mikhail
console.log(Object.keys(mikhail) ); // => [ 'first_name', 'last_name', 'age', 'gender' ]

     jsfiddle (1) для неверующих (IE9+)

3.2. Как работает [[Prototype]]


Как видно из примера, ни одно свойство из person не было упомянуто в mikhail, но все они прекрасно работают, потому что в JS передаются (делегируются) доступы к свойствам, т.е. свойства ищутся во всех родителях объекта.

Цепочка родителей определена в скрытых объектах каждого родителя, именуемых [[Prototype]]. Их нельзя изменить напрямую (кроме реализаций, где поддерживается .__proto__), поэтому единственный (специфицированный) способ — сеттеры при создании.

Когда свойство запрашивается из объекта, интерпретатор проверяет собственные свойства объекта. Если такое свойство отсутствует, проверяется родитель, и так — до конца цепочки родителей или до первого существующего свойства.

Если изменяем свойство прототипа, оно немедленно изменится для всех прототипов других объектов-наследников.

// (person:String) → String
person.greet = function(person){ // Приветствие человеку
    return this.name + ': привет ' + person + '.'
};
mikhail.greet('тебе'); // => 'Michael White: привет тебе.'


3.3. Переопределение свойств


Таким образом, прототипы и наследование используется для разделения доступа к данным для разных объектов и выполняется очень быстро и эффективно по затратам памяти, поскольку используется один источник данных для всех наследников.

Что, если нужно добавить специализированные методы на основе данных, которые имелись в момент наследования? Мы видели раньше, что методы определяются на основе свойств, поэтому будем определять особое поведение тем же способом — просто присваивать новые свойства.

Для демонстрации предположим, что Person реализует общее приветствие, а наследники Person — определяют собственные. Кроме того, добавим ещё одного человека, чтобы увидеть разницу.


Заметьте, что mikhail и kristin имеют индивидуальные приветствия, выражаемые версиями метода greet.
...пример в кодах
(Не будем далее полностью описывать определения используемых переменных в примерах кодов — они есть в дублях примеров на jsfiddle.net (над разделительной чертой из "=====") или легко дописываются на основе прежних примеров из статьи --прим.перев.)

// (person:String) → String
person.greet = function(person){ //общее формальное приветствие от персонажа
    return this.name + ': Здравствуйте' + (person ?', '+ person :'') +'!';
};
var mikhail = Object.create(person);
  mikhail.first_name = 'Михаил';
  mikhail.last_name = 'Белый';
  mikhail.age = 19;
  mikhail.gender = 'Male';
//переопределим greet -- вспомним про индивидуальность Михаила:
//(person:String) → String
mikhail.greet = function(person){ //индивидуальное панибратское приветствие
    return this.name + ': Здорово'+ (person ?', '+ person :', братан') +'!';
};
var kristin = Object.create(person); //новый персонаж
  kristin.first_name = 'Кристина';
  kristin.last_name = 'Белая';
  kristin.age = 19;
  kristin.gender = 'Female';
//(У неё другая манера приветствия)

// (person:String) → String
kristin.greet = function(person){ //индивидуальное эмоциональное приветствие
    return this.name + ': Чмоки, ' + (person ||'парниша');
};
//===Проверим, как это всё работает===

console.log(mikhail.greet(kristin.first_name) ); //=> 'Михаил Белый: Здорово, Кристина!'

console.log(mikhail.greet() ); //=> 'Михаил Белый: Здорово, братан!'

console.log(kristin.greet(mikhail.first_name) ); //=> 'Кристина Белая: Чмоки, Михаил'

//пользуясь прототипом kristin, вернём Кристине стандартное поведение:
console.log('Удаление свойства: ', delete kristin.greet); //=> true
console.log(kristin.greet(mikhail.first_name) ); //=> 'Кристина Белая: Здравствуйте, Михаил'
     jsfiddle (2)

3.4 Миксины (примеси)


Прототипы в Javascript позволяют использование общих методов, и хотя они — несомненно, сильный инструмент, они могли бы быть ещё мощнее. Они только обеспечивают наследование одного объекта другим в момент наследования.

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

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

К счастью, поскольку мы напрямую определяем методы объектов, мы можем решать эти проблемы примесями — некоторым дополнительным определением объектов во время их создания.

Что есть примеси? Можно сказать, они — «безродные», неунаследованные ниоткуда объекты. Они полностью определены в своих свойствах-методах и, чаще всего, сделаны для включения в другие объекты (хотя, их методы могли бы использоваться напрямую).

Развивая нашу небольшую модель персонажей, давайте добавим им некоторые способности. Пусть человек может быть пианистом или певцом — иметь методы pianist или singer в произвольных сочетаниях. Этот случай не укладывается в прототипную модель, поэтому пойдём на небольшой трюк. (На самом деле, можно заменить миксины переменной цепочкой наследований с прототипами, поэтому выбор миксина — это вопрос удобства и оптимальной реализации, а не следствие невыполнимости в модели прототипов. — прим.перев.)


Для работы миксинов, прежде всего, скомпонуем разные объекты в один. JS нативно не поддерживает этот необычный формат объекта, но он легко создаётся копированием всех собственных (не унаследованных) свойств.

var descriptor = Object.getOwnPropertyDescriptor //сокращения
  ,properties = Object.getOwnPropertyNames
  ,define_prop = Object.defineProperty;

// (target:Object, source:Object) → Object
function extend(target, source){ //копируем свойства source в target
  properties(source).forEach(function(key){
    define_prop(target, key, descriptor(source, key)) });
  return target;
}


extend() здесь перебирает собственные свойства source и копирует их в target. Отметим, что target будет изменяться, для него эта функция — разрушительная, что обычно — не проблема. Важнее то, что она наименее затратна.

Теперь можем добавлять «способности» к нашим объектам.
Добавляем в коды примесь способностей
var pianist = Object.create(null); //pianist - тот, кто может .play() на пианино
pianist.play = function(){
    return this.name + ' начинает играть на пианино.';
};
var singer = Object.create(null); //singer - тот, кто может .sing()
singer.sing = function(){
    return this.name + ' начинает петь.';
};

extend(mikhail, pianist); //добавляем возможности конечным объектам - примесь пианиста
console.log(mikhail.play() ); // => 'Михаил Белый начинает играть на пианино.'

// смотрим собственные, неунаследованные свойства у mikhail
console.log(Object.keys(mikhail) ); //=> ['first_name', 'last_name', 'age', 'gender', 'play']

extend(kristin, singer); //определим kristin как певца (певицу)
console.log(kristin.sing() ); //=> 'Кристина Белая начинает петь.'

// mikhail ещё не умеет петь:
try{
    mikhail.sing(); //=> TypeError: Object #<Object> has no method 'sing'
}catch(er){console.error('Предусмотренная ошибка: ', er)}

// Но mikhail получит .sing, если расширить прототип у объекта-предка person:
extend(person, singer);
console.log(mikhail.sing() ); //=> 'Михаил Белый начинает петь.'
     jsfiddle (3) для удобства контроля (IE9+)

3.5. Доступ к экранированным свойствам


Мы научились наследовать свойства и расширять их миксинами. Теперь есть небольшая проблема: что делать, если хотим получить доступ к перезаписанному (экранированному) свойству родительского объекта?

JS предоставляет функцию Object.getPrototypeOf которая возвращает [[Prototype]]. Поэтому доступ к свойствам прототипа достаточно прост:

Object.getPrototypeOf(mikhail).name; //не получаем результата, как и для person.name
// => 'undefined undefined'
  person.first_name = 'Random'; //...но можем определить человека по .first_name и .last_name
  person.last_name  = 'Person'; //...пользуясь тем, что они вызываются в геттере
Object.getPrototypeOf(mikhail).name; //=> 'Random Person'


Можно было бы навно предположить, что достаточно обращения к прототипу контекста (this):

var proto = Object.getPrototypeOf;
// (name:String) → String
mikhail.greet = function(name){ //личное обращение к одной определённой персоне
  return name == 'Кристина Белая'?  this.name +': Приветик, Кристи'
    : /*обращение ко всем остальным*/  proto(this).greet.call(this, name);
};
console.log(mikhail.greet(kristin.name) ); //=> 'Михаил Белый: Приветик, Кристи'
console.log(mikhail.greet('Маргарет') ); //=> 'Михаил Белый: Здравствуйте, Маргарет'


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


Простое решение — брать прототип из родительского объекта, а не из текущего. Последний пример становится таким:

var proto = Object.getPrototypeOf;
//(name:String) → String
//Явно указали прототип объекта mikhail - ошибки с искажением ссылки this не будет
mikhail.greet = function(name){ //Избирательное приветствие
  return name =='Кристина Белая'
    ? this.name + ': Приветик, Кристи'
    : proto(mikhail).greet.call(this, name); //обращение к остальным
};
mikhail.greet(kristin.name); //=> 'Михаил Белый: Приветик, Кристи'
mikhail.greet('Маргарет'); //=> 'Михаил Белый: Здравствуйте, Маргарет!'


Способ не лишён недостатков: объект жёстко задан в функции, и мы не можем так просто взять, и применить функцию к любому объекту, как было до сих пор. Функция будет зависима от предка объекта, а не от него самого.

Если делать динамический, универсальный достуступ к прототипу родителя, это потребовало бы передачи дополнительного параметра для каждого вызова функции, что не может быть решено, как сейчас, по-быстрому в уродливых хаках. (А именно, надо сопровождать каждое наследование свойством типа .ancestor или .superclass для доступа к конструктору-предку, а функциям — использовать эти данные — прим.перев.)

Подход, предложенный в новой версии JS, решает только первую часть задачи, что самое простое. Здесь мы будем делать то же самое, но введением другого способа определения методов. Да, методов, а не общих функций.

Функции для доступа к свойствам в [⁣[Prototype]⁣] требуют дополнительной информации: объекта, где они записаны. Это требует поискового алгоритма, работающего со статическими данными, но решает наши рекурсивные проблемы.

Введём функцию make_method, которая возвращает функцию которая передаёт эту информацию целевой функции. (Т.е. нужно получить ссылку на прототип, в котором объявлен наш экранированный метод, и это достигается модификацией метода на каждом шаге наследования — прим.перев.)

трёхступенчатое наследование для демонстрации доступа к экранированным свойствам
//(object:Object, fun:Function) → Function
function make_method(object, fun){ //сохранение места объявления в методе при наследовании
  return function(){ var args;
    args = [].slice.call(arguments);
    args.unshift(object); //вставить 'object' первым аргументом
    fun.apply(this, args);
  };
}
//Все методы будут содержать в первом аргументе объект
// (прототип) их объявления (конечно, теперь нигде
//нельзя использовать позиционный доступ к аргументам - только по имени)
function message(self, message){ var proto;
  proto = Object.getPrototypeOf(self);
  if(proto && proto.log)
    proto.log.call(this, message);
  console.log('-- собственное имя прототипа: ' + self.name
    +'; видимое name: '+ this.name + '; контекст вызова: '+ message);
}
var A  = Object.create(null); //описываем цепочку прототипов C -> B -> A
A.name = 'A';
A.log  = make_method(A, message);

var B  = Object.create(A);
B.name = 'B';
B.log  = make_method(B, message);

var C  = Object.create(B);
C.name = 'C';
C.log  = make_method(C, message);

//===тестируем вызовами методов===
A.log('~A~');
//=>-- собственное имя прототипа: A; видимое name: A; контекст вызова: ~A~

B.log('~B~');
//=>-- собственное имя прототипа: A; видимое name: B; контекст вызова: ~B~
//=>-- собственное имя прототипа: B; видимое name: B; контекст вызова: ~B~

C.log('~C~');
//=>-- собственное имя прототипа: A; видимое name: C; контекст вызова: ~C~
//=>-- собственное имя прототипа: B; видимое name: C; контекст вызова: ~C~
//=>-- собственное имя прототипа: C; видимое name: C; контекст вызова: ~C~

(Для лучшей наглядности этот пример сильно изменён в формате вывода трассировки по сравнению с оригиналом статьи; исправлены опечатки оригинала — прим.перев.)
     jsfiddle (4), для любителей поковырять

Продолжение следует.

Only registered users can participate in poll. Log in, please.

Используете ли вы в повседневной разработке (в не менее 20% кода) возможности EcmaScript 5 вообще и наследование в частности?

  • 9.4%Да, использую различные возможности ES5 в не менее 20% рабочих или самодеятельных проектах, но без ООП25
  • 9.8%Да, использую не менее в 20% проектов, в том числе и описанные ООП-ориентированные методы26
  • 13.9%Да, мои проекты в не менее 80% случаев поддерживают ES5, широко использую37
  • 5.6%Да, и, к тому же, я существенно использую уже ES6 (EcmaScript 6) в не менее чем 20% проектов15
  • 36.8%Нет, не использую ES5-ES6, применяется ES3 или jQuery и прочий софт по привычке, хотя проекты позволяли бы писать на ES598
  • 24.4%Нет, не использую (потому что нужна работа в ES3-only-браузерах)65

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 22

    0
    Я бы сказал, что уже скоро будет ES6 и все новые фичи ES5 уже не новые.
      0
      Интересно было бы запустить опрос, какая доля разработчиков использует фичи ES5 в повседневных разработках. У меня такой мысли не было, потому что думаю, что весьма небольшая. Но добавил опрос для проверки этого мнения.
        +1
        Очень прискорбно, что сейчас лидируют 2 последних пункта.
        0
        В принципе ES5 поддерживается уже достаточно хорошо, и его уже можно использовать если забыть про всякие не важные мелочи и strict mode: kangax.github.io/es5-compat-table/

        К сожалению ES6 будет доступен ещё не скоро.
          +1
          Я уже года 2 использую es5 в продакшене, strict mode включён естественно. Где поддерживается, там хорошо, где нет, увы.
            0
            К сожалению ES6 будет доступен ещё не скоро.

            Ну многие фичи уже давно работают, просто в ES6 официально описаны в спецификации.
          0
          Мне кажется, что тут забыли Object.create
            0
            Первая часть статьи с него и начинается.
            0
            Хм, я использую obj.constructor.prototype вместо Object.getPrototypeOf(obj). Это плохо?
              +1
              В вашем подходе все в порядке. Если интересны подробности, то посмотрите вот тут — tobyho.com/2010/11/22/javascript-constructors-and/
                +1
                Потому, что может быть obj.constructor.prototype != Object.getPrototypeOf(obj)
                  +1
                  Главное на грабли не наступать:
                  function F1(){};
                  F1.prototype = {
                    prop1: 1,
                    prop2: 2
                  }
                  obj1 = new F1;
                  console.log(Object.getPrototypeOf(obj1) == F1.prototype);    // true
                  console.log(obj1.constructor.prototype == F1.prototype);     // false - O_o
                  console.log(obj1.constructor.prototype == Object.prototype); // true
                  function F2(){}
                  function F3(){}
                  F3.prototype = new F2;
                  obj2 = new F3;
                  console.log(Object.getPrototypeOf(obj2) == F3.prototype);    // true
                  console.log(obj2.constructor.prototype == F3.prototype);     // false - O_o
                  console.log(obj2.constructor.prototype == F2.prototype);     // true
                  
                    0
                    Разумеется, ссылку constructor у прототипа надо править, иначе constructor созданного объекта будет указывать не на конструктор объекта а на конструктор прототипа. Видимо getPrototypeOf ввели, чтобы не париться по этому поводу, или просто для ясности.
                  +1
                  Как раз недавно запилил либу для множественного наследования: javascript.ru/forum/project/40210-jin-i-snova-o-nasledovanii.html

                  Множественное наследование — это безусловно классно, но есть ряд проблем:
                  1. Конфликты имён. Не должны методы одной примеси молча затирать методы другой. В моей либе вместо затирания происходит создания метода-конфликта. Чтобы разрешить конфликт нужно примешать такой метод в описании которого указано, что он пригоден для разрешения конфликта между теми конфликтующими методами.
                  2. Не всегда понятно какой метод/свойство взяты из какой примеси/класса. В моей либе каждый метод имеет глобальный идентификатор. А также есть функция которая раздаст идентификаторы уже готовой структуре объектов.
                  3. Зачастую нужен доступ ко всем перегруженным методам, а не только к методу одного последнего предка. В моей либе все когда либо примешанные методы сохраняются в прототипе по тому самому глобальному идентификатору.

                  По ссылке больше примеров)
                    +1
                    Не холивара ради, но множественное наследование говорит лишь о проблемах в обсерватории. Мне много где приходилось применять обычное наследование (одиночное, хоть на прототипах, хоть классическое), и много делал полезных примесей, которые придают объекту определённые «способности» (удачно подобранное выражени в статье). Но множественное наследование — это перебор. IMHO.
                      0
                      И чем же множественное примешивание отличается от множественного наследования? х)

                      Я вот каких только вариантов повторного использования кода не перепробовал, но остановился на таком:
                      Есть типажи (штрихи/примеси/классы), они по умолчанию ничего не умеют.
                      Для типажей можно определять методы.
                      Типажи могут быть примешаны друг ко другу. При этом ранее определённые и позже определяемые в предках методы также копируются и в потомков каскадно.
                      В качестве типажей могут быть использованы любые глобально доступные объекты.
                      Каждый метод хранит информацию о том, какие методы он умеет перегружать.
                      Одноимённые методы определяемые в одном типаже проходят через процедуру «слияния».
                      Результатом «слияния» является либо один из методов (который должен уметь перегружать всех оставшихся), либо автоматически генерируемый метод-конфликт, хранящий в себе информацию о конфликтующих методах. Совсем как в системах контроля версий: типаж — это фичеветка.
                        0
                        Множественное примешивание — это простая операция, которая не сохраняет информацию о перегрузках методов, она их просто перекрывает и всё. После примешивания вы не можете обратиться к источнику примеси (т.к. это не требуется), и не можете знать какой источник предпоследний и т.д. В общих словах, вы ориентируетесь на результат без истории его возникновения.
                          0
                          Из пальца высосал разницу)
                          Ну и что хорошего в этаком примешивании? Скорость? Одинаково. Потребление памяти? Одинаково. Простота реализации? А толку от простоты, если она приводит к трудноуловимым багам? По твоему ловить глюки, вызванные конфликтом методов, лучше, чем вменяемое сообщение о конфликте? Или писать костыли, когда нужно не просто переопределить метод, а дополнить?
                            0
                            Хорошего именно простота логики, не надо заботиться о что откуда почему пришло и как это себя поведёт если… Не стоит усложнять там, где это не требуется, и стоит упрощать там, где это возможно.

                            Я не спорю, что бывают задачи разные и разные подходы к их решению, и каждому унструменту свою задачу. Я высказал своё мнение, что я лучше разобью логику на более простые составляющие, чем буду применять «множественное наследование», и да, я считаю, что если это самое множественное наследование применяется повсеместно, то стоит пересмотреть подходы к проектированию/программированию.

                            Серебряной пули не существует, на этом давайте и закончим, и без того затянувшийся и слишком абстрактный разговор.
                              +2
                              Вот так вот не беспокоятся, а потом тратят на дебаг в 3 раза больше времени. Потому что беспокоиться о том что откуда пришло — необходимо. Потому, что нужно точно знать какие методы будут у объектов и как они будут действовать. Когда ты примешиваешь две пимеси, то у тебя возникает неопеделенность, которая сейчас работает как надо, а потом может сломаться. А находить такие баги довольно проблематично. Да и для разрешения конфликта используется как правило копипаста, либо хитрые конструкции типа Ext.Window.superclass.initConstructor.apply( this, arguments ) которые как ни странно ещё и работают через жопу.

                              И ничего во множественном наследовании плохого нет. Бывает плохая декомпозиция, да.
                    –2
                    А меня вот эта запись смущает. Вроде, всё правильно, и даже цепочки прототипов есть (prototype.prototype...), но что-то сильно не нравится, не пойму что.

                    var Animal = function(){
                    this.prototype = new function(){
                    this.walk = true;
                    }
                    }
                    var Cat = function(){
                    this.prototype = new Animal();
                    }
                    console.log(new Cat().prototype.prototype.walk); //true

                      0
                      Ммм… prototype определяется у функции-конструктора, а не у this

                      var Animal = function() {};
                      Animal.prototype.walk = true;
                      
                      var Cat = function() {};
                      Cat.prototype = Object.create(Animal.prototype);
                      Cat.prototype.constructor = Cat;
                      
                      console.log((new Cat()).walk); // true
                      

                    Only users with full accounts can post comments. Log in, please.