Конспект JS-course

Прототип: наследование и методы

Источник: http://learn.javascript.ru/prototype

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

Наследование через ссылку __proto__

Наследование в JavaScript реализуется при помощи специального свойства __proto__.

Если один объект, например, rabbit, имеет специальную ссылку __proto__ на другой объект animal , то все свойства, которые ищутся в rabbit, будут затем искаться в animal.

var animal = { eats: true };
var rabbit = { jumps: true };

rabbit.__proto__ = animal;  // унаследовать

alert(rabbit.eats); // true
alert(rabbit.jumps); // true

Когда запрашивается свойство rabbit, интерпретатор ищет его сначала в самом объекте rabbit, а если не находит — в объекте rabbit.__proto__, то есть, в данном случае, в animal.

Объект, на который указывает __proto__ , называется его «прототипом».

Нет никаких ограничений на объект, который записывается в прототип. Например:

var rabbit = { };

rabbit.__proto__ = window;

rabbit.open('http://google.com'); // вызовет метод open из window

Если вы будете читать спецификацию EcmaScript — свойство __proto__ обозначено в ней как [[Prototype]] . Не путать со свойством prototype. Прототип используется только если свойство не найдено.

var animal = { eats: true };
var fedUpRabbit = { eats: false };

fedUpRabbit.__proto__ = animal;

alert(fedUpRabbit.eats); // false, свойство взято из fedUpRabbit

Другими словами, прототип — это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.

Цепочка прототипов

У объекта, который является __proto__, может быть свой __proto__, у того — свой, и так далее.

Например, цепочка наследования из трех объектов donkey -> winnie -> owl:

var owl = {
  sum: function(a, b) {
    return a + b;
  }
}

var winnie = { /* ... */ };
winnie.__proto__ = owl;

var donkey = { /* ... */ }
donkey.__proto__ = winnie;

alert( donkey.sum(2,2) );  // "4" ответит owl

Картина происходящего:

Создание объекта с данным прототипом

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

Object.create(proto) (кроме IE8-)

Вызов Object.create(proto) создаёт пустой объект с данным прототипом proto . Например:

var animal = { eats: true };

var rabbit = Object.create(animal);

alert(rabbit.eats); // true

Этот код создал пустой объект rabbit с прототипом animal:

Мы можем добавить свойства в новый объект rabbit:

var animal = { eats: true };

var rabbit = Object.create(animal);

rabbit.jumps = true;

Станет:

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

Свойство F.prototype

Созданием объектов часто занимается функция-конструктор. Чтобы таким объектам автоматически ставить прототип, существует свойство prototype.

Свойство prototype можно указать на любом объекте, но особый смысл оно имеет, если назначено функции.

При создании объекта через new, в его прототип __proto__ записывается ссылка из prototype функции-конструктора.

Например:

var animal = { eats: true }

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

Rabbit.prototype = animal;

var rabbit = new Rabbit('John');

alert( rabbit.eats ); // true, т.к. rabbit.__proto__ == animal

Установка Rabbit.prototype = animal говорит интерпретатору следующее: «запиши __proto__ = animal при создании объекта через new Rabbit».

Значением prototype может быть только объект.

Эмуляция Object.create для IE8-

Вызов Object.create(proto), который создаёт пустой объект с данным прототипом, можно эмулировать, так что он будет работать во всех браузерах, включая IE6+.

Кросс-браузерный аналог — inherit состоит буквально из нескольких строк:

function inherit(proto) {
  function F() {}
  F.prototype = proto;
  var object = new F;
  return object;
}
  1. Создана новая функция F. Она ничего не делает с this, так что вызов new F вернёт пустой объект.
  2. Свойство F.prototype устанавливается в будущий прототип proto;
  3. Результатом вызова new F будет пустой объект с __proto__ равным значению F.prototype.
  4. Готово! Мы получили пустой объект с заданным прототипом.

Результат вызова inherit(animal) идентичен Object.create(animal). Это будет новый пустой объект с прототипом animal.

Например:

var animal = { eats: true };

var rabbit = inherit(animal);

alert(rabbit.eats); // true

Метод Object.getPrototypeOf(obj)

Вызов Object.getPrototypeOf(obj) возвращает прототип obj.

Не поддерживается в IE8-.

var animal = {
  eats: true
};

rabbit = Object.create(animal);

alert( Object.getPrototypeOf(rabbit) === animal ); // true

Этот метод даёт возможность получить __proto__. но не изменить его.

Метод obj.hasOwnProperty

Метод obj.hasOwnProperty(prop) есть у всех объектов. Он позволяет проверить, принадлежит ли свойство самому объекту, без учета его прототипа.

Например:

var animal = {
  eats: true
};

rabbit = Object.create(animal);
rabbit.jumps = true;

alert( rabbit.hasOwnProperty('jumps') ); // true: jumps принадлежит rabbit
alert( rabbit.hasOwnProperty('eats') ); // false: eats не принадлежит

Перебор свойств объекта без прототипа

Цикл for..in перебирает все свойства в объекте и его прототипе.

Например:

var animal = {
  eats: true
};

rabbit = Object.create(animal);
rabbit.jumps = true;

for (var key in rabbit) {
  alert (key + " = " + rabbit[key]); // выводит и "eats" и "jumps"
}

Иногда хочется посмотреть, что находится именно в самом объекте, а не в прототипе.

Это можно сделать, если профильтровать key через hasOwnProperty:

var animal = {
  eats: true
};

rabbit = Object.create(animal);
rabbit.jumps = true;

for (var key in rabbit) {
  if ( !rabbit.hasOwnProperty(key) ) continue; // пропустить "не свои" свойства
  alert (key + " = " + rabbit[key]); // выводит только "jumps"
}

Итого

Мы рассмотрели основы наследования в JavaScript. Упорядочим эту информацию.

  • Наследование реализуется через специальное свойство __proto__ (в спецификации обозначено [[Prototype]]). Оно работает так: при попытке получить свойство из объекта, если его там нет, оно ищется в __proto__ объекта.
  • Замыкания и this с прототипами никак не связаны, они работают по своим правилам.

Установка прототипа __proto__:

  • Firefox, Chrome и Safari дают полный доступ к obj.__proto__. Эта нестандартная возможность бывает полезна в целях отладки.
  • Функция-конструктор при создании объекта устанавливает его __proto__ равным своему prototype.
  • Вызов Object.create(proto) создаёт пустой объект с прототипом proto.

В IE8- этого метода нет, но его можно эмулировать при помощи следующей функции inherit:

function inherit(proto) {
  function F() {}
  F.prototype = proto;
  return new F;
}

Можно даже присвоить Object.create = inherit, чтобы иметь унифицированный вызов, но при этом стоит иметь в виду, что современные браузеры поддерживают также дополнительный второй аргумент Object.create, позволяющий задать свойства объекта, а inherit — нет.

Кроме того, есть следующие методы для работы с прототипом:

  • Метод Object.getPrototypeOf(obj) — получить прототип объекта obj, кроме IE8-
  • Метод obj.hasOwnProperty(prop) — возвращает true, если prop является свойством объекта obj, без учёта прототипа.

Проверка obj.hasOwnProperty используется, в частности, в for..in для перебора свойств именно самого объекта, без прототипа.