Javascript.RU

ООП в Javascript: наследование

Javascript - очень гибкий язык. В отличие от Java, PHP, C++ и многих других языков, где наследование можно делать одним способом - в javascript таких способов много.

На уровне языка реализовано наследование на прототипах. С помощью некоторых трюков можно сделать (хотя и не так удобно, как в Java/C++) наследование на классах, объявить приватные свойства объекта и многое другое.

Корректность этой статьи

С момента появления эта статья вызвала критику некоторых профессионалов в javascript. Поэтому появилось это небольшое "пред-введение".

Не всё, происходящее при наследовании в javascript, статья описывает абсолютно корректно. В том числе, упрощено описание таких вещей как activation object, scope, prototype встроенных объектов.

Если Вы хотите максимально корректное и точное описание наследования, содержащее все детали - оно дано в стандарте языка, параграфы 13.2 и 15.3. Возможно, хороший вариант - прочитать статью для создания общей картины, а потом - уточнить технические детали, прочитав стандарт.

Упрощения в статье не затрагивают сути происходящего и не играют роли при real-life использовании javascript. Если Вы аргументированно считаете, что это не так - напишите комментарий, буду рад принять его к сведению и дополнить статью.

P.S Комментарии типа "А что будет, если прототип функции сделать числом и почему статья этого не описывает, это неправильная статья" - не принимаются. Не делают такого в real-life.

Обязательно пишите в комментах, если что неправильно или непонятно - на все отвечу и поправлю. Автор.

Любая функция, кроме некоторых встроенных, может создать объект.

Для этого ее нужно вызвать через директиву new.

Например, функция Animal в примере ниже создаст новый объект.

function Animal(name) {
    this.name = name
    this.canWalk = true
}

var animal = new Animal("скотинка")

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

В этом примере был создан объект класса Animal, и ему добавлены свойства name и canWalk. Получилось вот так:

animal.name = 'скотинка'
animal.canWalk = true

Класс объекта определяется функцией, которая его создала. Для проверки принадлежности классу есть оператор instanceof:

alert(animal instanceof Animal)  // => true
Этот оператор иногда не работает, как полагается. Далее мы подробно разберем логику его работы, чтобы понимать, при каком наследовании и как его использовать.

В javascript базовое наследование основано не на классах. То есть, нет такого, что классы наследуют друг от друга, а объект класса-потомка получает общие свойства.

Вместо этого объекты наследуют от объектов без всяких классов. Наследование на классах можно построить(эмулировать), опираясь на базовое наследование javascript.

Разберем подробнее, что такое наследование от объектов и как оно работает.

Реализуется наследование через неявную(внутреннюю) ссылку одного объекта на другой, который называется его прототипом и в спецификации обозначается [[prototype]]. Это свойство обычно скрыто от программиста.

Также существует свойство с похожим названием prototype (без квадратных скобок) - оно вспомогательное и указывает, откуда брать прототип при создании объекта.

Когда вы ставите функции Animal свойство Animal.prototype = XXX - вы этим декларируете: "все новые объекты класса Animal будут иметь прототип XXX".

Исходя из спецификации языка, ссылка на прототип объекта [[prototype]] не обязана быть доступной для чтения и изменения.

Однако в реализации javascript, используемой в Gecko-браузерах: Firefox/Mozilla и т.п., эта ссылка является обычным свойством объекта: __proto__.
В этих браузерах ее можно читать и изменять.

Наследование происходит через скрытое свойство прототип [[prototype]], однако единственный кроссбраузерный способ указать прототип - это использовать свойство prototype функции-конструктора.

Например пусть объект кролик "rabbit" наследует от объекта животное "animal".

В наследовании на прототипах это реализуется как ссылка rabbit.[[prototype]] = animal:

наследование javascript Объект

Ссылка [[prototype]] работает так:

  1. Любое запрошенное свойство ищется сначала в rabbit
  2. Если свойство там не найдено, то оно ищется в rabbit.[[prototype]], т.е в animal

Благодаря поиску по прототипу получается, что все функции и переменные, которые были в animal, доступны и в rabbit.

Ссылка на прототип создается оператором new во время создания объекта.

Ее значением становится свойство prototype функции-конструктора. Значение prototype указывает, от кого будут наследовать новые объекты

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

Сделаем класс, наследующий от Animal - назовем его Rabbit.

Для этого сначала объявим функцию Rabbit.

function Rabbit(name) {
    this.name = name
}

Пока что она просто создает объекты Rabbit. Поставим свойство prototype, чтобы новые объекты имели прототип animal (мы объявили этот объект чуть выше):

Rabbit.prototype = animal
function Animal(name) {
    this.name = name
    this.canWalk = true
}

var animal = new Animal("скотинка")

function Rabbit(name) {
	this.name = name
}

// все объекты, созданные Rabbit
// будут иметь прототип (наследовать) animal
Rabbit.prototype = animal

А теперь - создадим пару кроликов.

big = new Rabbit('Chuk')
small = new Rabbit('Gek')

alert(big.name)  // Chuk
alert(small.name) // Gek

alert(big.canWalk) // true

// в Firefox можно еще так
if (big.__proto__) {  // в Firefox __proto__ это [[Prototype]]
    alert(big.__proto__.name) // скотинка
}

Свойство name хранится прямо в объектах Rabbit, а canWalk берется из прототипа animal.

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

alert(big.canWalk)  // true

// поменяли в прототипе
animal.canWalk = false

alert(big.canWalk)  // false
alert(small.canWalk)  // false

Запишем свойство canWalk напрямую в объект Rabbit:

animal.canWalk = false

small.canWalk = true

alert(big.canWalk)  // false
alert(small.canWalk)  // true

У разных кроликов получилось разное значение canWalk, независимое от родителя.

Таким образом мы реализовали перекрытие (override) свойств родительского объекта.

Наверху цепочки всегда находится объект встроенного класса Object.

Так получается из-за того, что по умолчанию свойство prototype функции равно пустому объекту new Object().

// Animal.prototype не указан явно, по умолчанию:
Animal.prototype = {}

Получается такая картинка:

наследование javascript объект

Это хорошо, потому что у класса Object есть ряд полезных функций: toString(), hasOwnProperty()... А, например в Firefox, есть даже функция toSource(), которая дает исходный код, т.е "полный дамп" объекта.

Благодаря тому, что вверху цепочки наследования стоит Object, все остальные объекты имеют доступ к этому функционалу.

При вызове метода - он имеет доступ ко всем данным "своего" объекта.

Для этого в javascript (как, впрочем, и во многих других языках) используется ключевое слово this.

Например мы хотим добавить всем объектам класса Animal функцию перемещения. Для этого запишем в Animal.prototype метод move. Каждый его вызов будет изменять расстояние distance:

Animal.prototype.move = function(n) {
	this.distance = n
	alert(this.distance)
}

Теперь если мы сделаем новый объект, то он сможет передвигаться:

var animal = new Animal("животное")
animal.move(3)   // => 3
animal.move(4)   // => 4
...

При вызове animal.move, интерпретатор находит нужный метод в прототипе animal: Animal.prototype.move и выполняет его, устанавливая this в "текущий" объект.

this в javascript

В javascript this работает не так, как в PHP, C, Java.

Значение this ставится на этапе вызова функции и может быть различным, в зависимости от контекста.

Подробнее это описано в статье как javascript работает с this.

Точно также смогут вызывать move и объекты класса Rabbit, так как их прототипом является animal.

Методы класса объявляются в Класс.prototype. Например, Animal.prototype - содержит методы для всех объектов класса Animal.

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

Объявление move в классе Animal при таком подходе выглядело бы вот так:

function Animal(n) {
    // конструируем объект
    .....
    // добавляем методы
    this.move = function(n) {
        this.distance = n
        alert(this.distance)
    }
}

В наиболее распространенных javascript-библиотеках используется первый подход, т.е добавление методов в прототип.

Свойства-объекты или "иногда прототип это зло"

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

Например, объявим объект класса хомяк(Hamster). Метод found набирает еду за щеки, набранное хранит в массиве food.

function Hamster() {  }
Hamster.prototype = {
	food: [],
	found: function(something) {
		this.food.push(something)
	}
}

Создадим двух хомячков: speedy и lazy и накормим первого:

speedy = new Hamster()
lazy = new Hamster()

speedy.found("apple")
speedy.found("orange")

alert(speedy.food.length) // 2
alert(lazy.food.length) // 2 (!??)

Открыть этот код в новом окне

Как видно - второй хомяк тоже оказался накормленным! В чем дело?

Причина заключается в том, что food не является элементарным значением.

Если при простом присвоении hamster.property="..." меняется свойство property непосредственно в объекте hamster, то при вызове hamster.food.push(...) - яваскрипт сначала находит свойство food - а так, как в hamster его нет, то оно берется из прототипа Hamster.prototype, а затем вызывает для него метод push.

На каком бы хомяке не вызывался hamster.food.push(..) - свойство food будет браться одно и то же, из общего прототипа всех хомяков.

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

Но в данном случае такое ни к чему.

Чтобы разделить данные, неэлементарные свойства обычно присваивают в конструкторе:

function Hamster() {
    this.food = []
}
Hamster.prototype = {
    food: [], // просто для информации
    found: function(something) {
        this.food.push(something)
    }
}

Теперь у каждого объекта-хомяка будет свой собственный массив food.

Свойство food в прототипе оставлено как комментарий. Оно не используется, но может быть полезно для удобства документирования.

Рабочий вариант наследования на классах, в общем-то, готов.

Для того чтобы объект класса Rabbit унаследовал от класса Animal - нужно

  1. Описать Animal
  2. Описать Rabbit
  3. Унаследовать кролика от объекта Animal:
    Rabbit.prototype = new Animal()
    

Однако, у такого подхода есть два недостатка:

  1. Для наследования создается совершенно лишний объект new Animal()
  2. Конструктор Animal должен предусматривать этот лишний вызов для и при необходимости делать такое "недоживотное", годное лишь на прототип.

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

function extend(Child, Parent) {
	var F = function() { }
	F.prototype = Parent.prototype
	Child.prototype = new F()
	Child.prototype.constructor = Child
	Child.superclass = Parent.prototype
}

Использовать ее для наследования можно так:

// создали базовый класс
function Animal(..) { ... }

// создали класс
// и сделали его потомком базового
function Rabbit(..)  { ... }
extend(Rabbit, Animal)

// добавили в класс Rabbit методы и свойства
Rabbit.prototype.run = function(..) { ... }

// все, теперь можно создавать объекты
// класса-потомка и использовать методы класса-родителя
rabbit = new Rabbit(..)
rabbit.animalMethod()

Функция очень удобная и работает "на ура".

Она не создает лишних объектов и в качестве бонуса записывает класс-родитель в свойство потомка superclass - это удобно для вызова родительских методов в конструкторе и при перекрытии методов.

Как оно работает?

Есть разные мнения, кто придумал функцию extend, но популяризацией она обязана Дугласу Крокфорду.

Как и почему она все-таки работает - может быть неочевидно даже опытным javascript-специалистам.

Попробуйте понять это, полистав спецификацию, особенно параграфы 13.2 и 15.3, а если какие-то вопросы остались - читайте дальше.

Предупреждение. Объяснение сложное, подробное и, вообще говоря, не обязательное, ведь функция "просто работает". Читайте на свой страх и риск.

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

mwsnap023.jpg

Этот синтаксис - ни что иное как удобная форма записи для:

F = new Function()

Эта строка cоздает новый объект класса Function (встроенный класс javascript).

Конструктор Function хранит ссылку на Function.prototype, который содержит общие свойства функций: call, apply, constructor, toString и т.п. Поэтому F.[[prototype]] = Function.prototype.

Кстати, за счет такого прототипа все функции и имеют доступ к методам call, apply и т.д.

Создание объекта F можно изобразить так:

mwsnap026.jpg

На картинке также изображено свойство prototype, которое автоматически устанавливается в new Object(). Свойство constructor также генерируется интерпретатором и показывает обратно, так что по кругу prototype.constructor для функции можно идти бесконечно: F.prototype.constructor === F.

Следующая строка устанавливает свойство F.prototype:

F.prototype = Parent.prototype

До второй строки свойства имели такие значения:

mwsnap027.jpg

Свойство F.prototype указывало на объект new Object() (справа снизу на рисунке).

Теперь значение поменялось, и старый new Object() перестал быть доступен - ни одна ссылка на него не ведет. Поэтому сборщик мусора убивает его.

Вот так изменения отразятся на картине:

mwsnap028.jpg

Следующая строка устанавливает свойство prototype для дочернего класса, чтобы оно служило прототипом всех дочерних объектов.

Child.prototype = new F()

При создании объекта класса F, свойство [[prototype]] нового объекта конструктор возьмет из F.prototype:

(new F).[[prototype]] = (т.к F.prototype==Parent.prototype) = Parent.prototype

т.е получится такая цепочка присвоения

Child.prototype.[[prototype]] = (new F).[[prototype]] = Parent.prototype

Иначе говоря, у нас получилось, что

Child.prototype = [объект,  прототип которого - Parent.prototype]

Это присвоение можно изобразить на картинке вот так:

mwsnap029.jpg

В правом-нижнем углу - как раз и находится Child.prototype, прототипом которого получился Parent.prototype.

Собственно, наследование уже работает. В самом деле, создадим новый объект класса Child:

child = new Child(...)

Вспоминаем, что интерпретатор, выполняя new, ставит child.[[prototype]] = Child.prototype.

