Чем является f prototype
Перейти к содержимому

Чем является f prototype

  • автор:

Свойство 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) >
  1. Создана новая функция F . Она ничего не делает с this , так что если вызвать new F , то получим пустой объект.
  2. Свойство F.prototype устанавливается в будущий прототип proto
  3. Результатом вызова new F будет пустой объект с __proto__ равным значению F.prototype .
  4. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.

Для унификации можно запустить такой код, и метод 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.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *