Свойство F.prototype и создание объектов через new
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/function-prototype.
До этого момента мы говорили о наследовании объектов, объявленных через <. >.
Но в реальных проектах объекты обычно создаются функцией-конструктором через new . Посмотрим, как указать прототип в этом случае.
Свойство F.prototype
Самым очевидным решением является назначение __proto__ в конструкторе.
Например, если я хочу, чтобы у всех объектов, которые создаются new Rabbit , был прототип animal , я могу сделать так:
var animal = < eats: true >; function Rabbit(name) < this.name = name; this.__proto__ = animal; >var rabbit = new Rabbit("Кроль"); alert( rabbit.eats ); // true, из прототипа
Недостаток этого подхода – он не работает в IE10-.
К счастью, в JavaScript с древнейших времён существует альтернативный, встроенный в язык и полностью кросс-браузерный способ.
Чтобы новым объектам автоматически ставить прототип, конструктору ставится свойство prototype .
При создании объекта через new , в его прототип __proto__ записывается ссылка из prototype функции-конструктора.
Например, код ниже полностью аналогичен предыдущему, но работает всегда и везде:
var animal = < eats: true >; function Rabbit(name) < this.name = name; >Rabbit.prototype = animal; var rabbit = new Rabbit("Кроль"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true
Установка Rabbit.prototype = animal буквально говорит интерпретатору следующее: «При создании объекта через new Rabbit запиши ему __proto__ = animal «.
Свойство prototype имеет смысл только у конструктора
Свойство с именем prototype можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору.
Само по себе, без вызова оператора new , оно вообще ничего не делает, его единственное назначение – указывать __proto__ для новых объектов.
Значением prototype может быть только объект
Технически, в это свойство можно записать что угодно.
Однако, при работе new , свойство prototype будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано.
Свойство constructor
У каждой функции по умолчанию уже есть свойство prototype .
Оно содержит объект такого вида:
function Rabbit() <> Rabbit.prototype = < constructor: Rabbit >;
В коде выше я создал Rabbit.prototype вручную, но ровно такой же – генерируется автоматически.
function Rabbit() <> // в Rabbit.prototype есть одно свойство: constructor alert( Object.getOwnPropertyNames(Rabbit.prototype) ); // constructor // оно равно Rabbit alert( Rabbit.prototype.constructor == Rabbit ); // true
Можно его использовать для создания объекта с тем же конструктором, что и данный:
function Rabbit(name) < this.name = name; alert( name ); >var rabbit = new Rabbit("Кроль"); var rabbit2 = new rabbit.constructor("Крольчиха");
Эта возможность бывает полезна, когда, получив объект, мы не знаем в точности, какой у него был конструктор (например, сделан вне нашего кода), а нужно создать такой же.
Свойство constructor легко потерять
JavaScript никак не использует свойство constructor . То есть, оно создаётся автоматически, а что с ним происходит дальше – это уже наша забота. В стандарте прописано только его создание.
В частности, при перезаписи Rabbit.prototype = < jumps: true >свойства constructor больше не будет.
Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не «сломается». Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие constructor вручную:
Rabbit.prototype = < jumps: true, constructor: Rabbit >;
Либо можно поступить аккуратно и добавить свойства к встроенному prototype без его замены:
// сохранится встроенный constructor Rabbit.prototype.jumps = true
Эмуляция Object.create для IE8-
Как мы только что видели, с конструкторами всё просто, назначить прототип можно кросс-браузерно при помощи F.prototype .
Теперь небольшое «лирическое отступление» в область совместимости.
Прямые методы работы с прототипом отсутствуют в старых IE, но один из них – Object.create(proto) можно эмулировать, как раз при помощи prototype . И он будет работать везде, даже в самых устаревших браузерах.
Кросс-браузерный аналог – назовём его inherit , состоит буквально из нескольких строк:
function inherit(proto) < function F() <>F.prototype = proto; var object = new F; return object; >
Результат вызова inherit(animal) идентичен Object.create(animal) . Она создаёт новый пустой объект с прототипом animal .
var animal = < eats: true >; var rabbit = inherit(animal); alert( rabbit.eats ); // true
Посмотрите внимательно на функцию inherit и вы, наверняка, сами поймёте, как она работает…
Если где-то неясности, то её построчное описание:
function inherit(proto) < function F() <>// (1) F.prototype = proto // (2) var object = new F; // (3) return object; // (4) >
- Создана новая функция F . Она ничего не делает с this , так что если вызвать new F , то получим пустой объект.
- Свойство F.prototype устанавливается в будущий прототип proto
- Результатом вызова new F будет пустой объект с __proto__ равным значению F.prototype .
- Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
Для унификации можно запустить такой код, и метод Object.create станет кросс-браузерным:
if (!Object.create) Object.create = inherit; /* определение inherit - выше */
В частности, аналогичным образом работает библиотека es5-shim, при подключении которой Object.create станет доступен для всех браузеров.
Итого
Для произвольной функции – назовём её Person , верно следующее:
- Прототип __proto__ новых объектов, создаваемых через new Person , можно задавать при помощи свойства Person.prototype .
- Значением Person.prototype по умолчанию является объект с единственным свойством constructor , содержащим ссылку на Person . Его можно использовать, чтобы из самого объекта получить функцию, которая его создала. Однако, JavaScript никак не поддерживает корректность этого свойства, поэтому программист может его изменить или удалить.
- Современный метод Object.create(proto) можно эмулировать при помощи prototype , если хочется, чтобы он работал в IE8-.
Задачи
Прототип после создания
важность: 5
В примерах ниже создаётся объект new Rabbit , а затем проводятся различные действия с prototype .
Каковы будут результаты выполнения? Почему?
Начнём с этого кода. Что он выведет?
function Rabbit() <> Rabbit.prototype = < eats: true >; var rabbit = new Rabbit(); alert( rabbit.eats );
Добавили строку (выделена), что будет теперь?
function Rabbit() <> Rabbit.prototype = < eats: true >; var rabbit = new Rabbit(); Rabbit.prototype = <>; alert( rabbit.eats );
А если код будет такой? (заменена одна строка):
function Rabbit(name) <> Rabbit.prototype = < eats: true >; var rabbit = new Rabbit(); Rabbit.prototype.eats = false; alert( rabbit.eats );
А такой? (заменена одна строка)
function Rabbit(name) <> Rabbit.prototype = < eats: true >; var rabbit = new Rabbit(); delete rabbit.eats; // (*) alert( rabbit.eats );
И последний вариант:
function Rabbit(name) <> Rabbit.prototype = < eats: true >; var rabbit = new Rabbit(); delete Rabbit.prototype.eats; // (*) alert( rabbit.eats );
Результат: true , из прототипа
Результат: true . Свойство prototype всего лишь задаёт __proto__ у новых объектов. Так что его изменение не повлияет на rabbit.__proto__ . Свойство eats будет получено из прототипа.
Результат: false . Свойство Rabbit.prototype и rabbit.__proto__ указывают на один и тот же объект. В данном случае изменения вносятся в сам объект.
Результат: true , так как delete rabbit.eats попытается удалить eats из rabbit , где его и так нет. А чтение в alert произойдёт из прототипа.
Результат: undefined . Удаление осуществляется из самого прототипа, поэтому свойство rabbit.eats больше взять неоткуда.
Аргументы по умолчанию
важность: 4
Есть функция Menu , которая получает аргументы в виде объекта options :
/* options содержит настройки меню: width, height и т.п. */ function Menu(options)
Ряд опций должны иметь значение по умолчанию. Мы могли бы проставить их напрямую в объекте options :
function Menu(options) < options.width = options.width || 300; // по умолчанию ширина 300 . >
…Но такие изменения могут привести к непредвиденным результатам, т.к. объект options может быть повторно использован во внешнем коде. Он передаётся в Menu для того, чтобы параметры из него читали, а не писали.
Один из способов безопасно назначить значения по умолчанию – скопировать все свойства options в локальные переменные и затем уже менять. Другой способ – клонировать options путём копирования всех свойств из него в новый объект, который уже изменяется.
При помощи наследования и Object.create предложите третий способ, который позволяет избежать копирования объекта и не требует новых переменных.
Можно прототипно унаследовать от options и добавлять/менять опции в наследнике:
function Menu(options) < options = Object.create(options); options.width = 300; alert("width: " + options.width); // возьмёт width из наследника alert("height: " + options.height); // возьмёт height из исходного объекта >var options = < width: 100, height: 200 >; var menu = new Menu(options); alert("original width: " + options.width); // width исходного объекта alert("original height: " + options.height); // height исходного объекта
Все изменения будут происходить не в исходном options , а в его наследнике, при этом options останется незатронутым.
Поделиться
Комментарии
перед тем как писать…
- Если вам кажется, что в статье что-то не так — вместо комментария напишите на GitHub.
- Для одной строки кода используйте тег , для нескольких строк кода — тег , если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
- Если что-то непонятно в статье — пишите, что именно и с какого места.
- © 2007—2023 Илья Кантор
- о проекте
- связаться с нами
- пользовательское соглашение
- политика конфиденциальности
Чем является f prototype
Cоздание класса в JavaScript выполняется описанием функции-конструктора. А создание объекта — экземпляра класса — при помощи вызова конструктора в качестве операнда операции new :
При этом в создаваемый объект копируются все свойства, описанный в конструкторе при помощи ключевого слова this . Именно все свойства, в том числе и те, которые являются методами (вспомним, что свойство в JavaScript условно называется методом, если его значением является функция).
Когда в экземпляр класса из конструктора копируются свойства-данные — это правильно. Ведь в каждом экземпляре они могут иметь разные значения, как, например, размеры прямоугольников в объектах rect1 и rect2 :
Но в каждый объект из конструктора копируются ещё и методы. Значит, и в rect1 , и в rect2 копируются функция square , вычисляющая площадь прямоугольника.
Это явно лишнее. Если данные могут быть разными, и могут меняться во время работы с объектами, то функции одни и те же.
В языках программирования, основанных на настоящих классах, в объект копируются только данные, а функции остаются в классе для общего пользования.
А как же псевдоклассы JavaScript? Они такие нерациональные?
Дело в том, что функции в JavaScript — это обычные данные, их можно менять, а значит, иногда есть смысл передавать методы конструктора в создаваемые с его помощью объекты. JavaScript это допускает, и в этом проявляется мощь, гибкость (и сложность, конечно) этого языка.
Но чаще всего методы, описанные в классе, неизменны, поэтому есть смысл хранить их только в конструкторе в единственном экземпляре, а не копировать в создаваемые объекты.
И это, действительно, можно (и нужно делать) при помощи прототипа .
Все функции (как экземпляры класса Function ) имеют свойство prototype (). Начальным значением этого свойства является объект с единственным свойством constructor , которое содержит ссылку на саму функцию.
Пусть определена функция f :
JavaScript для каждой функции создает свойство prototype (ведь функция — это объект, значит, у неё могут быть свойства). Для функции f создаётся свойство f.prototype . Свойство f.prototype содержит ссылку на объект, содержащий единственное свойство constructor .
Таким образом, f.prototype.constructor — то же самое, что f :
alert(f.prototype.constructor(2)); // 4
А теперь раскроем ещё одну особенность работы оператора new . Мы уже говорили, что оператор new создаёт пустой объект, а затем вызывает функцию-конструктор. Но это не все. После создания пустого объекта оператор new устанавливает в этом объекте ссылку на свойство prototype функции-конструктора. Любые свойства, добавленные к прототипу конструктора, автоматически становятся «как бы» свойствами объектов, создаваемых с его помощью. «Как бы» сказано потому, что эти свойства не копируются в объекты, а хранятся в прототипе конструктора в единственном экземпляре.
Это именно то, что нужно! Теперь мы должны принять на вооружение следующее правило.
Свойства нужно описывать в конструкторе. Методы нужно описывать в прототипе конструктора.
Сделаем это на примере построения класса Rectangle .
// Методы класса Rectangle.prototype.square=function();
С объектами можно работать, как и прежде:
Мы действуем так, будто метод square принадлежит каждому экземпляру класса Rectangle . И цикл for/in подтвердит это:
"; alert(str); > var rect = new Rectangle(12,85); alertob(rect); // >
Но метода square теперь реально в объекте нет, он лежит в прототипе конструктора в единственном экземпляре, хотя доступен во всех объектах, созданным при помощи этого конструктора, словно свой собственный метод.
Смотрите пример «Конструктор класса Rectangle с прототипом».
Понятно, что прототип конструктора — идеальное место для хранения методов класса, а также констант класса.
Например, мы можем добавить в прототип конструктора Rectangle константу квадратный дюйм:
// Методы и константы класса Rectangle.prototype.square=function(); Rectangle.prototype.IN2=2.54*2.54; // Добавили константу в прототип // Создать прямоугольник (размеры в сантиметрах) var rect = new Rectangle(12,85); // Вычислить его площадь в квадратных дюймах alert(rect.IN2*rect.square()); // 6580.632
Смотрите пример «Константы класса».
Прототипы — JS: Введение в ООП
В этом уроке дается небольшое введение в тему прототипов. Полностью раскрыть её одним уроком невозможно. Не хватит даже целого курса. Прототипы познаются только через практику, перемешанную с теоретической подготовкой. Поэтому не переживайте, если не всё поймёте с первого раза, это нормально.
Прототипы — это механизм, который оказывает основное влияние на то, как работают объекты в JavaScript. Сами они напрямую в коде используются редко (и обычно только в библиотеках), но их знание важно для понимания поведения кода и отладки. Особенно при работе с классами, которые мы изучим дальше по курсу. В этом уроке мы затронем только самые основы. Глубоко разобраться с прототипами поможет наш курс, указанный в дополнительных материалах.
В JavaScript с каждым объектом связан прототип. Прототип – это обычный объект, хранящийся в специальном служебном поле [[prototype]] (к этому полю невозможно обратиться напрямую). Его можно извлечь так:
const date = new Date(); // Эта функция извлекает прототип объекта из самого объекта const proto = Object.getPrototypeOf(date); // Date <> // В прототипе хранится не конструктор // Что там хранится – узнаем дальше proto === Date; // false const numbers = [1, 2]; Object.getPrototypeOf(numbers); // [] – отображение отличается, но это массив // Прототипы есть и у конструкторов, которые мы определяем сами function Company(name) this.name = name; > const company = new Company(); Object.getPrototypeOf(company); // Company <>
Для чего нужны прототипы
Представим, что мы обращаемся к свойству объекта, и при этом никакого свойства в этом объекте нет. В таком случае мы получим значение undefined . Обычно это так и работает, но есть одна важная особенность.
Если свойства в объекте нет, то JavaScript смотрит прототип этого объекта. Если в прототипе есть искомое свойство, то его значение возвращается. В итоге мы можем обратиться к свойству, которого нет в объекте, но есть в прототипе. И тогда мы получим из прототипа какое-то значение.
В реальности процесс еще сложнее. Если свойство не найдено в прототипе, то JavaScript смотрит прототип прототипа и так далее. Так он проходит до конца цепочки прототипов, то есть до последнего прототипа — это всегда null . На базе этого механизма реализуется наследование. Эта тема выходит за рамки текущего урока.
Прототипы есть даже у обычных JavaScript-объектов:
Object.getPrototypeOf(<>); // <> — это и есть Object
Именно по этой причине даже пустые объекты содержат свойства и методы:
const obj = <>; // То же самое можно сделать так: const obj = new Object(); // Это функция-конструктор, из которой был получен текущий объект, в нашем случае — Object obj.constructor; // [Function: Object] // У obj нет своего собственного свойства constructor, оно пришло из прототипа Object.hasOwn(obj, 'constructor'); // false Object.hasOwn(obj, 'name'); // false obj.name = 'hexlet'; // Имя есть в самом объекте, потому что мы его только что добавили Object.hasOwn(obj, 'name'); // true
Доступ к прототипу можно получить не только из объектов, но и из свойства prototype конструктора, который эти объекты создаёт:
function Company(name) this.name = name; > // Одно и то же, полученное разными способами // Company.prototype === Object.getPrototypeOf(new Company())
Теперь мы можем ответить на вопрос, откуда берется прототип. Прототип – это объект, находящийся в свойстве prototype функции-конструктора, а не сам конструктор. Проверить работу прототипов достаточно легко, изменив их:
// Добавляем свойство getName (делаем его методом) Company.prototype.getName = function getName() // this по-прежнему зависит от контекста, в котором вызывается return this.name; > const company = new Company('Hexlet'); // Свойство доступно! console.log(company.getName()); // => Hexlet
При этом никто не мешает заменить значение свойства getName в конкретном объекте. Это никаким образом не отразится на других объектах, так как они извлекают getName из прототипа:
const company1 = new Company('Hexlet'); const company2 = new Company('Google'); company2.getName = function getName() return 'Alphabet'; > // Этот вызов возьмет свойство из самого объекта company2.getName(); // Alphabet // Этот вызов возьмет значение свойства из прототипа company1.getName(); // Hexlet
Создание свойств через прототип – и есть правильный способ создания своих абстракций в JavaScript. Любая новая абстракция, которая нам нужна в коде, должна выглядеть как конструктор и прототип, наполненный нужными свойствами.
Что даёт этот механизм?
Самое простое – расширение ядра языка и библиотек без прямого доступа к исходному коду. Прототипы – невероятно гибкий механизм, который позволяет менять всё что угодно из любого места программы в рантайме, то есть во время работы. Например, мы можем добавить или заменить любые методы в любых объектах самого языка:
const numbers1 = [1, 3]; // Как только выполнится этот код, все массивы, // включая уже созданные, обзаведутся методом last Array.prototype.last = function last() // Такое обращение сработает, ведь this — это ссылка на сам объект, // который в нашем случае является массивом return this[this.length - 1]; > numbers1.last(); // 3 const numbers2 = [10, 0, -2]; numbers2.last(); // -2 // Пример замены // Это запрещенный прием, никогда так не делайте в реальном коде Array.prototype.map = function map() return 'Ehu!'; > numbers1.map(); // "Ehu!"
Как и любой другой мощный механизм, прототипы опасны. Можно наменять такого, что программирование превратится в ад:
// Очень злой код, который ломает работу метода push Array.prototype.push = function push(value) return this.unshift(value); > const numbers = [1, 2]; numbers.push(3); console.log(numbers); // => [3, 1, 2] .
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Наследование через F.prototype
Здравствуйте! В этом уроке поговорим о на следовании через F.prototype. Как вы знаете, новые объекты могут быть созданы с помощью функции-конструктора new F().
Если в F.prototype содержится объект, оператор new устанавливает его в качестве свойства [[Prototype]] для нового объекта.
Язык JavaScript использовал прототипное наследование с момента своего появления. Это одна из основных особенностей языка.
Но раньше прямого доступа к прототипу объекта не было. Надёжно работало только свойство «prototype» функции-конструктора. Поэтому оно используется во многих скриптах.
Обратите внимание, что F.prototype означает обычное свойство с именем «prototype» для F. Это ещё не «прототип объекта», а обычное свойство F с таким именем.
let animal = < eats: true >; function Rabbit(name) < this.name = name; >Rabbit.prototype = animal; let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true
Установка Rabbit.prototype = animal буквально говорит интерпретатору следующее: «При создании объекта через new Rabbit() запиши ему animal в свойство [[Prototype]]».
F.prototype используется только в момент вызова new F()
F.prototype используется только при вызове new F() и присваивается в качестве свойства [[Prototype]] нового объекта. После этого F.prototype и новый объект уже ничего не связывает. Следует понимать это как «единоразовый бонус» объекту.
После создания F.prototype может измениться, и новые объекты, созданные с помощью new F(), будут иметь другой объект в качестве [[Prototype]], но уже существующие объекты сохранят старый.
F.prototype по умолчанию, свойство constructor
У каждой функции по умолчанию уже есть свойство «prototype».
По умолчанию «prototype» – объект с единственным свойством constructor, которое ссылается на функцию-конструктор.
function Rabbit() <> /* прототип по умолчанию Rabbit.prototype = < constructor: Rabbit >; */
function Rabbit() <> // по умолчанию: // Rabbit.prototype = < constructor: Rabbit >alert( Rabbit.prototype.constructor == Rabbit ); // true
Соответственно, если мы ничего не меняем, то свойство constructor будет доступно всем кроликам через [[Prototype]]:
function Rabbit() <> // по умолчанию: // Rabbit.prototype = < constructor: Rabbit >let rabbit = new Rabbit(); // наследует от alert(rabbit.constructor == Rabbit); // true (свойство получено из прототипа)
Мы можем использовать свойство constructor существующего объекта для создания нового.
Читайте также Объекты в функциях
function Rabbit(name) < this.name = name; alert(name); >let rabbit = new Rabbit("White Rabbit"); let rabbit2 = new rabbit.constructor("Black Rabbit");
Это удобно, когда у нас есть объект, но мы не знаем, какой конструктор использовался для его создания (например, он мог быть взят из сторонней библиотеки), а нам необходимо создать ещё один такой объект.
Но, пожалуй, самое важное о свойстве «constructor» это то, что JavaScript сам по себе не гарантирует правильное значение свойства «constructor».
Да, оно является свойством по умолчанию в «prototype» у функций, но что случится с ним позже – зависит только от нас.
В частности, если мы заменим прототип по умолчанию на другой объект, то свойства «constructor» в нём уже не будет.
function Rabbit() <> Rabbit.prototype = < jumps: true >; let rabbit = new Rabbit(); alert(rabbit.constructor === Rabbit); // false
Таким образом, чтобы сохранить верное свойство «constructor», мы должны добавлять/удалять/изменять свойства у прототипа по умолчанию вместо того, чтобы перезаписывать его целиком:
function Rabbit() <> // Не перезаписываем Rabbit.prototype полностью, // а добавляем к нему свойство R abbit.prototype.jumps = true // Прототип по умолчанию сохраняется, и мы всё ещё имеем доступ к Rabbit.prototype.constructor
Или можно заново создать свойство constructor:
Rabbit.prototype = < jumps: true, constructor: Rabbit >; // теперь свойство constructor снова корректное, так как мы добавили его
Итого
В этом уроке мы кратко рассмотрели способ задания [[Prototype]] для объектов, создаваемых с помощью функции-конструктора. В следующем уроке мы рассмотрим, как можно использовать эту возможность.
Выделим основные моменты:
- Свойство F.prototype (не путать с [[Prototype]]) устанавливает свойство [[Prototype]] для новых объектов при вызове new F().
- Значение F.prototype должно быть либо объектом, либо null. Другие значения не будут работать.
- Свойство «prototype» является особым, только когда оно назначено функции-конструктору, которая вызывается оператором new.
Читайте также Привязка контекста и карринг: «bind» в JavaScript
В обычных объектах prototype не является чем-то особенным:
let user = < name: "John", prototype: "Bla-bla" // никакой магии нет - обычное свойство >;
По умолчанию все функции имеют F.prototype = < constructor: F >, поэтому мы можем получить конструктор объекта через свойство «constructor».
Задания
Создайте новый объект с помощью уже существующего
Представьте, что у нас имеется некий объект obj, созданный функцией-конструктором – мы не знаем какой именно, но хотелось бы создать ещё один объект такого же типа.
Можем ли мы сделать так?
let obj2 = new obj.constructor();
Приведите пример функции-конструктора для объекта obj, с которой такой вызов корректно сработает. И пример функции-конструктора, с которой такой код поведёт себя неправильно.
If you have found a spelling error, please, notify us by selecting that text and tap on selected text.