Поиск свойств, не найденных в child будет идти по
child.[[prototype] = Child.prototype = new F().
Если там не нашли, то интерпретатор будет искать в new F().[[prototype]] = F.prototype = Parent.prototype, то есть, в конечном счете:

child -> Child.prototype -> Parent.prototype

.. Что и требовалось получить.

Свойство Child.prototype.constructor осталось старое, и его нужно поправить строкой:

Child.prototype.constructor = Child

Для каждой функции свойство prototype.constructor всегда должно указывать на саму функцию.

Если это так, то объекты, которые функция создает, тоже будут иметь (через прототип) правильное свойство constructor, указывающее на создавшую их функцию.

Например:

function Z() {}
alert(Z.prototype.constructor)  // => функция Z

z = new Z()
//в z нет ничего, но в z.[[prototype]]=Z.prototype есть constructor
alert(z.constructor)   // => функция Z

Эта строка extend как раз и проставляет правильное свойство prototype.constructor.

Добавим в класс явную ссылку на родительский класс для удобного обращения к его методам. Понадобится для вызова конструктора родителя или если родительский метод был перекрыт в потомке.

Child.superclass = Parent.prototype

В механизме наследования, разобранном выше, есть одно белое пятно. Это - конструктор.

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

С наследованием через extend - это очень просто.

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

function Rabbit(..)  {
	...
	Rabbit.superclass.constructor.apply(this, arguments)

	...
}

Конечно же, аргументы можно поменять, благо apply дает возможность вызвать функцию с любыми параметрами вместо arguments в примере.

Аналогично можно вызвать и любой другой метод родительского класса:

Rabbit.superclass.run.apply(this, ...)

В этих примерах везде в явном виде указано имя класса Rabbit, хотя можно бы попробовать указать this.constructor, который должен указывать на Rabbit, т.к объект принадлежит этому классу.

Если так поступить, то будет ошибка при цепочке наследования классов из 3 элементов типа foo -> bar -> zot.

Проиллюстрируем ее на примере:

function foo() {}
foo.prototype.identify = function() {
	return "I'm a foo";
}

function bar() {}
extend(bar, foo)
bar.prototype.identify = function() {
	return "I'm a bar and " +
	this.constructor.superclass.identify.apply(this, arguments);
}

function zot() {}
extend(zot, bar)
zot.prototype.identify = function() {
	return "I'm a zot and " +
	this.constructor.superclass.identify.apply(this, arguments);
}

f = new foo();

alert(f.identify()); // "I'm a foo"

b = new bar();

alert(b.identify()); // "I'm a bar and I'm a foo"

z = new zot();

alert(z.identify()); // stack overflow

Последний вызов приведет к ошибке "too much recursion" из-за того, что this.constructor.superclass, к которому идет обращение в функции bar.identity обращается к дочернему классу zot. В результате bar.identity вызывает сама себя в бесконечной рекурсии.

Правильный способ заключается в явном обозначении класса, т.е Rabbit.superclass...

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

Логику его работы можно описать так:

function instanceOf(object, constructor) {
   var o=object

   while (o.__proto__ != null) {
      if (o.__proto__ === constructor.prototype) return true
      o = o.__proto__
   }
   return false
 }

Поэтому при правильной структуре прототипов он всегда корректно работает.

У этого оператора есть неприятная особенность при использовании нескольких окон: в разных окнах объекты классов (окружение) разное, поэтому массив из одного окна(фрейма) не будет опознан как Array в другом фрейме.

Впрочем, такая ситуация возникает довольно редко.

Для окончательной организации удобного javascript-наследования на классе, пригодится функция копирования свойств из объекта src в другой dst:

// копирует все свойства из src в dst,
// включая те, что в цепочке прототипов src до Object
function mixin(dst, src){
	// tobj - вспомогательный объект для фильтрации свойств,
	// которые есть у объекта Object и его прототипа
	var tobj = {}
	for(var x in src){
		// копируем в dst свойства src, кроме тех, которые унаследованы от Object
		if((typeof tobj[x] == "undefined") || (tobj[x] != src[x])){
			dst[x] = src[x];
		}
	}
	// В IE пользовательский метод toString отсутствует в for..in
	if(document.all && !document.isOpera){
		var p = src.toString;
		if(typeof p == "function" && p != dst.toString && p != tobj.toString &&
		 p != "\nfunction toString() {\n    [native code]\n}\n"){
			dst.toString = src.toString;
		}
	}
}

В полном примере мы создадим класс Animal c методом walk и его насленика Bird, который умеет летать: fly. Функции walk и fly принимают время ходьбы/полета и соответственно увеличивают свойство distance - расстояние до животного:

// ---- родительский класс ----

function Animal(name, walkSpeed) {
	this.name = name
	this.walkSpeed = walkSpeed
}

// добавляем методы объекта
mixin(Animal.prototype, {

	// пример переменной
	distance: 0,

	// пример метода
	walk: function(time) {
		this.distance = this.distance + time*this.walkSpeed
	},

	toString: function() {
		return this.name+" на расстоянии "+this.distance
	}
})

// ---- класс наследник ----

function Bird(name, walkSpeed, flySpeed) {
	// вызов родительского конструктора
	Bird.superclass.constructor.call(this, name, walkSpeed)

	this.flySpeed = flySpeed
}
extend(Bird, Animal)

mixin(Bird.prototype, {
	fly: function(time) {
		this.distance = this.distance + time*this.flySpeed
	}
})

Пример создания объекта-наследника:

bird = new Bird("Птыц", 1, 10)

bird.walk(3)

alert(bird) // => Птыц на расстоянии 3

bird.fly(2)

alert(bird) // => Птыц на расстоянии 23

Конечно же, вызовы extend и mixin можно объединить в одну функцию. В примере это не сделано для наглядности происходящего.

При наследовании можно организовать "настоящие" приватные члены класса. Для этого, однако, придется объявлять все методы не отдельно от конструктора, а внутри него:

function extend(Child, Parent) {
	var F = function() { }
	F.prototype = Parent.prototype
	Child.prototype = new F()
	Child.prototype.constructor = Child
	Child.superclass = Parent.prototype
}

// ---- родительский класс ----

function Animal(name, walkSpeed) {

	// объявить приватную переменную
	var speed = walkSpeed

	// объявить открытую переменную
	this.distance = 0

	// добавить метод, использующий private speed
	this.walk = function(time) {
		this.distance = this.distance + time*speed
	}

	// добавить метод, использующий private name
	this.toString = function() {
		return name+" на расстоянии "+this.distance
	}
}


// ---- класс наследник ----

function Bird(name, walkSpeed, flySpeed) {
	// вызов родительского конструктора
	Bird.superclass.constructor.call(this, name, walkSpeed)

	this.fly = function(time) {
		this.distance = this.distance + time*flySpeed
	}
}
extend(Bird, Animal)


bird = new Bird("Птыц", 1, 10)

bird.walk(3)

alert(bird) // => Птыц на расстоянии 3

bird.fly(2)

alert(bird) // => Птыц на расстоянии 23

Приватными являются все свойства, которые доступны только из внутренних методов объекта через механизм замыкания (см. статью о функциях javascript).

Это свойства, явно объявленные через var, плюс аргументы конструктора.

При таком способе объявления - все свойства и методы записываются не в прототип объекта, а в сам объект.

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

Если Вы использовали ООП в других языках программирования, то наверняка знаете, что чаще делаются не private свойства, а protected, т.е такие, к которым могут получить доступ наследники. Javascript не предоставляет синтаксиса для создания protected свойств, поэтому их просто помечают подчеркиванием в начале.

Например,

function Animal(name) {

	var privateVariable = 0

	this._protectedName = name

	this._protectedMethod = function(..) {
		... alert(privateVariable)..
	}

	this.publicMethod = function() { ... }
}

Все функции, объявленные внутри конструктора, имеют доступ к приватным свойствам и, конечно же, к защищенным и публичным.

Ограничение доступа к таким "защищенным" свойствам не жесткое и остается на совести программиста.

Есть альтернативный способ наследования, который вообще не требует вызова new.

При этом объекты создаются с помощью "фабричных функций" (factory function). Например, в следующем примере это функция Animal, которая производит некие действия и возвращает объект.

function Animal(name) {
	var speed = 10
	return {
		name: name,
		run: function(distance) { 
			return distance / speed
		}
	}
}
pet1 = Animal()
pet2 = Animal()

Через замыкания (о функциях и замыканиях) организуются "приватные" члены класса. В примере выше доступ к переменной speed возможен только из функции run .

Задача фабрики объектов - создать объект и инициализировать его.

Для создания потомка фабрика объектов просто модифицирует объект, создаваемой функцией-родителем.

Рассмотрим пример с созданием Rabbit - потомка Animal:

function Rabbit(name) {

	// вызвать конструктор родителя, 
	// получить родительский объект в me
	var me = Animal(name)  
	
	// добавить приватную переменную
	var jumps = 0   

	/* добавить новые методы к me */
	me.jump = function() { jumps++ }
	me.getJumps = function() { return jumps }

	// поставить правильное свойство конструктора
	// (делаем вид, что объект создали мы, а не Animal)
	me.constructor = arguments.callee

	return me
}

При создании потомка фабричная функция делает следующее:

  1. Создает объект родительского класса
  2. Присваивает ему публичные свойства и методы
  3. Меняет свойство constructor объекта на себя
    • этот шаг можно пропустить (см дальше)

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

Фактически, функция берет другой объект и добавляет ему свои методы. Из-за этого такую реализацию наследования иногда называют паразитическим наследованием.

Почему этот способ мой любимый?

Во-первых, потому, что именно такой стиль ООП, как мне кажется, наиболее соответствует по духу яваскрипт.

Он прост и понятен.

В нем есть приватные переменные, которые замечательно сжимаются javascript-минификатором, что существенно сокращает объем скачиваемого посетителем кода и увеличивает производительность, т.к интерпретатор javascript быстрее работает с короткими именами переменных.

К сожалению, метод instanceof при таком наследовании не работает, так как он завязан на [[prototype]] объекта. Фактически, вызов instanceof A просто проверяет, есть ли в цепочке прототипов объекта класс A.

Свойство constructor, которое было присвоено на 3м шаге, как раз и служит для эмуляции instanceof:

alert(rabbit.constructor === Rabbit) // => true
Более сложная реализация наследования не присваивает constructor, а добавляет его в специальный список. Функция-аналог instanceof проверяет всю цепочку и выдает, есть ли в ней искомый класс.
При таком наследовании часто применяют альтернативный подход к instanceOf: проверку на нужный метод.

Как часто описывают такой способ - ".. Если объект умеет крякать, значит это утка. Кому какое дело что он на самом деле..":

if (arr.splice) {
  // умеет splice, значит это массив
  // вообще, какая разница, что это за объект на самом деле,
  // то, что надо, он умеет - пользуем..
    .. arr.splice(..) ..
}

В наследовании через классы свойства родителя доступны через superclass.

Здесь - чтобы получить доступ к методу родительского объекта, его обычно копируют куда-нибудь в замыкание. Например:

function Rabbit(name) {
	var me = animal(name)  

	var jumps = 0   

	me.jump = function() { jumps++ }
	me.getJumps = function() { return jumps }

	// скопировать метод run родителя в замыкание
	var super_run = me.run

	/* перекрыть метод */
	me.run = function(distance) {
		this.jump()	

		// вызвать родительский метод
		return super_run.call(this, distance)
	}
	return me
}

Таким образом нужно сделать "бэкап" всех нужных свойств.

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

В этой статье мы подробно разобрали, как в Javascript можно организовать наследование

  • с вызовом конструкторов родителей
  • с private/protected/public доступом
  • с перекрытием методов и вызовом методов родителей
  • с проверкой принадлежности объекта классу

..А также посмотрели внутренние механизмы наследования в javascript.

Пишите в комментарии, если что.


Автор: vashurin, дата: 3 апреля, 2008 - 09:45
#permalink

Полезная статья.


Автор: Vijon (не зарегистрирован), дата: 19 декабря, 2008 - 18:07
#permalink

Лично я отказался от прототайпа и использую следующий тип наследования...

function SomeClass(_Arg1, _Arg2)
{
       Parent1.call(this, Arg1);
       Parent2.call(this, Arg2);

       // далее, собственные методы и свойства SomeClass
}

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

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


Автор: Гость (не зарегистрирован), дата: 7 июля, 2010 - 17:54
#permalink

Круто. спс!


Автор: Гость (не зарегистрирован), дата: 18 марта, 2011 - 20:21
#permalink

Разъясните логику поведения. Там в call точно не пропущены '_'?


Автор: barbiturat, дата: 29 мая, 2008 - 19:48
#permalink
function extend(Child, Parent) {
    var F = function() { }
    F.prototype = Parent.prototype
    Child.prototype = new F()
    Child.prototype.constructor = Child
    Child.superclass = Parent.prototype    
}

Этот код не работает, если поля, к которым обращаются методы, заданные в прототипе, определены в конструкторе классов

// копирует все свойства из src в dst
function mixin(dst, src){
    var tobj = {};
    for(var x in src){
        // копируем в dst свойства src, кроме тех, которые унаследованы от Object
        if((typeof tobj[x] == "undefined") || (tobj[x] != src[x])){
            dst[x] = src[x];
        }
    }
    // В IE пользовательский метод toString отсутствует в for..in
    if(document.all && !document.isOpera){
        var p = src.toString;
        if(typeof p == "function" && p != dst.toString && p != tobj.toString &&
         p != "\nfunction toString() {\n    [native code]\n}\n"){
            dst.toString = src.toString;
        }
    }
}

Зачем в этом коде tobj?


Автор: Илья Кантор, дата: 17 июня, 2008 - 16:46
#permalink

О каких полях идет речь в комментарии по поводу extend ?

Про tobj добавил комментарий развернутый в текст статьи, спасибо.


Автор: barbiturat, дата: 20 июня, 2008 - 21:35
#permalink

Под полями я имел свойства. Если свойства, к которым обращаются методы родителя определены в конструкторе родителя (а не в прототипе, что, как вы сами сказали, делать не очень корректно), то код наследуемой функции, не работает. Пример:

function extend(Child, Parent) {
    var F = function() { }
    F.prototype = Parent.prototype
    Child.prototype = new F()
    Child.prototype.constructor = Child
    Child.superclass = Parent.prototype    
}

function Animal(){
    this.tail = true;
}
Animal.prototype.hasTail = function(){return this.tail};
function Man(){}
extend(Man, Animal);

var man = new Man();
alert('man.hasTail() = ' + man.hasTail());

А по поводу tobj, да, интересный прием. Я об этом не подумал Но не короче ли записывать просто {}[x] вместо того, что б создавать переменную tobj?


Автор: Илья Кантор, дата: 21 июня, 2008 - 02:11
#permalink

В Вашем коде вызов man.hasTail() возвращает undefined, потому что в Man() не вызывается конструктор суперкласса.

Чтобы вызвать конструктор родителя (Animal), нужно Man объявить, например, так:

function Man(){
    Man.superclass.constructor.call(this)   
}

После этого код сработает, как Вы и ожидаете, хвост вырастет - мама не горюй

Что касается временного объекта tobj - в данном случае это небольшая оптимизация. Действительно, можно убрать эту переменную, и подставить вместо нее {}.


Автор: Гость (не зарегистрирован), дата: 17 марта, 2013 - 16:16
#permalink

Использование {}[x] вместо tobj[x] не рационально, т. к. каждый раз будет создаваться анонимный объект класса Object.
1)

var tobj = {};
for(var x in src){
	if((typeof tobj[x] == "undefined") || (tobj[x] != src[x])) {
		dst[x] = src[x];
	}
}

2)

for(var x in src){
	if((typeof {}[x] == "undefined") || ({}[x] != src[x])) {
		dst[x] = src[x];
	}
}

