Прототип объекта
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/prototype-inheritance.
Объекты в JavaScript можно организовать в цепочки так, чтобы свойство, не найденное в одном объекте, автоматически искалось бы в другом.
Связующим звеном выступает специальное свойство __proto__ .
Прототип proto
Если один объект имеет специальную ссылку __proto__ на другой объект, то при чтении свойства из него, если свойство отсутствует в самом объекте, оно ищется в объекте __proto__ .
Свойство __proto__ доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.
Пример кода (кроме IE10-):
var animal = < eats: true >; var rabbit = < jumps: true >; rabbit.__proto__ = animal; // в rabbit можно найти оба свойства alert( rabbit.jumps ); // true alert( rabbit.eats ); // true
- Первый alert здесь работает очевидным образом – он выводит свойство jumps объекта rabbit .
- Второй alert хочет вывести rabbit.eats , ищет его в самом объекте rabbit , не находит – и продолжает поиск в объекте rabbit.__proto__ , то есть, в данном случае, в animal .
Иллюстрация происходящего при чтении rabbit.eats (поиск идёт снизу вверх):
Объект, на который указывает ссылка __proto__ , называется «прототипом». В данном случае получилось, что animal является прототипом для rabbit .
Также говорят, что объект rabbit «прототипно наследует» от animal .
Обратим внимание – прототип используется исключительно при чтении. Запись значения, например, rabbit.eats = value или удаление delete rabbit.eats – работает напрямую с объектом.
В примере ниже мы записываем свойство в сам rabbit , после чего alert перестаёт брать его у прототипа, а берёт уже из самого объекта:
var animal = < eats: true >; var rabbit = < jumps: true, eats: false >; rabbit.__proto__ = animal; alert( rabbit.eats ); // false, свойство взято из rabbit
Другими словами, прототип – это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.
У объекта, который является __proto__ , может быть свой __proto__ , у того – свой, и так далее. При этом свойства будут искаться по цепочке.
Ссылка proto в спецификации
Если вы будете читать спецификацию ECMAScript – свойство __proto__ обозначено в ней как [[Prototype]] .
Двойные квадратные скобки здесь важны, чтобы не перепутать его с совсем другим свойством, которое называется prototype , и которое мы рассмотрим позже.
Метод hasOwnProperty
Обычный цикл for..in не делает различия между свойствами объекта и его прототипа.
Он перебирает всё, например:
var animal = < eats: true >; var rabbit = < jumps: true, __proto__: animal >; for (var key in rabbit) < alert( key + " = " + rabbit[key] ); // выводит и "eats" и "jumps" >
Иногда хочется посмотреть, что находится именно в самом объекте, а не в прототипе.
Вызов obj.hasOwnProperty(prop) возвращает true , если свойство prop принадлежит самому объекту obj , иначе false .
var animal = < eats: true >; var rabbit = < jumps: true, __proto__: animal >; alert( rabbit.hasOwnProperty('jumps') ); // true: jumps принадлежит rabbit alert( rabbit.hasOwnProperty('eats') ); // false: eats не принадлежит
Для того, чтобы перебрать свойства самого объекта, достаточно профильтровать key через hasOwnProperty :
var animal = < eats: true >; var rabbit = < jumps: true, __proto__: animal >; for (var key in rabbit) < if (!rabbit.hasOwnProperty(key)) continue; // пропустить "не свои" свойства alert( key + " = " + rabbit[key] ); // выводит только "jumps" >
Object.create(null)
Зачастую объекты используют для хранения произвольных значений по ключу, как коллекцию:
var data = <>; data.text = "Привет"; data.age = 35; // .
При дальнейшем поиске в этой коллекции мы найдём не только text и age , но и встроенные функции:
var data = <>; alert(data.toString); // функция, хотя мы её туда не записывали
Это может быть неприятным сюрпризом и приводить к ошибкам, если названия свойств приходят от посетителя и могут быть произвольными.
Чтобы этого избежать, мы можем исключать свойства, не принадлежащие самому объекту:
var data = <>; // выведет toString только если оно записано в сам объект alert(data.hasOwnProperty('toString') ? data.toString : undefined);
Однако, есть путь и проще:
var data = Object.create(null); data.text = "Привет"; alert(data.text); // Привет alert(data.toString); // undefined
Объект, создаваемый при помощи Object.create(null) не имеет прототипа, а значит в нём нет лишних свойств. Для коллекции – как раз то, что надо.
Методы для работы с proto
В современных браузерах есть два дополнительных метода для работы с __proto__ . Зачем они нужны, если есть __proto__ ? В общем-то, не очень нужны, но по историческим причинам тоже существуют.
Чтение: Object.getPrototypeOf(obj) Возвращает obj.__proto__ (кроме IE8-) Запись: Object.setPrototypeOf(obj, proto) Устанавливает obj.__proto__ = proto (кроме IE10-).
Кроме того, есть ещё один вспомогательный метод:
Создание объекта с прототипом: Object.create(proto, descriptors) Создаёт пустой объект с __proto__ , равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать дескрипторы свойств.
Итого
- В JavaScript есть встроенное «наследование» между объектами при помощи специального свойства __proto__ .
- При установке свойства rabbit.__proto__ = animal говорят, что объект animal будет «прототипом» rabbit .
- При чтении свойства из объекта, если его в нём нет, оно ищется в __proto__ . Прототип задействуется только при чтении свойства. Операции присвоения obj.prop = или удаления delete obj.prop совершаются всегда над самим объектом obj .
Несколько прототипов одному объекту присвоить нельзя, но можно организовать объекты в цепочку, когда один объект ссылается на другой при помощи __proto__ , тот ссылается на третий, и так далее.
В современных браузерах есть методы для работы с прототипом:
- Object.getPrototypeOf(obj) (кроме IE8-)
- Object.setPrototypeOf(obj, proto) (кроме IE10-)
- Object.create(proto, descriptors) (кроме IE8-)
Возможно, вас смущает недостаточная поддержка __proto__ в старых IE. Но это не страшно. В последующих главах мы рассмотрим дополнительные методы работы с __proto__ , включая те, которые работают везде.
Также мы рассмотрим, как свойство __proto__ используется внутри самого языка JavaScript и как организовать классы с его помощью.
prototype js что это
Прототип — это объект, который используется для наследования свойств и методов.
- Создадим класс Car , который будет содержать свойства model и year , и метод start() :
function Car(model, year) this.model = model; this.year = year; > Car.prototype.start = function () console.log('Engine started'); >;
Мы создали функцию-конструктор Car , которая принимает модель и год выпуска автомобиля и сохраняет их в свойства объекта. Затем мы добавили метод start() в прототип объекта Car .
- Теперь создадим объект honda , используя оператор new :
var honda = new Car('Civic', 2020);
Мы создали объект honda на основе класса Car с помощью оператора new. honda наследует свойства и методы от прототипа Car .
Мы можем вызвать метод start() на объекте honda :
honda.start(); // выведет "Engine started"
Также мы можем добавить новый метод в прототип Car :
Car.prototype.stop = function () console.log('Engine stopped'); >;
- Теперь мы можем вызвать новый метод stop() на объекте honda :
honda.stop(); // выведет "Engine stopped"
Таким образом, мы использовали прототип для наследования свойств и методов от класса Car для объекта honda .
Прототипы объектов
JavaScript часто описывают как язык прототипного наследования — каждый объект, имеет объект-прототип, который выступает как шаблон, от которого объект наследует методы и свойства. Объект-прототип так же может иметь свой прототип и наследовать его свойства и методы и так далее. Это часто называется цепочкой прототипов и объясняет почему одним объектам доступны свойства и методы которые определены в других объектах.
Точнее, свойства и методы определяются в свойстве prototype функции-конструктора объектов, а не в самих объектах.
В JavaScript создаётся связь между экземпляром объекта и его прототипом (свойство __proto__ , которое является производным от свойства prototype конструктора), а свойства и методы обнаруживаются при переходе по цепочке прототипов.
Примечание: Важно понимать, что существует различие между прототипом объекта (который доступен через Object.getPrototypeOf(obj) или через устаревшее свойство __proto__ ) и свойством prototype в функциях-конструкторах. Первое свойство является свойством каждого экземпляра, а второе — свойством конструктора. То есть Object.getPrototypeOf(new Foobar()) относится к тому же объекту, что и Foobar.prototype .
Давайте посмотрим на пример, чтобы стало понятнее.
Понимание прототипа объектов
Вернёмся к примеру, когда мы закончили писать наш конструктор Person() — загрузите пример в свой браузер. Если у вас ещё нет работы от последней статьи, используйте наш пример oojs-class-further-exercises.html (см. Также исходный код).
В этом примере мы определили конструктору функцию, например:
function Person(first, last, age, gender, interests) // Определения методов и свойств this.name = first: first, last: last, >; this.age = age; this.gender = gender; //. см. Введение в объекты для полного определения >
Затем мы создаём экземпляр объекта следующим образом:
var person1 = new Person("Bob", "Smith", 32, "male", ["music", "skiing"]);
Если вы наберёте « person1. » в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:
В этом списке вы увидите элементы, определённые в конструкторе person 1 — Person() — name , age , gender , interests , bio , и greeting . Однако вы также увидите некоторые другие элементы — watch , valueOf и т. д. — они определены в объекте прототипа Person (), который является Object .
Итак, что произойдёт, если вы вызываете метод в person1 , который фактически определён в Object ? Например:
.valueOf();
Этот метод — Object.valueOf() наследуется person1 , потому что его конструктором является Person() , а прототипом Person() является Object() . valueOf() возвращает значение вызываемого объекта — попробуйте и убедитесь! В этом случае происходит следующее:
- Сначала браузер проверяет, имеет ли объект person1 доступный в нем метод valueOf() , как определено в его конструкторе Person() .
- Это не так, поэтому следующим шагом браузер проверяет, имеет ли прототип объекта ( Object() ) конструктора Person() доступный в нем метод valueOf() . Так оно и есть, поэтому он вызывается, и все хорошо!
Примечание: Мы хотим повторить, что методы и свойства не копируются из одного объекта в другой в цепочке прототипов — к ним обращаются, поднимаясь по цепочке, как описано выше.
Примечание: Официально нет способа получить доступ к объекту прототипа объекта напрямую — «ссылки» между элементами в цепочке определены во внутреннем свойстве, называемом [[prototype]] в спецификации для языка JavaScript ( см. ECMAScript). Однако у большинства современных браузеров есть свойство, доступное для них под названием __proto__ (это 2 подчёркивания с обеих сторон), который содержит объект-прототип объекта-конструктора. Например, попробуйте person1.__proto__ и person1.__proto__.__proto__ , чтобы увидеть, как выглядит цепочка в коде!
С ECMAScript 2015 вы можете косвенно обращаться к объекту прототипа объекта Object.getPrototypeOf (obj) .
Свойство prototype: Где определены унаследованные экземпляры
Итак, где определены наследуемые свойства и методы? Если вы посмотрите на страницу со ссылкой Object , вы увидите в левой части большое количество свойств и методов — это намного больше, чем количество унаследованных членов, доступных для объекта person1 . Некоторые из них унаследованы, а некоторые нет — почему это?
Как упоминалось выше, наследованные свойства это те, что определены в свойстве prototype (вы можете называть это подпространством имён), то есть те, которые начинаются с Object.prototype. , а не те, которые начинаются с простого Object . Значение свойства prototype — это объект, который в основном представляет собой контейнер для хранения свойств и методов, которые мы хотим наследовать объектами, расположенными дальше по цепочке прототипов.
Таким образом Object.prototype.watch() , Object.prototype.valueOf() и т. д. доступны для любых типов объектов, которые наследуются от Object.prototype , включая новые экземпляры объектов, созданные из конструктора Person() .
Object.is() , Object.keys() и другие члены, не определённые в контейнере prototype , не наследуются экземплярами объектов или типами объектов, которые наследуются от Object.prototype . Это методы / свойства, доступные только в конструкторе Object() .
Примечание: Это кажется странным — как у вас есть метод, определённый для конструктора, который сам по себе является функцией? Ну, функция также является типом объекта — см. Ссылку на конструктор Function() , если вы нам не верите.
- Вы можете проверить существующие свойства прототипа для себя — вернитесь к нашему предыдущему примеру и попробуйте ввести следующее в консоль JavaScript:
Person.prototype;
Object.prototype;
Вы увидите большое количество методов, определённых для свойства prototype Object ‘а , которые затем доступны для объектов, которые наследуются от Object , как показано выше.
Вы увидите другие примеры наследования цепочек прототипов по всему JavaScript — попробуйте найти методы и свойства, определённые на прототипе глобальных объектов String , Date , Number и Array , например. Все они имеют несколько элементов, определённых на их прототипе, поэтому, например, когда вы создаёте строку, вот так:
var myString = "This is my string.";
В myString сразу есть множество полезных методов, таких как split() , indexOf() , replace() и т. д.
Предупреждение: Важно: Свойство prototype является одной из наиболее противоречивых названий частей JavaScript — вы можете подумать, что this указывает на объект прототипа текущего объекта, но это не так (это внутренний объект, к которому можно получить доступ __proto__ , помните ?). prototype вместо этого — свойство, содержащее объект, на котором вы определяете членов, которые вы хотите наследовать.
Снова create()
Ранее мы показали, как метод Object.create() может использоваться для создания нового экземпляра объекта.
-
Например, попробуйте это в консоли JavaScript предыдущего примера:
var person2 = Object.create(person1);
.__proto__;
Это вернёт объект person1.
Свойство constructor
Каждая функция-конструктор имеет свойство prototype , значением которого является объект, содержащий свойство constructor . Это свойство constructor указывает на исходную функцию-конструктор. Как вы увидите в следующем разделе, свойства, определённые в свойстве Person.prototype (или в общем случае в качестве свойства прототипа функции конструктора, который является объектом, как указано в предыдущем разделе) становятся доступными для всех объектов экземпляра, созданных с помощью конструктор Person() . Следовательно, свойство конструктора также доступно для объектов person1 и person2 .
-
Например, попробуйте эти команды в консоли:
.constructor; person2.constructor;
var person3 = new person1.constructor("Karen", "Stephenson", 26, "female", [ "playing drums", "mountain climbing", ]);
.name.first; person3.age; person3.bio();
Это хорошо работает. Вам не нужно будет использовать его часто, но это может быть действительно полезно, если вы хотите создать новый экземпляр и не имеете ссылки на исходный конструктор, который легко доступен по какой-либо причине.
Свойство constructor имеет другие применения. Например, если у вас есть экземпляр объекта и вы хотите вернуть имя конструктора этого экземпляра, вы можете использовать следующее:
.constructor.name;
Например, попробуйте это:
.constructor.name;
Примечание: Значение constructor.name может измениться (из-за прототипического наследования, привязки, препроцессоров, транспилеров и т. д.), Поэтому для более сложных примеров вы захотите использовать оператор instanceof .
Изменение прототипов
Давайте рассмотрим пример изменения свойства prototype функции-конструктора — методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора.
-
Вернитесь к нашему примеру oojs-class-further-exercises.html и создайте локальную копию исходного кода. Ниже существующего JavaScript добавьте следующий код, который добавляет новый метод в свойство prototype конструктора:
Person.prototype.farewell = function () alert(this.name.first + " has left the building. Bye for now!"); >;
.farewell();
Должно появиться всплывающее окно, с именем пользователя, определённым в конструкторе. Это действительно полезно, но ещё более полезно то, что вся цепочка наследования обновляется динамически, автоматически делая этот новый метод доступным для всех экземпляров объектов, полученных из конструктора.
Подумайте об этом на мгновение. В нашем коде мы определяем конструктор, затем мы создаём экземпляр объекта из конструктора, затем добавляем новый метод к прототипу конструктора:
function Person(first, last, age, gender, interests) // определения свойств и методов > var person1 = new Person("Tammi", "Smith", 32, "neutral", [ "music", "skiing", "kickboxing", ]); Person.prototype.farewell = function () alert(this.name.first + " has left the building. Bye for now!"); >;
Но метод farewell() по-прежнему доступен в экземпляре объекта person1 — его элементы были автоматически обновлены, чтобы включить недавно определённый метод farewell() .
Примечание: Если у вас возникли проблемы с получением этого примера для работы, посмотрите на наш пример oojs-class-prototype.html (см. также это running live).
Вы редко увидите свойства, определённые в свойстве prototype , потому что они не очень гибки при таком определении. Например, вы можете добавить свойство следующим образом:
Person.prototype.fullName = "Bob Smith";
Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, создав fullName из name.first и name.last :
Person.prototype.fullName = this.name.first + " " + this.name.last;
Однако это не работает, поскольку в этом случае this будет ссылаться на глобальную область, а не на область функции. Вызов этого свойства вернёт undefined undefined . Это отлично работало с методом, который мы определили ранее в прототипе, потому что он находится внутри области функций, которая будет успешно перенесена в область экземпляра объекта. Таким образом, вы можете определить постоянные свойства прототипа (т. е. те, которые никогда не нуждаются в изменении), но обычно лучше определять свойства внутри конструктора.
Фактически, довольно распространённый шаблон для большего количества определений объектов — это определение свойств внутри конструктора и методов в прототипе. Это упрощает чтение кода, поскольку конструктор содержит только определения свойств, а методы разделены на отдельные блоки. Например:
// Определение конструктора и его свойств function Test(a, b, c, d) // определение свойств. > // Определение первого метода Test.prototype.x = function() . >; // Определение второго метода Test.prototype.y = function() . >; //. и так далее
Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.
Резюме
В этой статье рассмотрены прототипы объектов JavaScript (в том числе и то, как прототип цепочки объектов позволяет объектам наследовать функции друг от друга), свойство прототипа и как его можно использовать для добавления методов к конструкторам и другие связанные с этой статьёй темы.
В следующей статье мы рассмотрим то, как вы можете реализовать наследование функциональности между двумя собственными настраиваемыми объектами.
Прототипы в JavaScript
Прототипы в JavaScript — это механизм позволяющий наследовать объектам, свойства других объектов.
Сразу же начнем с примера и создадим два объекта student и programs , где в первом будет лежать информация о студенте, а во втором программы для изучения JavaScript согласно уровня ученика.
let student = < name: 'Дмитрий', age: 28, level: 'junior', >let programs = < junior: 'Основы JavaScript', middle: 'Продвинутый уровень', senior: 'Продвинутая архитектура проекта', >Object.setPrototypeOf(student, programs) console.log(student.junior) // Основы JavaScript
С помощью метода Object.setPrototypeOf() устанавили прототип объекта programs для student . Другими словами сделали доступным объекту student все свойства и методы, которые есть в programs . Проверить это можно обратившись через объект student к свойству junior — результатом будет значение Основы JavaScript.
Задать прототип, можно также с помощью свойства __proto__ , которое до сих пор поддерживается браузерами, но считается устаревшим.
student.__proto__ = programs
[[Prototype]]
Прототип — это объект, который хранится в [[Prototype]] и содержит в себе различные свойства и методы.
let testObject = <> console.log(testObject)
Если создать пустой объект и посмотреть в console, то можно увидеть свойство [[Prototype]] и то, что оно ссылается на объект, который содержит в себе другие свойства. Также если здесь прописать наш объект и поставить после него точку, браузер предложит доступные методы, как раз из прототипа.
Прототипы и функция конструктор
Для понимания работы функции конструктора посмотрим на работу встроенного объекта Date .
let date = new Date() console.dir(date)
Date доступен из коробки и предоставляет методы для работы с датой и временем. Для того, чтобы получить доступ до этих методов, нам нужно всего лишь создать свой экземпляр объекта используя оператор new .
В JavaScript можно самостоятельно писать похожие объекты и использовать их для создания отдельных экземпляров.
Создадим функцию конструктор Student и экземпляр объекта, который запишем в переменную student1 .
function Student(name, age, level) < this.name = name this.age = age this.level = level this.message = `Студент $, в возрасте $ лет пришел на курсы JavaScript c уровнем знания $` > let student1 = new Student('Дмитрий', 28, 'junior') console.log(student1.message) // Студент Дмитрий, в возрасте 28 лет пришел на курсы JavaScript c уровнем знания junior'
Теперь для того, чтобы создавать однотипные объекты в которых будет хранится информация о студентах и будут доступны общие методы, нам не нужно прописывать все с нуля, а достаточно создать экземпляр объекта Student , в прототипе которого и будет записана вся нужная нам информация.
Цепочки прототипов
В JavaScript каждый объект имеет объект-прототип от которого он наследует методы и свойства. В свою очередь этот объект-прототип может содержать в себе другой прототип и наследовать свойства уже от него. В итоге объект-экземпляр получает доступ ко всей цепочке прототипов и их свойствам и методам.
Возьмем для примера все тот же объект Date .
let date = new Date() console.dir(date)
И в console видим, что для date доступны, как методы связанные с датой, так и стандартные, к каким относится toString() . Развернув [[Prototype]] мы сможем увидеть вложенность прототипов.
Свойства в свою очередь ищутся сверху вниз и если интерпретатор находит нужное, тогда поиск останавливается.
let programAcademy = < course: 'JavaScript' >let programAcademy2 = < course: 'React' >Object.setPrototypeOf(programAcademy, programAcademy2) console.log(programAcademy.course) // JavaScript
Свойство course из прототипа programAcademy2 игнорируется.
Итого
1. Под прототипом могут понимать, как метод позволяющий наследовать объектам свойства друг друга, так и непосредственно объект с наследуемыми свойствами.
2. Все объекты в JavaScript имеют свойство [[Prototype]] , которое ссылается на объект, называемым прототипом.
3. Свойства __proto__ считается устаревшим, а для получения и установки прототипов рекомендуется использовать методы getPrototypeOf() и setPrototypeOf() .
4. Методы и свойства сначала считываются из самого объекта, если там ничего не найдено поиски продолжаются в прототипе.
5. Прототипы это тема, которая необходима для понимания основ объектно ориентированного программирования.
Skypro — научим с нуля