На каждой интерации создается 2 анонимных объекта.


Автор: freelancer (не зарегистрирован), дата: 19 июня, 2008 - 18:46
#permalink

Интересно. Не думал что javascript настолько функционален.


Автор: Виталий, дата: 21 июля, 2008 - 01:14
#permalink

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

function a_base() {
  this.a = 'a' ;
}
function b() {
  a_base.call( this ) ; // extends 
  this.b = 'b' ;
  this.constr = function() {
    alert( this.a + '->' + this.b ) ;
  }
  this.constr() ;
}

Автор: Илья Кантор, дата: 2 августа, 2008 - 03:02
#permalink

Спасибо, удобный и рабочий вариант.

Нечто среднее между наследованием на классах (объект создается new) и фабричным подходом: аналогичным образом можно сделать override, приватные переменные.

Та же проблема с instanceof, что и у фабричного подхода, т.к нет цепочки прототипов:

obj = new b;
obj instanceof a // => false

Автор: Гость (не зарегистрирован), дата: 16 августа, 2013 - 17:57
#permalink

Спасибо большое!!!!!!!!!


Автор: chop-chop (не зарегистрирован), дата: 25 июля, 2008 - 16:33
#permalink

Перечитал несколько раз но так и не понял почему не работает такая конструкция

function extend(Child, Parent) {
    var F = function() { }
    F.prototype = Parent.prototype
    Child.prototype = new F()
    Child.prototype.constructor = Child
    Child.superclass = Parent.prototype    
}
function a() {
    this.bar = 1;
    this.getBar = function() {
        return this.bar;
    }
}
function b() {
    this.bar = 2;
}
extend(b,a);
g = new b();
alert(g.getBar());

Выдает ошибку "g.getBar is not a function"


Автор: Илья Кантор, дата: 25 июля, 2008 - 17:22
#permalink

Потому что в потомке не вызван конструктор родителя.

function b() {
    b.superclass.constructor.apply(this, arguments) 
    this.bar = 2;
}

Автор: nikotyn (не зарегистрирован), дата: 1 августа, 2008 - 21:37
#permalink

Не в тему сказано, но может кто нибудь из профессионалов подскажет, возможно ли получить ссылку на родительский объект. К примеру:

function a() {
    this.child = document.createElement("a");
    this.url = "http://www.google.com";
}

var obj = new a();

Дале к примеру используем ссылку по ее назначению
и, например, в некоторой ф-ии, при щелчке на ссылке получаем
ссылку на нее (то есть на this.child).
Так вот вопрос, как получить значение this.url?


Автор: Гость (не зарегистрирован), дата: 5 августа, 2008 - 16:10
#permalink

Например так:

function a() {
var object = this;
this.child = document.createElement("a");
this.child.onclick = function() {
alert(object.url);
return;
}
this.url = "http://www.google.com";
}

var obj = new a();


Автор: Гость (не зарегистрирован), дата: 2 июля, 2010 - 21:26
#permalink

этот способ добраться до методов класса не используя this очень выручает, но если мы запускаем таймер для какого-либо метода класса, то как быть в этом случае? как обойти обращение к объекту экземпляра класса?
function a() {
var object = this;
this.child = document.createElement("a");
this.child.onclick = function() {window.setInterval("alert(obj.url)",1000);
return false;
}
this.url = "http://www.google.com";
}

var obj = new a();


Автор: Гость (не зарегистрирован), дата: 5 августа, 2008 - 16:21
#permalink

Меня интерескет другой вопрос. Вот код:

String.prototype.url = {
	addParam : function() {
		.
		alert(this);
		.
	},
	getParam : function() {
		.
		.
		.
	}
}

Как в методах объекта url докопаться до свойств родительскрго объекта, т.е. String ?


Автор: Илья Кантор, дата: 6 августа, 2008 - 21:11
#permalink

так: String.prototype.toLowerCase


Автор: NightmareZ, дата: 6 августа, 2008 - 02:42
#permalink

Статья замечательная. Очень понравилось. С одной стороны.
С другой же стороны, после Java, C++ и C#, у меня дрожь по коже от извращений JavaScript


Автор: Гость (не зарегистрирован), дата: 21 августа, 2008 - 21:54
#permalink

Это все замечательно, только миллион способов типизировать методы у объекта никак не помогают с областью видимости переменных, а указывать var myVar в том же "составном операторе", где и метод this.setMyVar очень не удобно. Может, как-нибудь можно добиться корректной работы такой конструкции?

function Parent() {
  var myVar;
  this.setMyVar = function(val) { myVar = val; }
  this.getMyVar = function() { return myVar; }
}
function Child() {
  // а тут, например, ничего нет :)
};
var c = new Child();
c.setMyVar(5);
alert(c.getMyVar());

Автор: Гость (не зарегистрирован), дата: 3 декабря, 2008 - 14:33
#permalink

function Child() {
Parent.call(this);
};


Автор: Rossomaha (не зарегистрирован), дата: 3 марта, 2013 - 12:58
#permalink

Thank you))))


Автор: Mikhus (не зарегистрирован), дата: 11 сентября, 2008 - 17:05
#permalink

Существует ООП-библиотека позволяющяя избавиться от нектороых негативных моментов в приведенном подходе. Она здесь - http://ajaxoop.org/. В частности нет необходимости вызывать методы посредством parent.method.call(this, ...) и оператор instanceof работает должным образом, что избавляет от необходимости иметь вспомогательный функционал. В общем приведенная реализация содержит некоторые ошибки из-за не совсем верного использования прототипов ЖС. В реализации же AJAX.OOP эти ошибки устранены.


Автор: Nayjest, дата: 17 июля, 2011 - 02:53
#permalink

О каких ошибках идет речь?
Посмотрел AJAX.OOP, имхо: срань.
По названию сразу подумал, что оно объединяет механизм наследования и асинхронную подгрузку скриптов с построением зависимостей. Ничего подобного. Просто очередной недо-jQuery.


Автор: Алла (не зарегистрирован), дата: 4 декабря, 2008 - 00:37
#permalink

все очень подробно расписаны основы ООП, зачет


Автор: milk3dfx, дата: 3 февраля, 2009 - 05:03
#permalink

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

function Animal(name) {
    this.name = name;
    this.canWalk = true;
}
function Rabbit(name) {
    animal = new Animal(name);
    animal.food = 10;
    return animal;
}
//и тогда...
ob1=Rabbit("Кролик");

Автор: Андрей Параничев, дата: 5 февраля, 2009 - 20:48
#permalink

Это и есть фабричный метод, по сути.


Автор: Гость (не зарегистрирован), дата: 6 февраля, 2009 - 12:31
#permalink

мне очень нравится позиция о "real-life" автора статьи. Я в короткие сроки освоил JavaScript благодаря javascript.ru, не вдаваясь в ненужные подробности. До этого меня тошнило от JavaScript, та как небыло подходящих книг/статей. Ничего подобного(javascript.ru) я не находил. Автору надо памятник ставить


Автор: billigflüge (не зарегистрирован), дата: 28 апреля, 2009 - 12:44
#permalink

- Thank you


Автор: kefi (не зарегистрирован), дата: 12 марта, 2009 - 16:24
#permalink

Мои пару замечаний:
1) Почему нельзя сделать так , как в FullCircle.prototype = Circle.prototype), а именно :

function extend(Child, Parent) {
    /*var F = function() { }
    F.prototype = Parent.prototype           // зачем эти усложнения
    Child.prototype = New F()                  // и столь же сложные масло-маслянные объяснения этого в статье ?
    Child.prototype.constructor = Child
    Child.superclass = Parent.prototype    
    */// Когда можно просто : 
    <strong>Child.prototype = Parent.prototype</strong> ; Child.superclass = Parent.prototype    
}

Сразу замечу, что приведенный код работает.

2) Сказано по моему ошибочно :

Последний вызов приведет к ошибке "too much recursion" из-за того, что this.constructor.superclass, к которому идет обращение в функции bar.identity обращается к родительскому классу zot. В результате bar.identity вызывает сама себя в бесконечной рекурсии.

Нужно так :

Последний вызов приведет к ошибке "too much recursion" из-за того, что this.constructor.superclass, к которому идет обращение в функции bar.identity обращается к дочернему классу zot. В результате bar.identity вызывает сама себя в бесконечной рекурсии.


Автор: Илья Кантор, дата: 12 марта, 2009 - 18:08
#permalink

Уважаемый kefi,

1) Простое присвоение прототипа - это не наследование. Вместо отдельно методов для родителя (в прототипе родителя) и отдельно - методов для потомка (в его прототипе) - у вас будет один общий прототип. О наследовании тут и речи нет.

2) Видимо, опечатка. Поправил.


Автор: Гость (не зарегистрирован), дата: 12 марта, 2009 - 22:38
#permalink

2 Илья Кантор > О наследовании тут и речи нет.
Рад, столь быстрому ответу, но -
Хм... Но :

1) В выше приведенной мной ссылке говорится как раз об обратном, - что наследование реализуется имено так , как я описал, при этом таким образом организуется цепочка прототипов от потомков к предкам. Прокомментируйте ,плз., мою ссылку на параграф из справочника WEB-разработчика Ю. Лукача (кроме того, гляньте параграф 3.7.2.3. Наследование
на той же странице ).

2) Приведенный мной способ работает точно также , как и Ваш, только он проще синтаксически.


Автор: Илья Кантор, дата: 12 марта, 2009 - 23:54
#permalink

Да, глянул. Скажите, как методы добавляются в объект при ООП-подходе Лукача?

Есть два способа:

  1. Circle.prototype.area = ...
  2. либо this.area = ... в конструкторе

Из текста справочника я так понял, что Лукач использует оба, это слегка некорректно: надо уж либо так либо эдак, структура наследования разная выходит. Скажите, какой мы возьмем для рассмотрения ?

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

P.S. Предлагаю вам зарегистрироваться на сайте для продолжения переписки.


Автор: kefi, дата: 13 марта, 2009 - 00:53
#permalink

Есть два способа: И я так понимаю, ОБА они нужны :
Circle.prototype.area = ... - Это даст нам Не Private члены для Circle ( protected и public )
либо this.area = ... в конструкторе Circle - а Это даст нам Private члены для Circle .

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

PS Хотелось бы где-ни увидеть максимально четкие построенные модели OOПодхода в JavaScript с описанием реализованных концепций, пока все только не очень хорошо формализованный набор разных идей...


Автор: Илья Кантор, дата: 13 марта, 2009 - 00:56
#permalink

Вы не так понимаете. Оба подхода дают public члены класса. Просто в одном случае методы хранятся в прототипе, а в другом - непосредственно в объекте. Ну и, разумеется, объявление через this образует замыкания и дает доступ методу к переменным, объявленным через var внутри конструктора (и это уже реальные "приватные" свойства).

Просто при использовании приравнивания прототипов для наследования в одном подходе будут одни глюки а в другом - другие.

На предмет глюков смотрите а) как работает override б) как работает цепочка A -> B -> C


Автор: NecroProger (не зарегистрирован), дата: 27 июля, 2009 - 11:15
#permalink

Первый пункт. Хорошо, а почему бы тогда просто не сделать так:
Child.prototype={prototype:Parent.prototype};
?
Заранее спасибо.


Автор: Илья Кантор, дата: 27 июля, 2009 - 12:11
#permalink

Прототип объекта (он же [[prototype]],__proto__) и свойство prototype это разные вещи.

Прототип ставится по свойству prototype функции-конструктора.


Автор: NecroProger (не зарегистрирован), дата: 27 июля, 2009 - 13:33
#permalink

Ой, точно, понял, что сказал глупость. Приношу извинения.


Автор: nahab (не зарегистрирован), дата: 13 мая, 2011 - 17:32
#permalink
function extend(Child, Parent) {
    /*var F = function() { }
    F.prototype = Parent.prototype           // зачем эти усложнения
    Child.prototype = New F()                  // и столь же сложные масло-маслянные объяснения этого в статье ?
    Child.prototype.constructor = Child
    Child.superclass = Parent.prototype    
    */// Когда можно просто : 
    Child.prototype = Parent.prototype ; 
Child.superclass = Parent.prototype    
}

А если так:

Child.prototype = new Parent.prototype();

Автор: Гость (не зарегистрирован), дата: 20 июля, 2011 - 10:02
#permalink

prototype это не функция, его нельзя "вызвать"
Более корректно будет

Child.prototype = new Parent()

Но этот (очень простой) подход можно практиковать только если конструктор Parent() не принимает никаких аргументов


Автор: yuniki (не зарегистрирован), дата: 21 марта, 2009 - 20:56
#permalink

[url=http://javascript.ru/tutorial/object/inheritance#protected-chlieny]Здесь[/url]
[quote]protected, т.е такие, к которым могут получить доступ наследники[/quote]
Сказано верно, но вывод не аккуратный (если, хотите, неверный) :

function Animal(name) {
 
    var privateVariable = 0
 
    this._protectedName = name

Наследником в классическом ООП считается класс Bird (,а не экземпляр типа Animal ), имеющий наследуемые свойства от предка-класса Animal. Следовательно для упомянутых Вами наследников-классов protected членами будут также свойства объекта Animal.prototype, которые, конечно, будут доступны и в остальных классах-НЕнаследниках Animal , но вот тут уж ничего не поделаешь, кроме уже сказанного Вами - обозначить их подчерком _.

Т.е. для javascript с помощью подчерка можно натянуть три уровня доступа к членам класса ( классы верхнего уровня будут public ) :

private - ( var объявления и внутренние функции ) доступ только изнутри класса(т.е. его конструктора) Animal.

protected - (Animal.propotype._члены с подчерком и this._члены с подчерком) доступ изнутри конструкторов классов-наследников,
но и, поскольку, одновременно, отовсюду через свойства создаваемых экземпляров Animal (new Animal()) , то НЕ рекомендуется использовать НЕ в конструкторе наследника класса Animal подчеркнутое свойство Animal.propotype._член или (new Animal())._член .

public - (Animal.propotype.члены без подчерка и this.члены ) - доступ отовсюду, но для this.члены только через созданые экземпляры класса Animal (new Animal()).член .


Автор: kefi, дата: 29 марта, 2009 - 18:29
#permalink
// копируем в dst свойства src, кроме тех, которые унаследованы от Object
        if((typeof tobj[x] == "undefined") || (tobj[x] != src[x])){

Во-первых цель(копировать КРОМЕ Object) так не достичь ( если б не во-вторых ) ,
Во-вторых , ее достигать и не требуется, т.к. ВСЕ свойства Object.prototype ( как,кстати, и Object,Fucntion,Function.prototype ) DontEnum .

// В IE пользовательский метод toString отсутствует в for..in
    if(document.all && !document.isOpera){
        var p = src.toString;
        if(typeof p == "function" && p != dst.toString && p != tobj.toString &&
         p != "\nfunction toString() {\n    [native code]\n}\n"){
            dst.toString = src.toString;
        }
    }

Да, действительно, IE не видит в for in toString свойство, ну так это можно сделать куда проще и правильнее :

// Если браузер видит свойство toString в for in и уже выше его назначил, то здесь будет просто повторение назначения (что не страшно) :
if (src.toString!==Object.prototype.toString) {dst.toString = src.toString;} ;

Автор: Илья Кантор, дата: 30 марта, 2009 - 03:16
#permalink

Суть этого метода - в избавлении от лишних свойств, добавленных непосредственно в Object.prototype.

При этом если свойство было унаследовано, то оно копируется. Этим проверка отличается от hasOwnProperty.


Автор: kefi, дата: 30 марта, 2009 - 15:32
#permalink

Суть этого метода - в избавлении от лишних свойств, добавленных непосредственно в Object.prototype.
1) Ну так для for in и объекта var tobj = {} оператор :
if((typeof tobj[x] == "undefined") || (tobj[x] != src[x]))
совершенно эквивалентен:
if( tobj[x] != src[x] )
2) И потом, зачем же Вы хотите избавиться от свойств, добавленных в Object.prototype пользователем? Нелогично это - А почему только Вы это хотите для Прародителя, а почему не захотеть того же для одного из потомков в цепи наследования src <- Object.prototype?
А ну впрочем, если src есть первый потомок Object.prototype, то - понятно.

Но тогда непонятно это :
При этом если свойство было унаследовано, то оно копируется. Этим проверка отличается от hasOwnProperty.

Вопрос : От кого унаследовано? Если src первый потомок Object.prototype, то достаточно проверять :
if( ! Object.prototype.hasOwnProperty(x) )
Если не первый, то см.выше.

PS . Насчет toString свойства Вы не ответили.


Автор: Zeroglif, дата: 30 марта, 2009 - 22:06
#permalink

> почему не захотеть того же для одного из потомков в цепи наследования

Свойства 'Object.prototype' и так доступны 'tobj', не обязательно из доступных наследуемых свойств насильно делать свои. Иные же прототипы в цепи возможных прототипов объекта 'src' скорее всего не доступны объекту 'tobj', их свойства подхватываются через 'for-in'.

> toString

Там не только с toString проблема.
https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute


Автор: Azzx, дата: 23 апреля, 2009 - 22:50
#permalink

Гм... Второй день не могу засунуть в голову этот текст. Подозреваю, мне подробностей маловато. Но это фигня - почитаю стандарт. Но вот одного я понять не могу - а чего всё так сложно-то? Почему нельзя сделать так:

<html>
<body>
<script type="text/javascript">

function Parent() {
  this.methodA = function() {
    document.write("methodA<br>")
  }
}

function Child() {
  this.methodB = function() {
    document.write("methodB<br>")
  }
}

function extend(child, parent) {
  child.prototype = new parent()
}

extend(Child, Parent)

function test() {
  var x = new Child()

  x.methodA()
  x.methodB()
}

test()

</script>

</body>
</html>

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


Автор: G@mOBEP (не зарегистрирован), дата: 24 апреля, 2009 - 13:09
#permalink

Rabbit.prototype = new Animal()

Однако, у такого подхода есть два недостатка:

1. Для наследования создается совершенно лишний объект new Animal()
2. Конструктор Animal должен предусматривать этот лишний вызов для и при необходимости делать такое "недоживотное", годное лишь на прототип.


Автор: Azzx, дата: 27 апреля, 2009 - 09:54
#permalink

Пасибо, статью я читал.
По поводу оных пунктов:
1. а) Не понимаю, что в нём лишнего - общие свойства в любом случае где хранятся, так?; б) "Child.prototype = new F()" - это, типа, не объект?
2. На кой ляд ему что-то предусматривать, если это делается функцией Extend в обоих случаях?


Автор: Илья Кантор, дата: 27 апреля, 2009 - 10:55
#permalink

Разберем на примере. Вот определение объекта Parent:

function Parent(elem) {
  elem.innerHTML = 'I am parent'
  ... 
}

Parent нельзя унаследовать его через Child.prototype = new Parent(), потому что в Parent не предусмотрено создание недородителя, годного лишь на прототип. Ошибка будет в браузере.

Если мы все же предусмотрим такой вызов, то объект new Parent() все равно будет "лишним", потому что этот Parent нам нужен не для практических целей, для которых он задуман, а только для наследования. В нормальном ООП, которое мы хотим иметь, такого быть не должно.


Автор: Гость (не зарегистрирован), дата: 14 июня, 2009 - 01:17
#permalink

Такой вот замысловатый код решает следующие проблемы
1) реализует наследование на основе прототипов
2) не создает "лишних" объектов родителей
3) сохраняет функционал instanceof
4) работает во всех современных браузерах

Пример взят из книги http://www.bhv.ru/books/full_contents.php?id=184616

Разумеется, это только идея. Настоящий код обернут в функции create и derive

function Class(){}
Class.nativePrototype = Class.prototype;

function Parent(elem) {
  elem.innerHTML = 'I am parent'
  ... 
}

function Child(arg0,arg1){
  Parent.apply(this, [arg0,arg1]);
}

Class.prototype = Parent.prototype;
Child.prototype = new Class();
Child.prototype.consructor = Child;
Class.prototype = Class.nativePrototype;

var obj = new Child();

Автор: Ден Хабаровский (не зарегистрирован), дата: 5 мая, 2011 - 09:18
#permalink

Илья, спасибо за статью! Хорошая точка отправки для понимания прототипного ООП в javascript.

Предложение

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

Вопрос

Обязательна ли подпрограмма mixin? Разве не достаточно условиться, что методы в prototype потомка будут добавлены только после вызова extend?

Т.е. можно ли в примере

// ---- родительский класс ----

function Animal(name, walkSpeed) {
	this.name = name
	this.walkSpeed = walkSpeed
}

// добавляем методы объекта
mixin(Animal.prototype, {

	// пример переменной
	distance: 0,

	// пример метода
	walk: function(time) {
		this.distance = this.distance + time*this.walkSpeed
	},

	toString: function() {
		return this.name+" на расстоянии "+this.distance
	}
})

// ---- класс наследник ----

function Bird(name, walkSpeed, flySpeed) {
	// вызов родительского конструктора
	Bird.superclass.constructor.call(this, name, walkSpeed)

	this.flySpeed = flySpeed
}
extend(Bird, Animal)

mixin(Bird.prototype, {
	fly: function(time) {
		this.distance = this.distance + time*this.flySpeed
	}
})

вызов mixin заменить на

Animal.prototype.distance = 0
Animal.prototype.walk = function(time) {
		this.distance = this.distance + time*this.walkSpeed
	}

и т.д.?

Заранее благодарю за ответ. Надеюсь, Вы заглядываете в комментарии к старым статьям. :-)


Автор: Леонид Евстигнеев, дата: 9 июня, 2009 - 13:29
#permalink

И всё таки в рассказе про extend есть путаница между [[prototype]] и prototype.

Написано поиск свойств будет идти по цепочке:

child -> Child.prototype -> Parent.prototype

Эта строка вводит в заблуждение. Алгоритм получения свойств описан в 8.7.1, 8.6.2.1
И если написать точнее, то свойство ищется по цепочке

child -> 
child.[[prototype]] оно же (new F()) оно же (Child.prototype) -> 
(new F()).[[prototype]] оно же (F.prototype) оно же (Parent.prototype)

Если написать так, то по моему, становится намного понятней.
А так же понятно почему надо использовать mixin(..., ...).
И ясно, что функцию mixin(..., ...) надо использовать после extend (..., ...)


Автор: Алексей Я (не зарегистрирован), дата: 30 июня, 2009 - 12:56
#permalink

После заголовка Вторая строка. Меняем F.prototype становится непонятно, зачем вообще это все делается, с какой целью?
Хорошо рассказано о prototype, очень доступно, с примерами. Что же такое constructor понять из контекста статьи сложно.


Автор: NecroProger (не зарегистрирован), дата: 3 августа, 2009 - 17:07
#permalink

Несколько примечаний.
1. Класс F можно описать как "Parent, годный лишь на прототип".
2. Условно можно написать extend так:

Child.prototype={__proto__:Parent.prototype,constructor:Child};
Child.superclass=Parent.prototype;

3. Интересно, что

Object.prototype===Object.prototype.[[prototype]]

. И тем не менее, при ненахождении нужного свойства/метода в Object.prototype, интерпретатор не зацикливается.
4. Неужели в JS нет стандартной функции объединения объектов?.. Очень жаль.


Автор: tenshi, дата: 9 сентября, 2009 - 13:18
#permalink

4. а ему оно и не нужно ;-)

var Figure= function(){}
Figure.prototype= new function(){
    var update= function(){} // private
    this.move= function(){} // public
}

var Rect= function(){}
Rect.prototype= new function(){
    Figure.prototype.constructor.call( this )
    var update= function(){} // private
    this.move= function(){} // public overloaded
}

.ня


Автор: Ramzess, дата: 6 ноября, 2009 - 00:36
#permalink

Можно ли показать реальный и простой пример использования прототипов... а то я не совсем понимаю зачем нужны "дополнительные свойства". Здесь не всем понятен компьютерный сленг...


Автор: zenitchik (не зарегистрирован), дата: 10 декабря, 2009 - 21:06
#permalink

Я плохо осознал, зачем в JavaScript притащена такая несвойственная для него сущность как класс?


Автор: ilya-stromberg (не зарегистрирован), дата: 20 декабря, 2009 - 15:39
#permalink

Пожалуйста, поясните, где и в каком порядке нужно вызывать функции extend и mixin? Из текста статьи я этого не понял. После таких объектно-ориентированных языков как C++ и C# сложно разбираться в искусственном создании наследования. Если можно, приведите пример и объявите одну функцию, реализующую наследование.

Например:

//Объявляем базовый класс любым известным способом
function Parent(varA) {

//переменные класса объявляются здесь
this.myVarA = varA

//Методы класса объявляются здесь
this.methodA = function() {
document.write("methodA" + this.myVarA + "")
}
}

//Объявляем производный класс любым известным способом
function Child(varB) {

//Вызываем конструктор базового класса
Child.superclass.constructor.call(this, varA)

//переменные класса объявляются здесь
this.myVarB = varB

//Методы класса объявляются здесь
this.methodB = function() {
document.write("methodB" + this.myVarB + "")
}

//Наследование обеспечивается вызовом функции extend
extend(Child, Parent)

Вопрос: если пример написан правильно, то тогда зачем вообще нужна функция mixin и где ее вызывать?


Автор: Kind (не зарегистрирован), дата: 19 января, 2010 - 22:20
#permalink

Как можно увидеть свойства прототипов встроенных классов?
Ни один из способов не работает. Даже без hasOwnProperty().

var obj={};
for(var x in obj){
    console.info(x);
}

или

var obj={};
for(var x in obj.__proto__){
    console.info(x);
}

или сразу

for(var x in Object.prototype){
    console.info(x);
}

Автор: Гость (не зарегистрирован), дата: 12 марта, 2010 - 00:20
#permalink

а есть разница как вызывать родительские конструктор Rabbit.superclass.constructor.apply(this, arguments);
или Animal.apply(this);


Автор: Гость (не зарегистрирован), дата: 28 января, 2011 - 16:34
#permalink

Разница в том, что ты указываешь от какого ты обьекта наследуешся, если завтра ты захочешь наследоваться от класса Animal11, а у тебя создано5 обьектоа где используеться вместо superclass полное имя - от прийдеться менять везде имя.. а так только в одном месте..


Автор: Гость (не зарегистрирован), дата: 12 марта, 2010 - 00:23
#permalink

у меня и так и так с работало


Автор: mcpro, дата: 13 марта, 2010 - 19:52
#permalink

не могу понять с наследованием.
Вот код

function Animal(name){
			this.name = name;
		}
		var animal = new Animal('cat');
		function Cat(){
			
		}
		Cat.prototype = animal;
		var cat = new Cat('barsic');
		alert(cat.name);

по идее Cat наследник Animal. Тогда вызов new Cat('barsic') должен создать объект со свойством name равным 'barsic', но alert(cat.name); выводит cat, а не barsic. Не пойму (


Автор: Илья Кантор, дата: 13 марта, 2010 - 22:40
#permalink

Потому что автоматического вызова конструктора суперкласса не происходит. У вас же function Cat() пустая.

Правильный вариант для вашего примера:

function Cat() {
  Animal.apply(this, arguments)
}

Автор: Родзевич Олег (не зарегистрирован), дата: 29 марта, 2010 - 02:30
#permalink

Вот, имеем функцию, которая обеспечивает наследование одного объекта другим

function extend(Child,Parent){
	var F =new Function();
	F.prototype=Parent.prototype;
	Child.prototype=new F();
	Child.prototype.constructor=Child;
	Child.superclass=Parent.prototype;
}

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

Object.prototype.extend=function(Parent){};

Такое возможно? Если да, то подскажите как это можно сотворить?


Автор: mycoding, дата: 27 мая, 2010 - 12:54
#permalink

Хотел спросить, а не лучше ли в extend вот так делать?

function Parent(){
	this.name='Родитель';
	this.say = function(){alert(this.name);}
}

function Child(){
	this.name = 'Ребёнок';
	this.sayConstructor = function(){alert(this.constructor);}
}

function extend(Parent,Child){
 var p = new Parent();
 Child.prototype = p;
 Child.prototype.constructor = Child;
}

extend(Parent,Child);

var p = new Parent();
var c = new Child();
c.say();
c.sayConstructor();

Автор: Гость (не зарегистрирован), дата: 20 июня, 2010 - 02:55
#permalink

Хотел спросить, а не лучше ли в extend вот так делать?
Этот код не работает


Автор: Glinkz, дата: 22 июня, 2010 - 17:55
#permalink

Вот походу изучения у меня возник вопрос...
Смотрите:
Существует некий объект
co = {
some : function(){ /*some code*/} // в нем функция
}

и есть некая функция Co1

вопрос: можно ли сделать функцию co1, принадлежащую функцие Some
то есть вызов её выглядел бы так: "Co.Some.Co1"


Автор: X000R (не зарегистрирован), дата: 13 июля, 2010 - 12:26
#permalink

Приведите пожалуйста пример наследования/заимствования методов/свойств встроенных типов объектов, ну скажем от Image().


Автор: Дан (не зарегистрирован), дата: 1 августа, 2010 - 02:28
#permalink

Фабричный метод в корне ошибочен (хотя он и рабочий). Вы похоже не до конца поняли смысл prototype. Поясню на примере.

Рассморим два варианта кода:

function Rectangle(w, h) {
  this.width = w;
  this.height = h;
}
Rectangle.prototype.area = function( ) { return this.width * this.height; }

и

function Rectangle(w, h) {
  this.width = w;
  this.height = h;
  this.area = function( ) { return this.width * this.height; }
}

И первый код и второй мы можем использовать одинаковым способом:

var r = new Rectangle(8.5, 11);
var a = r.area();

и все отлично будет работать.

Однако, второй вариант - безответственное транжирство ресурсов, так как по сути одна и та же функция area будет заново определяться для каждого экземпляра класса. При использовании prototype ничего подобного происходить не будет.

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

Вообще говоря, метод экземпляра - это по сути статический метод класса, аргументом которого выступает экземпляр этого класса. Кстати, таким образом можно без prototype организовывать корректное ООП, однако выйдет многословнее и менее удобно, чем с использованием prototype.


Автор: Васька (не зарегистрирован), дата: 19 августа, 2010 - 18:46
#permalink
var Depart = new myDates();
var Arrive = new myDates();
Depart.getFisrstDate('2010-07-09','mon');

function myDates() 
{
	this.days = ['sun','mon','tue','wen','thu','fri','sat'];
}

myDates.prototype = {

	getFisrstDate: function(date,day)
	{
		this.myDate = new Date(date);
		return;
	}
}

в ответ получаю

Ошибка: Depart.getFisrstDate is not a function


Автор: B@rmaley.e><e, дата: 19 августа, 2010 - 22:21
#permalink

Наверно стоит сначала объявить класс и его прототип, а потом уже создавать экземпляры?


Автор: Гость (не зарегистрирован), дата: 8 сентября, 2010 - 13:14
#permalink

Трудно было по статье понять, что [[prototype]] и prototype - разные вещи, что свойство prototype имеет смысл только для функций, что единственное его назначение - устанавливать истинный прототип объекта при использовании оператора new, что единственный способ создавать объекты - использовать функции и new, что в функции extend свойство superclass - это пользовательское свойство, а не встроенное (как prototype). Все это следовало бы указать.
Очень помогло в понимании прототипов начало 4-й главы переведенного стандарта ecma.
Вообще, глава очень непонятна - новичку тут делать нечего, а тот, кто знаком с другими ОО-языками, увидит много недоговорок. Думаю, что Вам следует пересмотреть всю статью и более структурированно и точно всё изложить.


Автор: oleg2, дата: 8 сентября, 2010 - 15:43
#permalink

Илья, а подскажи пожалуйста, есть ли какие-либо недостатки у следующего метода...

function extend(classParent, classChild){
            var constructor = function(){};

            classChild.constructor = (function(p, c) {
               var parentProto = p.prototype;
               var childCons = c.constructor;
               return function() {
                  parentProto.constructor.apply(this, arguments);
                  childCons.apply(this, arguments);
               }
            })(classParent, classChild);

            var classResult = classChild.constructor;
            constructor.prototype = classParent.prototype;
            classResult.prototype = new constructor();
            for (var i in classChild){
               if (classChild.hasOwnProperty(i))
                  classResult.prototype[i] = classChild[i];
            }
            classResult.prototype.constructor = classChild.constructor;
            classResult.superclass = classParent.prototype;
            return classResult;
         }

Отличия:
- в качестве расширяемого класса передаем не функцию-конструктор а объект со свойствами-методами будущего класс, свойством "constructor" объявляем функцию-конструктор.
- основное отличие - в конструкторе класса-наследника не надо вызывать конструктор класса-родителя - это делается автоматически (подразумевается что он всегда есть)


Автор: Илья Кантор, дата: 27 сентября, 2010 - 20:06
#permalink

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

Если оно работает как надо, то вроде все гуд..

---
Илья


Автор: Гость (не зарегистрирован), дата: 9 сентября, 2010 - 20:35
#permalink

Кто пользовался функцией extend?, я после ее использования не могу вызвать конструктор базового типа

function extend(Child, Parent) {
var F = function() { }
F.prototype = Parent.prototype
Child.prototype = new F()
Child.prototype.constructor = Child
Child.superclass = Parent.prototype
}

Дебаг показывает что в Parent.prototype.constructor лежит конструктор Object, это нормально?


Автор: oleg2, дата: 10 сентября, 2010 - 12:47
#permalink

Я пользовался и пользуюсь.

А как зовешь конструктор?


Автор: Гость (не зарегистрирован), дата: 5 октября, 2010 - 20:07
#permalink

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


Автор: Гость (не зарегистрирован), дата: 20 октября, 2010 - 15:52
#permalink

Что за возмущения? Не нравится - не используй... Как говорится, не любишь баб, не муча орган.
"JavaScript и Java

Общим заблуждением является то, что JavaScript аналогичен или тесно связан с Java, это не так[17]. Оба языка имеют C-подобный синтаксис, являются объектно-ориентированными и как правило широко используются в клиентских веб-приложениях, на этом их сходство заканчивается:

* Java реализует ООП подход, основанный на классах, JavaScript на прототипах;
* Java имеет статическую типизацию, JavaScript динамическую типизацию;
* Java загружается из скомпилированного байт-кода; JavaScript интерпретируется напрямую из файла (но часто с незаметной JIT-компиляцией)."(wiki)
Оба языка создавались разными людми, и практически параллельно.


Автор: pluseg, дата: 22 октября, 2010 - 19:39
#permalink

Почему-то мне кажется, что создатели языка не такие уж и нубы в программировании.

К тому ж при таком подходе можно сказать и про создателей Java. "Вот ведь делать нехер было, придумали свой язык, С++ им не хватало!"


Автор: Гость (не зарегистрирован), дата: 9 октября, 2010 - 03:55
#permalink

Подскажите, а то понять не могу:
Почему требует ";" перед фиг. скобкой? При этом правильно не работает.

plastic = new material();{
	this.name='...';
	this.description='...';
}

Автор: Гость (не зарегистрирован), дата: 3 мая, 2011 - 02:24
#permalink

потому что new используете и одновременно пытаетесь описать функцию


Автор: pluseg, дата: 22 октября, 2010 - 18:13
#permalink

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

В наболее распространенных javascript-библиотеках используется первый подход, т.е добавление методов в прототип.

Поясните, пожалуйста, почему добавление методов в прототип предпочтительнее?


Автор: HelpeR, дата: 25 ноября, 2010 - 23:15
#permalink

> Поясните, пожалуйста, почему добавление методов в прототип предпочтительнее?

Потому, что при создании нескольких объектов будет создаваться много копий одних и тех же методов!

Пример

function f() {
    this.method = function() { 
        /* some code */
    }
}

var a = new f();
var b = new f();
// это приведет к тому, что будет создано два метода method один для а второй для b. Т.е. при вызове a.method() будет вызываться свой метод method
//а при записи
function f() {};

f.prototype.method = function() {
    /* some code */
}
var a = new f();
var b = new f();
// метод method не будет создаваться два раза. А при его вызове он будет доставаться из прототипа объекта

Автор: Beast Winterwolf, дата: 9 января, 2011 - 02:23
#permalink

Какая же мутная вещь эти прототипы. Спасибо конечно автору за статью, но я так ничего и не понял Sad Попроще бы как-нибудь это разложить по полочкам для совсем тупых вроде меня. А то я даже сидел и переписывал код, заменял Animal на fAnimal (функция) а animal на oAnimal (объект), что бы не путаться, и всёравно запутался Sad


Автор: Beast Winterwolf, дата: 9 января, 2011 - 02:23
#permalink

Какая же мутная вещь эти прототипы. Спасибо конечно автору за статью, но я так ничего и не понял Sad Попроще бы как-нибудь это разложить по полочкам для совсем тупых вроде меня. А то я даже сидел и переписывал код, заменял Animal на fAnimal (функция) а animal на oAnimal (объект), что бы не путаться, и всёравно запутался Sad


Автор: Beast Winterwolf, дата: 9 января, 2011 - 02:57
#permalink

И ещё вопрос - в чём преимущество использования прототипов, если они такие мутные и неудобочитаемые. Т.е. вот приведу пример: есть у меня 36 игральных карт, у каждой есть свойства (масть и номер) и функции (положить на стол, дать игроку и т.п.)... Так вот объясните, чем лучше/удобнее

карта.дать_игроку(масть, номер)

чем

дать_игроку(карта, масть, номер)

Зачем встраивать функции в объекты, если можно задавать объекты в аргументах функций и не парить себе мозги?


Автор: Beast Winterwolf, дата: 11 января, 2011 - 07:13
#permalink

Разобрался сам! простите за дурацкие вопросы
Вот так я сделал птицу животным:

//
		// Животные (умеют ходить)
		function Animal () {
			this.name = "безымянное животное";
			this.moving = 1;
			this.run = function(distance) {
				return distance / this.moving
			}
		}

		// Птички (умеют летать)
		function Bird () {
			this.name = "безымянная птичка";
			this.flying = 10;
			this.fly = function(distance) {
				return distance / this.flying
			}
		}

		// Зайцы (умеют прыгать)
		function Rabbit () {
			this.name = "безымянный зайчик";
			this.jumping = 8;
			this.jump = function(distance) {
				return distance / this.jumping
			}
		}

		// Птички - это такие животные
		Bird.prototype = new Animal();
		// Зайчики - тоже животные
		Rabbit.prototype = new Animal();

		// Делаем ворону :о)
		var v = new Bird ();
		v.name = "Каркуша";
		v.moving += 3; // Наша Каркуша бегает чуть быстрее обычных птиц!
		document.write("Имя: " + v.name + "<br>");
		document.write("Скорость движения: " + v.moving + "<br>");
		document.write("Скорость полёта: " + v.flying + "<br>");
		document.write("Пробегает 10 метров за: " + v.run(10) + " секунд<br>");
		document.write("Пролетает 10 метров за: " + v.fly(10) + " секунд<br>");

Автор: Beast Winterwolf, дата: 16 января, 2011 - 20:39
#permalink

Но вообще в этом плане рулит мутулс ! http://mootools.net/docs/core/Class/Class


Автор: UpJump, дата: 19 января, 2011 - 16:54
#permalink

Заранее извиняюсь за вопрос так как предчувствую что напишу глупость но понять не могу так что...

Никак не пойму зачем нужен этот код.

if(document.all && !document.isOpera){
		var p = src.toString;
		if(typeof p == "function" && p != dst.toString && p != tobj.toString &&
		 p != "\nfunction toString() {\n    [native code]\n}\n"){
			dst.toString = src.toString;
		}
	}

В ie ваша функция mixin(); прекрасно работает и без нее.


Автор: Сергей Каменский (не зарегистрирован), дата: 27 января, 2011 - 23:37
#permalink

Посещал ваши лекции в Харькове. Очень помогли, спасибо. В благодарность за это дарю, так сказать, метод который я придумал после лекции и активно используем в нашем фреймворке.

когда делаем extend сохраняем перебопределяемую функцию в саму функцию как свойстово parent

//for each new property 
    for (var k in prot)
    {
        //save old property parent of new function
        // can be used in method as 
        //arguments.callee.parent.apply(this, arguments);
        
        var old = constr.prototype[k] 
        constr.prototype[k] = prot[k]        
        if ($.isFunction( prot[k]) ) 
        {
            // save old function to new function filed 'parent'
        
            constr.prototype[k].parent = $.isFunction(old)? old : function(){};
        }
        
        old = null;
    }

что позволяет запросто обойтись без прямых указаний на предка и позволяет легко рефакторить код (вставлять или удалять предков)

вызывается метод предка так

var Zaporojets = $.Class(
            Car,
            {
                toString : function()
                {
                    return 'Zaporojets';
                },
                _create : function()
                {
                    arguments.callee.parent.apply(this, arguments);  //magic
                    //your code
                },
                getA: function(a)
                {
                    return 'A returned';
                },
                drive : function()
                {
                    arguments.callee.parent.apply(this, arguments);
                }
            }
        );

Как вам такая идея?


Автор: Гость (не зарегистрирован), дата: 28 января, 2011 - 00:03
#permalink

Добрый вечер!

Не особо ясна роль

__proto__

, это аналогичная ссылку prototype в Gecko? В чем основное отличие? Если можно на простом примере.

Заранее благодарен!


Автор: Гость (не зарегистрирован), дата: 1 февраля, 2011 - 00:41
#permalink
function Hamster() {
    this.food = []
}
Hamster.prototype = {
    food: [], // просто для информации
    found: function(something) {
    this.food.push(something)
    }
}

пример с разделением данных не работает. почему всёравно у хомяков общий массив food??


Автор: nahab (не зарегистрирован), дата: 13 мая, 2011 - 18:14
#permalink

у меня пример вполне работает.

Единственное что если вы вдруг сделаете
delete someHamster.food
у вас все равно останется родительский food
Конечно маловероятный случай.
Но то что "просто для информации" надо удалить как по мне


Автор: Гость (не зарегистрирован), дата: 2 февраля, 2011 - 13:30
#permalink

Опечатка: В наиболее распространенных


Автор: Коллега (не зарегистрирован), дата: 16 февраля, 2011 - 21:23
#permalink

Здравствуйте, коллеги!Статья заслуживает внимания, но позвольте обратить ваше внимание на неточность в статье. Неточность допущена при обьяснении четвёртой строки авторской версии функции extend:

function extend(Child, Parent) {
	var F = function() { }
	F.prototype = Parent.prototype
	Child.prototype = new F()
	Child.prototype.constructor = Child
	Child.superclass = Parent.prototype
}

Выдержка из цитаты:

Свойство Child.prototype.constructor осталось старое, и его нужно поправить строкой …

Неточность заключается в следующем.Сама операция правильная. Однако поведение операции раскрыто не верно:

  1. то, что в цитате подразумевается под старым свойством (ссылка на конструктор суперкласса), НЕ поправляется, а остаётся прежним;
  2. то, на что в цитате ссылаются, создаётся как НОВОЕ свойство.

В результате выполнения данной операции будет создано новое свойство Child.prototype.constructor в контексте обьекта (new F). Старое же свойство (new F).[[prototype]].constructor останется без изменений.
Иначе, если следовать поведению согласно цитате, теоретически возникла бы петля: зацикливание при вызове конструктора суперкласса из конструктора потомка. Практически, такое поведение не возможно. JavaScript не разрешает потомкам подменять какие-либо свойства прототипов. Именно подменять.Основанием для вышеизложенных утверждений послужили:

  • стандарт ECMA-262, 3я редакция, раздел 8.6.2, третий абзац
  • практика

Спасибо автору за хорошую статью.Спасибо всем за внимание.


Автор: Гость (не зарегистрирован), дата: 7 мая, 2011 - 21:21
#permalink

КАКОЙ РАХИТ придумал этот язык!!! Руки ему оторвать надо, вместе с головой.Уже мозг кипит от этой Javascript. Если говорят что в PHP бардак - то здесь я даже слова не могу подобрать....Полностью согласен, что это гавноскрипт, от которого к сожалению никуда не подеваться. Любят сейчас юзеры всякую прыгающую и мигающую хрень. Если кто то знает нормальную альтернативу этому дерьму - подскажите...


Автор: Гость (не зарегистрирован), дата: 8 мая, 2011 - 22:27
#permalink

Аргументы будут или считать это пердежом на воду?


Автор: Гость (не зарегистрирован), дата: 13 июля, 2011 - 18:35
#permalink

А разве описанных в статье извращений недостаточно?)


Автор: Гость (не зарегистрирован), дата: 14 июля, 2011 - 18:55
#permalink

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


Автор: kobezzza, дата: 15 июля, 2011 - 01:44
#permalink

Вашей глупости и нетерпения достаточно


Автор: b (не зарегистрирован), дата: 30 мая, 2011 - 12:50
#permalink

Вопрос такой, а при реализации функции наследования, почему-бы в ней не вызывать конструктор родителя сразу?

this.superclass.constructor.call(this.prototype);

Автор: Гость (не зарегистрирован), дата: 26 июля, 2013 - 13:01
#permalink

такое не будет работать, если в иерархии 3 или больше классов. Свойство this.superclass будет одно для всех, таким образом конструктор непосредственного родителя терминального класса будет вызывать постоянно сам себя и зациклится.


Автор: UX (не зарегистрирован), дата: 7 июля, 2011 - 20:42
#permalink

Немного изменил функцию наследования, чтобы ее можно было вызывать после определения прототипа, также во внимание принимаются getter'ы и setter'ы:

Object.prototype.extend = function(parent) {
    var child_prototype = this.prototype;

	var F = function() { };
	F.prototype = parent.prototype;
	this.prototype = new F();

    for (var prop in child_prototype) {
        if ( ! child_prototype.hasOwnProperty(prop))
            continue;

        var getter = child_prototype.__lookupGetter__(prop);
        var setter = child_prototype.__lookupSetter__(prop);

        if (getter || setter) {
            if (getter)
                this.prototype.__defineGetter__(prop, getter);
            if (setter)
                this.prototype.__defineSetter__(prop, setter);
         }
         else
            this.prototype[prop] = child_prototype[prop];
    }

	this.prototype.constructor = this;
	this.superclass = parent.prototype;
};

Пример использования:

function A()
{
    this._prop1 = 'first';
}

A.prototype = {
    constructor: A,
    get prop1() { return this._prop1; },
    set prop1(val) { this._prop1 = val; },
};

function B()
{
    B.superclass.constructor.apply(this, arguments);
    this._prop2 = 'second';
}

B.prototype = {
    get prop2() { return this._prop2; },
    set prop2(val) { this._prop2 = val; },
};

B.extend(A);

Автор: Alex M, дата: 21 июля, 2011 - 21:03
#permalink

что-то я запарился с комментариями, 3 раз пытаюсь запостить солюшен, а мне пишут, что отправлен на модерацию, хотя вижу сообщения чуть ли не от анонимусов )
см. http://ifolder.ru/24816602


Автор: Vitalii (не зарегистрирован), дата: 27 июля, 2011 - 03:27
#permalink

Вникаю понемногу в наследование. Не могу разобраться, почему алерты происходят в последовательности 0-3-1-2. В целом, хочу понять, как работает наследование в jQuery


var classWin=(function(){
var classWin=function(){
return new classWin.fn.init()
};
classWin.fn={
constructor: classWin,
init:function(){
alert(3)
},
test:function(){
alert(2)
}
};
classWin.fn.init.prototype = classWin.fn
return classWin
})();

alert(0)
var objWin=new classWin()
alert(1)


Автор: Гость (не зарегистрирован), дата: 6 августа, 2011 - 22:44
#permalink
function record()
{
    this.rec=12345;
}
function record4()
{
    record.apply(this,arguments);// вызывает конструктор record
    this.rec=6789; // переопределям rec
}
extend(record4, record);

test2=new record4();

Как из test2 вызвать свойство rec родителя?


Автор: Гость (не зарегистрирован), дата: 2 ноября, 2011 - 05:33
#permalink

test2.rec;
только вот у меня extend не заработал почему то...


Автор: Wayne, дата: 18 сентября, 2011 - 13:12
#permalink

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


Автор: romirez, дата: 3 октября, 2011 - 13:02
#permalink

Очень полезная статья.
Но функция extend выносит мозг Wacko
Видимо я не все пока понимаю...

1) Почему extend нельзя сократить до:

Child.prototype = new Parent();
Child.prototype.constructor = Child;

Что меняет этот трюк с F??

2) Запись:

Child.superclass = Parent.prototype

Добавляет в объект вызова Child свойство superclass
ссылающееся на "абстрактный" класс родителя.
Это мало что дает... ((
А вот как эмулировать __proto__?
Я правильно понимаю, что:

Child.prototype.superclass = Child.prototype

Будет менять ссылку для всех экземпляров объектов Child
Неужели никак Cray нельзя сделать ссылку из объекта на свой прототип-объект?

Спасибо!


Автор: Гость (не зарегистрирован), дата: 27 октября, 2011 - 07:11
#permalink

Отличная статья.
Прототип действительно напрягает, можно ли как-то обойтись без него?
Какие грабли меня ждут если я буду делать наследование вот таким образом:

var ParentClass = function() {
 var that = this; 

 //private fields
 var x,y,z;

 //public fields
 that.q = 1;
 that.w = '2';

 //private methods
 var myPrivateMethod = function(){ 
 };
 
 //public methods
 that.myPublicMethod = function(){
  // do something
 };

 return that;
}

var ChildClass = function(){
 var that = new ParentClass();
 
 //override public method
 var myPublicMethodSuper = that.myPublicMethod;
 that.myPublicMethod(){
  //call a parent method if required
  myPublicMethodSuper();
  
  //do something else...
 };
 return that;
}

Автор: Гость (не зарегистрирован), дата: 27 октября, 2011 - 07:15
#permalink

Ошибка

that.myPublicMethod = function(){
  //call a parent method if required
  myPublicMethodSuper();
  //do something else...
 };

Автор: И.В.Ануш-ка (не зарегистрирован), дата: 29 ноября, 2011 - 18:51
#permalink

Комментарии типа "А что будет, если прототип функции сделать числом и почему статья этого не описывает, это неправильная статья" - не принимаются. Не делают такого в real-life.

Я даже растерялся малость. С таким апломбом, да такая, простите, глупость сказана.
Честно говоря, прототип всегда и безусловно число и только число. Любая ссылка - всего лишь unsigned integer и никак иначе. Выражаясь еще доходчивей, это номер ячейки RAM.


Автор: Гость (не зарегистрирован), дата: 26 декабря, 2011 - 03:51
#permalink

Что, так прям и пишут:
a.prototype = 7 или 0x154Ah ?
Уважаемый, не ведите себя как м... чудак, здравомыслящим людям понятен контекст о котором говорил автор, более того думаю значительная часть посещающих эту страницу знакома с моделями организации памяти. Если вы хотели блеснуть знаниями "подноготной компутеров", милости просим ну хотя бы на wasm.ru, например, блесните там чем-нить и я буду с интересом наблюдать как вас "повозят по столу", иносказательно еснно, я против неоправданного насилия.


Автор: LMnet, дата: 14 декабря, 2011 - 13:22
#permalink

После С++ достаточно сложно вникнуть в механизмы наследования JavaScript. Мне не понятно, почему не работает следующий пример:

function vehicle()
{
	this.speed = 100;
}

function car(_name)
{
	this.name = _name;
	this.capacity = 4;
	this.prototype = vehicle;
}

function objTestStart()
{
	var lada = new car("Лада");
	
	console.log(lada.name, lada.capacity, lada.speed);
}

lada.speed определяется как undefined.


Автор: Гость (не зарегистрирован), дата: 8 января, 2012 - 05:28
#permalink

можно поправить ваш код так:

function vehicle()
{
    var speed = 100;
	return {speed:speed};
};
function car(_name)
{
    this.name = _name;
    this.capacity = 4;   
}
car.prototype = new vehicle;

function objTestStart()
{
    var lada = new car("Лада");
    console.log(lada.name, lada.capacity, lada.speed);
}
objTestStart();

console.log(new vehicle().speed);
console.log(vehicle().speed);
var testSpeed = new vehicle();
testSpeed.speed = 200;
console.log(testSpeed.speed);
console.log(vehicle().speed);

Результат:
Лада 4 100
100
100
200
100


Автор: Гость (не зарегистрирован), дата: 11 января, 2012 - 11:28
#permalink

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

Если да, то опишите, пожалуйста, пример, начиная с модели наследования.


Автор: Гость (не зарегистрирован), дата: 8 апреля, 2013 - 21:15
#permalink

Абстрактные методы содержатся в абстрактных классах, при использовании абстрактного класса напрямую (не через потомка) вызывается ошибка:

var Abstract=function(){ throw Error('Ошибка!') }

Т.е. достаточно вызвать ошибку в конструкторе абстрактного класса.


Автор: Гость (не зарегистрирован), дата: 8 февраля, 2012 - 13:48
#permalink

вы БЫДЛО!


Автор: Гость (не зарегистрирован), дата: 12 февраля, 2012 - 16:13
#permalink

Статья хорошая для новичков, но некоторые моменты выносят мозг.

1 - сперва автор пишит: объекты наследуют от объектов
после приводит пример:
function Rabbit(name) {
this.name = name
}
Rabbit.prototype = animal

Как я вижу что наследование тут происходит от обьекта "animal" к функции(класса) Rabbit. А не обьект к обьекту.

2 - сперва автор наследование показывает нам простым приравниванием:
Rabbit.prototype = animal
ниже уже через new

Rabbit.prototype = new Animal() и как видно уже функция(класс) от функции(класса)
Дак как правильно? Разъясните пожалуйста.

3 - непонятно чем отличается способ добавления новых методов:
Rabbit.prototype.run = function(..) { ... }
и
this.run= function() {

}

в Rabbit.

4 - в коментариях прочитал кучу способов, что бы не засорять память и не использовать суперфункцию extend (которую ваще не понял).
Как все таки лучше новые обьтекты создавать что бы память не засорять?


Автор: Гость (не зарегистрирован), дата: 30 марта, 2012 - 19:15
#permalink

Как вызвать метод родительского класса:

так не работает

function Rabbit(){
Rabbit.superclass.run.apply(this, ...)
}


Автор: Shire (не зарегистрирован), дата: 23 апреля, 2012 - 21:33
#permalink

Хорошая статья, но я полностью согласен с этим комментарием - объяснение функции extend только еще больше запутывает. К тому же, я считаю, объект должен знать своего родителя по имени. Также я предпочитаю выполнять инициализацию методов объекта в одном месте - в инициализаторе прототипа, который передается в функцию inherit.

// наследование прототипа
// derived = порожденный класс
// base = базовый класс
// init = функция инициализации прототипа
function inherit(derived, base, init) {
  init.prototype = base.prototype;
  derived.prototype = new init();
  derived.prototype.constructor = derived;
}

function A(a) {
  this.a = a;
  A.a_cnt++; // увеличиваем счетчик объектов
}
// порождаем A от пустого объекта {}
inherit(A, {}, function() {
  // инициализация прототипа
  this.methodA = function() { return this.a; };
  A.a_cnt = 0; // инициализация счетчика объектов
});

// аналогично для порождаемых классов
function B(a, b) {
  A.call(this, a); // "класс" знает своего "предка"
  this.b = b;
  B.b_cnt++;
}
inherit(B, A, function() {
  this.methodB = function() { return this.b; };
  B.b_cnt = 0;
});

function C(a, b, c) {
  B.call(this, a, b);
  this.c = c;
  C.c_cnt++;
}
inherit(C, B, function() {
  this.methodC = function() { return this.c; };
  C.c_cnt = 0;
});

// проверка

var a = new A(1);
a = new A(1);
var b = new B(1,2);
b = new B(1,2);
var c = new C(1,2,3);
c = new C(1,2,3);

document.write('\na.a: ' + a.a
 + '\nc.a: ' + c.a
 + '\nmethodA' + c.methodA()
 + '\nc.b: ' + c.b
 + '\nmethodB' + c.methodB()
 + '\nc.c: ' + c.c
 + '\nmethodC' + c.methodC()
 + '\nA.a_cnt: ' + A.a_cnt
 + '\nB.b_cnt: ' + B.b_cnt
 + '\nC.c_cnt: ' + C.c_cnt
 + '\nb is A: ' + (b instanceof A)
 + '\nc is B: ' + (c instanceof A)
);

Вывод:

c.a: 1
methodA1
c.b: 2
methodB2
c.c: 3
methodC3
A.a_cnt: 6
B.b_cnt: 4
C.c_cnt: 2
b is A: true
c is B: true


Автор: Гость (не зарегистрирован), дата: 12 мая, 2012 - 16:50
#permalink

По поводу примечания, блок
Свойства-объекты или "иногда прототип это зло"

function Human(){};
Human.prototype = { 
  name:'',
  setName:function(name){
    this.name = name
  }
}

var human1 = new Human();
var human2 = new Human();

human1.setName('Vasya');
human2.setName('Petya');

console.log(human1.name); // Vasya
console.log(human2.name); // Petya

Что я делаю не так?)


Автор: Гость (не зарегистрирован), дата: 29 июня, 2012 - 01:17
#permalink

переиначиваете пример из статьи, заменяя объектный тип (массив) базовым (строка)


Автор: Гость (не зарегистрирован), дата: 21 июня, 2012 - 01:22
#permalink

самое сложное - понять где ты это будешь использовать:D
потом все становится просто


Автор: Дима-гость (не зарегистрирован), дата: 24 июня, 2012 - 15:42
#permalink

не ебите себе мозг с цепочками прототипов! используйте композицию в связке с call функцией.


Автор: Gugin, дата: 28 июня, 2012 - 16:16
#permalink

Пробую реализовать написанную функцию extend но безрезультатно.

function extend(Child, Parent) {
    var F = function() { }
    F.prototype = Parent.prototype
    Child.prototype = new F()
    Child.prototype.constructor = Child
    Child.superclass = Parent.prototype
}
function Animal(name, speed) {this.name = name, this.can_walk = (speed>0) }
function Bird(name, speed) {this.name = name, this.can_fly = (speed>30) }
extend(Bird, Animal)
orel = new Bird("Gosha", 40)
orel.can_fly // => true 
orel.can_walk   // => undefined

Подскажите, что я не так делаю..


Автор: Actine, дата: 1 июля, 2012 - 18:06
#permalink

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


Автор: gyrdym, дата: 31 июля, 2012 - 12:31
#permalink

Согласен абсолютно, по-моему вся фишка наследования в Javascript заключается в прототипировании


Автор: Kines (не зарегистрирован), дата: 8 августа, 2012 - 12:37
#permalink

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

Например у нас есть 3 объекта расширяющие друг друга.
SphinxCat -> Cat -> Animal,
пользуясь вышеописанным extend получаем
SphinxCat.superclass = Cat
Cat.superclass = Animal

Допустим по интерфейсу у нас есть у каждого класса метод configure который принимает options, каждый объект в что-либо дописывет в эти options, а последний Animal делает что типа this.options = options.

получается такой код
...SphinxCat...

configure: function(options) {
  options.sphinxCat = 1;
  // тут пользуемся вызовом парента
  this.superclass.configure.call(this, options);
}

...Cat...

configure: function(options) {
  options.cat = 1;
  // тут пользуемся вызовом парента
  this.superclass.configure.call(this, options);
}

...Animal...

configure: function(options) {
  options.animal = 1;
  // он знает что поледний и делает такое
  this.options = options;
}

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

var myCat = new SphinxCat();
myCat.configure({name: 'My Super Puper Cat'});

по логике у myCat должен теперь быть

this.options = {
  name: 'My Super Puper Cat',
  SphinxCat: 1,
  Cat: 1,
  Animal: 1
}

Но на самом деле получим вечный цикл )

Метод вызова парентов через this.superclass.blablabla.apply(this, ...) в чистом виде не будет работать с таким юзкейсом (а согласитесь это реальная задача).

Проблема кроется в скоупах.
Рассмотри подробнее:

Шаг 1
SphinxCat дописал в options SphinxCat = 1 и вызвал configure у this.superclass, но в своем скоупе.

Шаг 2
Cat дописывает в options Cat = 1 и вызывает configure у this.superclass в скоупе SphinxCat (потому что this = SphinxCat)

Шаг 3
Тут вы подумаете что Animal что то там дописывает в options, но нет
Опять Cat начинает работать.
Все потому что на шаге 2 у метода Cat.configure this ссылается на SphinxCat а следовательно this.superclass на Сat.

Интересно было как то решить эту проблему.


Автор: Шальков Петр (не зарегистрирован), дата: 18 июня, 2014 - 10:15
#permalink

В общем-то все верно, работать и не должно. Контекст вызова будет принадлежать объекту, созданному через new или {}, в данном случае new myCat. Достаточно сделать правильный вызов родительских методов через superclass (кто от кого наследуется известно же) и все заработает:

function extend(Child, Parent) {
var F = function() { };
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.superclass = Parent.prototype;
}

Animal = function() {
}
Animal.prototype = {
configure: function(options) {
options.animal = 1;
// он знает что поледний и делает такое
this.options = options;
}
}

Cat = function() {
}
extend(Cat, Animal);

Cat.prototype = {
configure: function(options) {
options.cat = 1;
// тут пользуемся вызовом парента
Cat.superclass.configure.call(this, options);
}
}

SphinxCat = function() {
}
extend(SphinxCat, Cat);

SphinxCat.prototype = {
configure: function(options) {
options.sphinxCat = 1;
// тут пользуемся вызовом парента
SphinxCat.superclass.configure.call(this, options);
}
}

var myCat = new SphinxCat();
myCat.configure({name: 'My Super Puper Cat'});
var opt = myCat.options;
alert(opt.name +" " + opt.animal + opt.cat + opt.sphinxCat);


Автор: binzz (не зарегистрирован), дата: 2 ноября, 2012 - 15:25
#permalink

Если функцию объекта зацепить за событие, то this в функции объекта слетает...

Например:

function sThiz(odiv) {

var thiz = this;

this.a = 1;
this.b = 2;

this.event = function(data) {
alert("event - "+this.a+",  "+thiz.b);
this.a = 11;
thiz.b = 12;
}

}

obj = new sThiz();
obj.event("ОК"); // здесь ок, this = thiz
$.get("url", {}, obj.event); // досвиданье this в obj.event() , остается thiz

Автор: binzz (не зарегистрирован), дата: 2 ноября, 2012 - 15:38
#permalink

Или:

f = obj.event;

obj.event("ОК");
f("NO, this = window");

Автор: sergey86 (не зарегистрирован), дата: 21 ноября, 2012 - 16:32
#permalink

Этот код работает

function Animal() {
	alert(1);
}
Animal.prototype.show = function() {}
function Rabbit() {
	Rabbit.superclass.constructor.call(this);
	alert(2);
}
extend(Rabbit, Animal)
new Rabbit();

а этот нет

function Animal() {
	alert(1);
}
Animal.prototype = {
	show: function() {}
}
function Rabbit() {
	Rabbit.superclass.constructor.call(this);
	alert(2);
}
extend(Rabbit, Animal)
new Rabbit();

можно узнать почему?


Автор: Гость (не зарегистрирован), дата: 18 января, 2013 - 14:43
#permalink

Слетает конструктор у класса Animal.
Если его прописать явно, то заработает:
Animal.prototype.constructor = Animal;


Автор: Робин (не зарегистрирован), дата: 6 февраля, 2013 - 16:12
#permalink

Всё это конечно замечательно, но не раскрыта тема как прототипное объявление методов соединить с приватными методами класса. Проще говоря -- как вызывать приватные методы класса (в частности объявленные через замыкание) в методах объявленных через прототип того же класса.


Автор: Гость (не зарегистрирован), дата: 9 февраля, 2013 - 17:18
#permalink

Статья интересная. Хотя мне и не все понятно... Недавно стал интересоваться ООП как таковым. Не легче ли создавать объекты таким образом?

function Animal() {
	this.animalMethod = function(){};
}
function Rabbit() {
	$.extend(this, new a());
	this.rabbitMethod = function(){};
}

Автор: nvyush (не зарегистрирован), дата: 14 марта, 2013 - 14:42
#permalink

Статья хорошая и в меру полная, но возникла пара вопросов:
1) в почему бы в коде к разделу "Почему не this.constructor?" строки

this.constructor.superclass.identify.apply(this, arguments);

не заменить на

this.constructor.superclass.identify.apply(this.constructor.superclass, arguments);

,
это должно убрать зацикленность;
2) как правильно instanceof или instanceOf? В тексте встречается и так и так.


Автор: nvyush (не зарегистрирован), дата: 14 марта, 2013 - 15:34
#permalink

Проверил, работает и так, как я написал выше, и так:

this.constructor.superclass.identify(arguments);

Непонятно, зачем было передавать в метод предка контекст потомка?


Автор: Алексей Голубцов (не зарегистрирован), дата: 7 апреля, 2013 - 21:19
#permalink

Спасибо за статью!

Одна из лучших статей в нете, но везде сильно "пахнет" классами.
При понимании данной темы нужно очистить голову от классов.

Сейчас как раз плотненько занялся наследованием прототипов, для себя немного модифицировал функцию extend:

function extend(Child, Parent) {

	var F = function() { }
	F.prototype = Parent.prototype;
	Child.prototype = new F();
	Child.prototype.constructor = Child;

	// немного модифицированно, вместо superclass __proto__,
	if( !Child.prototype.__proto__ ) { Child.prototype.__proto__ = Parent.prototype; }

	// возврат потомка с уже установленным прототипом
	return Child.prototype;

}

Таким образом, объект всегда имеет свойство __proto__, все объекты наследуются от одного базового объекта, в свою очередь в базовый объект-прототип добавляю следующие методы и свойства:

// кусок кода
// базовый прототип называется Adapter
var Adapter=function(){};
Adapter.prototype={
	
	type:'Adapter', // тип прототипа
	
	extend:function( obj ){  // расширение прототипа
	// расширяется именно прототип, а не сам объект, т.к. вызывается в контексте прототипа
	// но можно вызвать и в контексте обычного объекта
		for( var prop in obj ) {
			if( obj.hasOwnProperty(prop) ){
				this[prop]=obj[prop];
			}
		}
		return this;
	},

	getProto:function( type ){// поиск прототипа по свойству type
		
		var p=this,
			proto=null;
		
		while( true ){	
			if( p.type && p.type==type ){
				proto=p;
				break;
			}
			if( p.__proto__ ) {
				p=p.__proto__;
			}else{
				break;
			}
		}
		
		if( !proto ){
			throw new Error('Не найден прототип '+type);
		}
		return proto;
	},


	......... и так далее, как обычный объект

}

А теперь самое интересное: НАСЛЕДОВАНИЕ:

// кусок реального кода
// этот объект наследует Adapter
var BaseEditor=function(name){

	this.name=name
}

// моя модификация возвращает BaseEditor.prototype, это "нулевой" уровень объекта new F(), 
// прототипом которого является Adapter.prototype
// к BaseEditor.prototype сразу можно "приростить" объект
extend(BaseEditor, Adapter).extend({

	type:'BaseEditor',
	
	....
})

// например
var b=new BaseEditor('hello');
/*
Теперь b состоит из трех уровней (+1 не наш):
0: это объект, в нем определено this.name='hello'
1: это прототип, объект BaseEditor.prototype, в нем определен type:'BaseEditor', этот уровень
	РАВЕН new F(), в функции extend он будет равен {}, т.к. конструктор пустой
2: это прототип прототипа, Adapter.prototype
3: самый последний уровень Object.prototype, он нами не устанавливается
*/

Ну конечно, зря я использую наименование extend два раза, нормальное наименование еще не придумал, может кто подскажет?

Работает в IE8, FF и Opera последних версий.

При изучении данной темы понял очень важную вещь, которую трудно понять при переходе с классов:
во-первых, прототип - это просто объект,
во-вторых, если прототипы рассматривать как уровни, то при создании объекта конструктором, объект состоит из двух уровней (тех которые от нас) это сам объект (его свойства устанавливаются внутри конструктора по ссылке this), как нулевой уровень и его прототип как первый уровень.
При изменении объектом своих свойств ( типа this.prop='new prop' ) прототип не изменяется, свойство записывается в нулевой уровень, т.е. prop можно установить в прототипе как свойство по умолчанию, а для того, чтобы изменить прототип я и создал метод getProto. Метод getProto позволяет не только изменить свойство прототипа, но и вызвать перекрытые методы прототипа в контексте нулевого уровня:

// у this есть свой method, а нам нужен метод прототипа
this.getProto('Adapter').method.call(this,arg1..)

Страдаю дефицитом времени, если кому интересно, по-подробнее распишу.

Контрольный вопрос: кто скажет, что будет, если вызвать function extend( Child, new Parent() ) ?


Автор: Алексей Голубцов (не зарегистрирован), дата: 8 апреля, 2013 - 20:43
#permalink

Нашел ошибки.
В IE не работает наследование больше двух прототипов, правильная функция:

function extend(Child, Parent) {
	var F = function() { }
	F.prototype = Parent.prototype;
	Child.prototype = new F();
	Child.prototype.constructor = Child;

	if( !Child.prototype.hasOwnProperty('__proto__') ) {
 		Child.prototype.__proto__ = Parent.prototype; 
	}
	return Child.prototype;
}

Необходимо проверять Child.prototype.hasOwnProperty('__proto__'), т.к. в IE __proto__ - это просто свойство, которое наследуется от прототипов.
Также, в методе getProto, нужно правильно проверять this.type:

getProto:function( type ){
	
		var p=this,
			proto=null;
		
		while( true ){
			if( p.hasOwnProperty('type') && p.type==type ){
				proto=p;
				break;
			}
			if( p.__proto__ ) {
				p=p.__proto__;
			}else{
				break;
			}
		}
		
		if( !proto ){
			throw new Error('Не найден прототип '+type);
		}
		return proto;
		
	},

Автор: Гость (не зарегистрирован), дата: 17 июля, 2013 - 12:43
#permalink

function Animal(){
this.property_1 = "A1";
this.property_2 = "A2";
}

function Fish(){
this.property_1 = "F1";
this.property_2 = "F2";
}

function Humster(){
this.property_3 = "H3"
}

var animal = new Animal;
Humster.prototype = animal;

var h1 = new Humster;
var h2 = new Humster;

h2.prototype = new Fish;

alert(h1.property_1 == h2.property_1);function Animal(){
this.property_1 = "A1";
this.property_2 = "A2";
}

function Fish(){
this.property_1 = "F1";
this.property_2 = "F2";
}

function Humster(){
this.property_3 = "H3"
}

var animal = new Animal;
Humster.prototype = animal;

var h1 = new Humster;
var h2 = new Humster;

h2.prototype = new Fish;

alert(h1.property_1 == h2.property_1);

//какой будет результат?


Автор: Blackening (не зарегистрирован), дата: 24 ноября, 2013 - 14:51
#permalink

Крайне благодарен автору за статью и за весь цикл учебников javascript.ru! Вырос на этих учебниках - автору реально надо ставить памятник Спасибо - ты помогаешь прогрессировать!


Автор: iyntx, дата: 28 ноября, 2013 - 19:02
#permalink

эээ!!!
При вызове animal.move, интерпретатор находит нужный метод в прототипе animal: Animal.prototype.move и выполняет его, устанавливая this в "текущий" объект.

не this устанавливается в текущий объект , а свойство distance устанавливается для текущего объекта (а не для прототипа).
А this будет являться то что перед точкой то есть если у нас
animal.begemot.motor.move() то this в этом методе будет ссылаться на объект animal.begemot.motor

поправьте текст, или исправьте меня
ну или разжуйте поподробней сей факт. а то новичкам приходится по 16 раз перечитывать. из разных источников. Потому что: 1) терминологией не владеют, 2) авторы иногда кое что опускают как само собой разумеющееся, и интуитивно понятное


Автор: Nekomata (не зарегистрирован), дата: 26 декабря, 2013 - 13:01
#permalink

Запишем свойство canWalk напрямую в объект Rabbit:

animal.canWalk = false
small.canWalk = true
alert(big.canWalk) // false
alert(small.canWalk) // true
У разных кроликов получилось разное значение canWalk, независимое от родителя.
Скажите почему в консоль выводится следующее? 2 свойства canWalk ???

Rabbit {name: "Chuk", name: "скотинка", canWalk: false} Rabbit {name: "Gek", canWalk: true, name: "скотинка", canWalk: false}

Автор: Гость (не зарегистрирован), дата: 16 января, 2014 - 14:34
#permalink

свойства name тоже по два. Видимо, это просто специфично для хромной консоли выводить помимо свойств объекта еще и свойства прототипа. В фаэрфоксе, например, отображается по одному свойству.


Автор: Максим97345 (не зарегистрирован), дата: 9 января, 2014 - 19:11
#permalink

animal.canWalk = false
small.canWalk = true
alert(big.canWalk) // false
alert(small.canWalk) // true
У разных кроликов получилось разное значение canWalk, независимое от родителя.

возможно опечатка в первой строчке. Нужно big.canWalk, иначе фраза "независимое от родителя" относится не к двум объектам а только ко второму, первый продолжает брать это свойство из прототипа родителя


Автор: nucer, дата: 15 января, 2014 - 15:38
#permalink

Люди, кто может нормально объяснить в чём разница между:

var F = function() { };
F.prototype = Parent.prototype;
Сhild.prototype = new F();

или же если сразу сделать

Child.prototype = new Parent();

На простом примере работает и тот и другой способ... Простой пример:

function Parent( someArg )
{
	this.some = someArg;
}

function Child( someArg, anotherArg )
{
	Child.superclass.constructor.call( this, someArg );
	this.another = anotherArg;
}
extend( Child, Parent );
var chld = new Child( "111", "222");

Далее смотрю объект chld через консоль Хрома. При использовании 2-го способа вижу, что у объекта два свойства с !одинаковым! именем some: у одного нормальное значение, которое ему присвоено через вызов конструктора супер-прототипа, а у второго - undefined!)

Также заметил, что строка:

Child.prototype.constructor = Child

никакой роли не играет - без неё конструкторы обоих прототипов срабатывают также, ка к и с ней. Единственное, что у объекта chld не появляется свойство chld.constructor...

Копаю дальше! Создал класс Top, наследующий от Child!
И вот тут 2-й способ отказался работать нормально, а 1-й - всё чётко наследует
Так почему же так необходимо использовать посредник в виде F?? В чём же разница, кто знает?


Автор: Гость (не зарегистрирован), дата: 18 июня, 2014 - 08:53
#permalink

Некропост, но все же.
Во 2ом случае

Child.prototype = new Parent();

происходит явный вызов конструктора Parent с пустыми аргументами, поэтому typeof Child.prototype.some == "undefined" для всех экземпляров child, тк контекст вызова this = new Parent(). В самом конструкторе Child вызов Child.superclass.constructor.call( this, someArg ) осуществляется с правильным this ребенка и typeof child.some == "string".
При дальнейшем наследовании в качестве прототипа Top.prototype уже выступает new Child() опять без параметров и some с anotherArg в Top.prototype
будут undefined.
Кстати в jsfiddle такой Top работает.
Основные проблемы такого "наследования" - дублирование переменных и new Parent() может возвратить бяку, что чревато потерей Parent.prototype


var Parent = function() {
return {
"some": "Хрен тебе, а не Parent"
}
}


ПС. Подсветка синтаксиса что-то не работает..

Автор: Гость (не зарегистрирован), дата: 3 февраля, 2014 - 06:16
#permalink
function parent(){
    this.options = {
        option1: 'someValue',
        options2: 'secondValue'
    };
}

function child(){
    child.superclass.constructor.apply(this, arguments);
    console.log(this.options); //undefined
}
extend(child, parent);

Не понимаю, почему в дочернем объекте нет полей родительского объекта?
И как следствие - невозможно задать параметры для дочернего класса =(


Автор: Гость (не зарегистрирован), дата: 21 июля, 2014 - 15:04
#permalink

Странно у меня этот пример нормально работает.


Автор: Гость (не зарегистрирован), дата: 3 февраля, 2014 - 06:17
#permalink

И ещё вопрос, есть ли разница как объявлять объект?
var parent = function(){};
или function parent(){};


Автор: Гость (не зарегистрирован), дата: 14 февраля, 2014 - 14:13
#permalink
[php]
[php]
[php]
[php]
[php]
[php]
[php]
[php]
[php]
[php]

[/php][/php][/php][/php][/php][/php][/php][/php][/php][/php]


Автор: Алексей Быков (не зарегистрирован), дата: 21 февраля, 2014 - 19:54
#permalink

Если в функцию extend добавить строку

Parent.prototype.constructor = Parent;

то можно использовать такой синтаксис:

function Base() {}
Base.prototype = {
   method1: function() {},
   method2: function() {}
};
function Child() {}
Child.prototype = {
  method3: function() {},
  method4: function() {}
};
extend(Child, Base);

Присваивание целого объекта в качестве прототипа затирает свойство constructor. Ну а "допиленная" версия extend это исправляет.


Автор: Олег Архангельский (не зарегистрирован), дата: 21 июля, 2014 - 14:35
#permalink

Всем привет, вот хочу поделиться функцией для наследования. Данная наработка совмещает defineClass Дэвида Фленгана, Klass Стояна Стефанова, комментариев Николаса Закаса и данной статьи. Пока не нашел способ как делать красиво привантые функции

/**
* Purpose   :       Вспомогательная функция для определения JavaScript классов.
*
* Comments  :       Эта функция ожидает получить объект в виде единственного 
*                   аргумента. Она определяет новый JavaScript класс, 
*                   основываясь на данных в этом объекте, и возвращает 
*                   функциюконструктор нового класса. Эта функция решает 
*                   задачи, связанные с определением классов: корректно 
*                   устанавливает наследование в объектепрототипе, копирует 
*                   методы из других классов и пр.
*
* Arguments :
*   * name      -   Имя определяемого класса.
*                       Если определено, это имя сохранится в свойстве 
*                       classname объектапрототипа.
*
*   * extend    -   Конструктор наследуемого класса. 
*                       В случае отсутствия будет использован конструктор 
*                       Object(). Это значение сохранится в свойстве 
*                       superclass объектапрототипа.
*
*   * construct -   Функцияконструктор класса. 
*                       В случае отсутствия будет использована новая
*                       пустая функция. Это значение станет возвращаемым 
*                       значением функции, а также сохранится в свойстве 
*                       constructor объектапрототипа.
*
*   * methods   -   Объект, который определяет методы (и другие свойства,
*                   совместно используемые разными экземплярами) экземпляра
*                       класса. Свойства этого объекта будут скопированы в 
*                       объектпрототип класса. В случае отсутствия будет 
*                       использован пустой объект. Свойства с именами 
*                       "classname", "superclass" и "constructor" 
*                       зарезервированы и не должны использоваться в этом 
*                       объекте.
*
*   * statics   -   Объект, определяющий статические методы (и другие 
*                   статические свойства) класса. 
*                       Свойства этого объекта станут свойствами функции 
*                       конструктора. В случае отсутствия будет использован 
*                       пустой объект.
*
*   * borrows   -   Функцияконструктор или массив функцийконструкторов.
*                       Методы экземпляров каждого из заданных классов будут
*                       скопированы в объектпрототип этого нового класса, 
*                       таким образом новый класс будет заимствовать методы 
*                       каждого из заданных классов. Конструкторы 
*                       обрабатываются в порядке их следования, вследствие 
*                       этого методы классов, стоящих в конце массива, могут 
*                       переопределить методы классов, стоящих выше. Обратите 
*                       внимание: заимствуемые методы сохраняются в 
*                       объектепрототипе до того, как будут скопированы 
*                       свойства и методы вышеуказанных объектов. Поэтому 
*                       методы, определяемые этими объектами, могут 
*                       переопределить заимствуемые. При отсутствии этого 
*                       свойства заимствование методов не производится.
*
*   * provides  -   Функцияконструктор или массив функцийконструкторов.
*                       После того как объектпрототип будет инициализирован, 
*                       данная функция проверит, что прототип включает методы 
*                       с именами и количеством аргументов, совпадающими с 
*                       методами экземпляров указанных классов. Ни один из 
*                       методов не будет скопирован, она просто убедится, что 
*                       данный класс "предоставляет" функциональность, 
*                       обеспечиваемую указанным классом. Если проверка 
*                       окажется неудачной, данный метод сгенерирует исключение. 
*                       В противном случае любой экземпляр нового класса может 
*                       рассматриваться (с использованием методики грубого 
*                       определения типа) как экземпляр указанных типов. Если 
*                       данное свойство не определено, проверка выполняться 
*                       не будет.
*   
* Returns   :       function
*
* Throws    :       Error если создаваемый класс не поддерживает необходимых 
*                   методов из provides
**/
defineClass = function () {
    //промежуточный объект для хранения методов которыми будут пользоваться 
    //все экземпляры классов и для установки прототипа
    var inheritance = function inheritance() { };

    return function defineClass(data) {

        // Извлечь значения полей из объектааргумента.
        // Установить значения по умолчанию.
        var classname = data.name,
            superclass = data.extend || Object,
            constructor = data.construct || function () { },
            methods = data.methods || {},
            statics = data.statics || {},
            borrows,
            provides;

        // Заимствование может производиться как из единственного конструктора,
        // так и из массива конструкторов.
        if (!data.borrows) {
            borrows = [];
        }
        else {
            if (data.borrows instanceof Array) {
                borrows = data.borrows;
            }
            else {
                borrows = [data.borrows];
            };
        };

        // То же для предоставляемых свойств.
        if (!data.provides) {
            provides = [];
        }
        else {
            if (data.provides instanceof Array) {
                provides = data.provides;
            }
            else {
                provides = [data.provides];
            };
        };

        //устанавливаем связь с родительским прототипом
        inheritance.prototype = superclass.prototype;

        // Создать объект, который станет прототипом класса.
        var proto = new inheritance();

        // Заимствовать методы из классовсмесей, скопировав их в прототип
        for (var i = 0; i < borrows.length; i++) {
            var c = borrows[i];

            // Скопировать методы из прототипа объекта c в наш прототип
            for (var p in c.prototype) {

                if (typeof c.prototype[p] != "function") continue;
                proto[p] = c.prototype[p];
            }
        }

        // Скопировать методы экземпляра в объектпрототип
        // Эта операция может переопределить методы, скопированные из классовсмесей
        for (var p in methods) {
            proto[p] = methods[p];
        };

        // Установить значения зарезервированных свойств "constructor",
        // "superclass" и "classname" в прототипе
        proto.constructor = constructor;
        constructor.superclass = superclass.prototype;

        // Свойство classname установить, только если оно действительно задано.
        if (classname) {
            proto.classname = classname;
        };

        // Убедиться, что прототип предоставляет все предполагаемые методы.
        // для каждого класса
        for (var i = 0; i < provides.length; i++) {

            var c = provides[i];

            // для каждого свойства        
            for (var p in c.prototype) {

                // только методы
                if (typeof c.prototype[p] != "function") {
                    continue;
                };

                if (p == "constructor" || p == "superclass") {
                    continue;
                };

                // Проверить наличие метода с тем же именем и тем же количеством
                // объявленных аргументов. Если метод имеется, продолжить цикл
                if (p in proto && typeof proto[p] == "function" && proto[p].length == c.prototype[p].length) {
                    continue;
                };

                // В противном случае возбудить исключение
                throw new Error("Class " + classname + " are not provided method " + c.classname + "." + p);
            };
        };

        // Связать объектпрототип с функциейконструктором
        constructor.prototype = proto;

        // Скопировать статические свойства в конструктор
        for (var p in statics) {
            constructor[p] = statics[p];
        };

        // И в заключение вернуть функцию-конструктор
        return constructor;
    }
}();



///////////////////////// ПРИМЕР ////////////////////////////////////////

var tempObj = function () { };
// пример переменной
tempObj.prototype.distance = 0;
// пример метода
tempObj.prototype.walk = function (time) {
    this.distance = this.distance + time * this.walkSpeed
};
tempObj.prototype.toString = function () {
    return this.name + " на расстоянии " + this.distance
};

var Animal = defineClass({
    name: "Animal",
    construct: function (name, walkSpeed) {
        this.name = name;
        this.walkSpeed = walkSpeed;
    },
    borrows: tempObj,
    methods: {
        distance: tempObj.prototype.distance
    }
});

var tempObj2 = defineClass({
    methods: {
        fly: function (time) {
            this.distance = this.distance + time * this.flySpeed
        }
    }
});

var Bird = defineClass({
    name: "Bird",
    construct: function (name, walkSpeed, flySpeed) {
        // вызов родительского конструктора
        Bird.superclass.constructor.call(this, name, walkSpeed)
        this.flySpeed = flySpeed
    },
    extend: Animal,
    borrows: tempObj2
});

var Cuckoo = defineClass({
    name: "Cuckoo",
    extend: Bird,
    construct: function (name, walkSpeed, flySpeed) {
        // вызов родительского конструктора
        Cuckoo.superclass.constructor.call(this, name, walkSpeed, flySpeed)
        this.word = "cucoo";
    },
    methods: {
        say: function () {
            return this.name + " says " + this.word;
        }
    }
});

var animal = new Animal("Dog", 2);
animal.walk(3);
var dd = animal.toString();     // => Dog на расстоянии 6

bird = new Bird("Птыц", 1, 10);
bird.walk(3);
var ww = bird.toString();               // => Птыц на расстоянии 3
bird.fly(2);
var ff = bird.toString();               // => Птыц на расстоянии 23

cuckoo = new Cuckoo("Кукушка", 1, 10);
cuckoo.walk(4);
var ww = cuckoo.toString();             // => Кукушка на расстоянии 4
cuckoo.fly(3);
var ff = cuckoo.toString();             // => Кукушка на расстоянии 34
var cSay = cuckoo.say();                // => Кукушка says cucoo

Автор: Олег Архангельский (не зарегистрирован), дата: 22 июля, 2014 - 12:09
#permalink

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

var Cuckoo = (function () {

            var hjy = "HI";
            return defineClass({
                name: "Cuckoo",
                extend: Bird,
                construct: function (name, walkSpeed, flySpeed) {
                    // вызов родительского конструктора
                    Cuckoo.superclass.constructor.call(this, name, walkSpeed, flySpeed)
                    this.word = hjy;
                },
                methods: {
                    say: function () {
                        return this.name + " says " + hjy;
                    }
                }
            })
        }());

Автор: Олег Архангельский (не зарегистрирован), дата: 22 июля, 2014 - 12:55
#permalink

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


Отправить комментарий

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
1 + 0 =
Введите результат. Например, для 1+3, введите 4.
 
Текущий раздел
Поиск по сайту
Реклама
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние комментарии
Последние темы на форуме
Forum