Prototype js что это

Обновлено: 18.06.2024

Подключаюсь к разработке проекта, в котором используется этот замечательный js-фреймворк. До этого использовал только jQuery, поэтому пришлось изучать эту новую для меня библиотеку.

Все заинтересованных — прошу под кат

Введение

Для начала, хочу отметить, что данный топик — не место для холивара, вида «X круче, чем Y». Я хочу рассказать только о возможностях данного фреймворка, не затрагивая другие.

Данный фреймоворк поддерживается Internet Explorer 6.0+, Mozilla Firefox 1.5+, Apple Safari 2.0+ и Opera 9.25+. Соответственно, его можно использовать в 99% проектов.

На текущий момент, последняя версия — 1.7, выпущена 22 ноября 2010.

Итак, мы скачали исходный код библиотеки и подключили её в нашем html-документе


Теперь рассмотрим возможности, которые нам стали доступны

Базовые возможности

Собственно, вся соль фреймворка изложена в 5-ти функциях. О них ниже.

$()
Здесь всё просто. Теперь

можно заменить на


Более того, если функции передать несколько параметров, то она вернет массив элементов


Этот массив можно обработать в цикле, например


$$()
Функция разбивает один или несколько CSS-cелекторов, которые поступают на вход, и возвращает элементы которые соответствуют этим фильтрам


Данный код выведет:
My name
pass

Рассмотрим пример по-сложнее, когда на вход функции будет подано несколько фильтров

Здесь мы увидим следующий результат:
My name
pass
Password

$F()
Для кода выше, мы могли получить значения элементов формы следующим образом:

Как вы, наверное, уже догадались, результатом будет
My name
pass

Для лучшего понимания, следующие три конструкции аналогичны

Значения a, b, c будут равны

$A()
Функция $A() преобразует один аргумент, который она получает в объект Array.

Получаем
3: Test 1
2: Test 2
4: Test 3

$H()
Функция $H() преобразовывает объекты в перечислимые Hash-объекты, которые похожи на ассоциативный массив.

Каждый элемент hash-объекта — это массив из двух элементов: ключ и значение. Помимо этого, объект обладает 5-ю методами
keys() — возвращает массив из всех ключей
values() — возвращает массив из всех значений
merge(Hashes) — принимает объекты типа Hash, возвращает только один объект, результат их объединения
toQueryString() — преобраовывает объект в строку query-string. Т.е строку вида «key1=value1&key2=value2&key3=value3»
inspect() — возвращает объект в формате массива, вида «ключ: значение»


Круто, не так ли?

Полезные функции

И рассмотрим еще несколько функций, без которых жизнь не была бы столь прекрасна

getElementsByClassName()
Работает аналогично функции getElementsByTagName(). Отличие лишь в том, что необходимо подавать не имя тега, а название класса. В массиве возвращаются все элементы, которые соответствуют указанному классу. Функция работает даже в том случае, если для элемента определено несколько классов.
Думаю, и без примера всё ясно.

В итоге, напечатается
первый
второй

А сама функция вернет 2.
Думаю, здесь всё понятно. Незаменимый инструмент при отладке

Еще одной полезной возможностью библиотеки, является работа с текстовыми шаблонами.

Получим:
Вы заказываете 1шт. товара Книга по 24.50р. каждая
Вы заказываете 3шт. товара Ручка по 5.44р. каждая
Вы заказываете 4шт. товара Тетрадь по 10.00р. каждая

Ну и разумеется, в современном мире нельзя закончить, не сказав об AJAX. Теперь AJAX-запрос можно выполнить следующим образом:


Метод — get или post.
Параметры — вида ключ=значение, объединённые в Query-string.
OnComplete — функция, которая будет вызвана, после завершения AJAX-запроса

Заключение

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

Спасибо, если вы дочитали до этого места.

Полезные ссылки

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

Итого

  • В JavaScript есть встроенное «наследование» между объектами при помощи специального свойства __proto__ .
  • При установке свойства rabbit.__proto__ = animal говорят, что объект animal будет «прототипом» rabbit .
  • При чтении свойства из объекта, если его в нём нет, оно ищется в __proto__ . Прототип задействуется только при чтении свойства. Операции присвоения obj.prop = или удаления delete obj.prop совершаются всегда над самим объектом obj .

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

В современных браузерах есть методы для работы с прототипом:

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

Также мы рассмотрим, как свойство __proto__ используется внутри самого языка JavaScript и как организовать классы с его помощью.

Прототип объекта

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

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

Связующим звеном выступает специальное свойство __proto__ .

Object.create(null)

Зачастую объекты используют для хранения произвольных значений по ключу, как коллекцию:

При дальнейшем поиске в этой коллекции мы найдём не только text и age , но и встроенные функции:

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

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

Однако, есть путь и проще:

Объект, создаваемый при помощи Object.create(null) не имеет прототипа, а значит в нём нет лишних свойств. Для коллекции – как раз то, что надо.

prototype

Все объекты в javascript наследуют от Object, и потому имеют свойство prototype .

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

Предположим, что нам требуется добавить в объект Array метод, который возвращает значение наибольшего элемента массива. Для этого объявляется функция, которая добавляется к объекту Array.prototype , а затем используется.

В obj.prototype как получить не все экземпляры этого класса, а конкретный экземпляр, используемый юзером сейчас?

this указывает на конкретный экземпляр.

По-моему в код опечатка или ошибка

скобок в этом случае быть не должно, т.к. Array.prototype.max должна быть функцией, а не ее результатом.

Чтобы было понятнее, можно сделать и так:

Ребята, читайте спецификацию, а не такие вот "руководства":
". объекты могут создаваться различными способами, в том числе – посредством буквенного обозначения или с помощью конструкторов, которые создают объекты и выполняют код, инициализирующий их полностью или частично путем присвоения их свойствам начальных значений. Каждый конструктор является функцией, которая обладает свойством “prototype”, используемым для реализации прототипного наследования и разделяемых свойств".
У объекта Javascript (не путать с функцией Object()) нет свойства prototype! Есть неявное свойство, которое ссылается на constructor.prototype. Таким образом, prototype можно задать только для конструктора объекта, но не для самого объекта.

Полностью согласен.
А свойство __proto__ доступно во всех браузерах или нет?
Если сильно надо, то можно им воспользоваться ведь.

Очень дельный комментарий. Тоже обратил внимание на не совсем корректное описание. Спасибо.

Видимо я не до конца понял работу свойства prototype, подскажите пожалуйста где ошибка. Вот мой код:
var ivan = im: "Иван",
>

ivan.prototype.say_name = function(name)
this.name = name;
alert("Привет, меня зовут "+this.name);
>

Вопрос закрыт - разобрался сам. - Просто прочитал статью выше и понял ошибку: свойство prototype можно задавать только конструктору объекта, а не конкретному объекту. Поэтому и выбивало ошибку.

Скажи пожалуйста, в чем разница

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

Во втором способе такого преимущества нет. Но, поскольку метод getA размещен внутри функции foo (что, вообще говоря, не обязательно), ему доступны объявленные в foo переменные

почему не работает такая конструкция?

Потому что "this" в "in_array" указывает на "fn", а не на "a".

Как часто пишут:
"В отличие от многих языков, this никак не привязано к объекту, а обозначает просто объект, вызвавший функцию."

В данной ситуации объект 'a' имеет свойство fn.
Свойство fn является самостоятельным объектом с методом in_array(el).
Поэтому this указывает именно объект fn.

Метод hasOwnProperty

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

Он перебирает всё, например:

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

Вызов obj.hasOwnProperty(prop) возвращает true , если свойство prop принадлежит самому объекту obj , иначе false .

Для того, чтобы перебрать свойства самого объекта, достаточно профильтровать key через hasOwnProperty :

Прототипы в JS и малоизвестные факты

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

Оказалось, что есть много неочевидных вещей из старых времён ES5 и даже ES6, о которых я не слышал. А еще оказалось, что вывод консоли браузера может не соответствовать действительности.

Что такое прототип

Объект в JS имеет собственные и унаследованные свойства, например, в этом коде:

у объекта foo имеется собственное свойство bar со значением 1 , но также имеются и другие свойства, такие как toString . Чтобы понять, как объект foo получает новое свойство toString , посмотрим на то, из чего состоит объект:




Дело в том, что у объекта есть ссылка на другой объект-прототип. При доступе к полю foo.toString сначала выполняется поиск такого свойства у самого объекта, а потом у его прототипа, прототипа его прототипа, и так пока цепочка прототипов не закончится. Это похоже на односвязный список объектов, где поочередно проверяется объект и его объекты-прототипы. Так реализовано наследование свойств, например, у (почти, но об этом позже) любого объекта есть методы valueOf и toString .

Как выглядит прототип

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

Да кто такой этот ваш constructor

constructor – это ссылка на функцию, с помощью которой был создан объект:

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

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

Где живёт прототип

На самом деле, объекты представляют собой не только поля, доступные для JS кода. Интерпретатор также сохраняет некоторые приватные данные объекта для работы с ним, для этого в стандарте определено понятие внутренних слотов, которые обозначены как имя в квадратных скобках [[SlotName]] . Для прототипов отведен приватный слот [[Prototype]] содержащий ссылку на объект-прототип (либо null , если прототипа нет).

Из-за того, что [[Prototype]] предназначался исключительно для самого JS движка, получить доступ к прототипу объекта было невозможно. Для случаев когда это было нужно, ввели нестандартное свойство __proto__ , которое поддержали многие браузеры и которое по итогу попало в сам стандарт, но как опциональное и стандартизированное только для обратной совместимости с существующим JS кодом.

О чем вам недоговаривает дебаггер, или он вам не прототип

Свойство __proto__ является геттером и сеттером для внутреннего слота [[Prototype]] и находится в Object.prototype :




Из-за этого я избегал записи __proto__ для обозначения прототипа. __proto__ находится не в самом объекте, что приводит к неожиданным результатам. Для демонстрации попробуем через __proto__ удалить прототип объекта и затем восстановить его:

Как так получилось? Дело в том, что __proto__ – это унаследованное свойство Object.prototype , а не самого объекта foo . Из-за этого в момент когда в цепочке прототипов пропадает ссылка на Object.prototype , __proto__ превращается в тыкву и перестает работать с прототипом.
А теперь отработаем кликбейт из введения. Представим следующую цепочку прототипов:



В консоли Chrome foo будет выглядеть следующим образом:




А теперь уберем связь между baz и Object.prototype :

И теперь в консоли Chrome видим следующий результат:




Связь с Object.prototype разорвана у baz и __proto__ возвращает undefined даже у дочернего объекта foo , однако Chrome все равно показывает что __proto__ есть. Скорее всего тут имеется в виду внутренний слот [[Prototype]] , но для простоты это было изменено на __proto__ , ведь если не извращаться с цепочкой прототипов, это будет верно.

Как работать с прототипом объекта

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

Для изменения прототипа у существующего объекта есть всего два метода: использование сеттера __proto__ и метод Object.setPrototypeOf .

Если браузер не поддерживает ни один из этих методов, то изменить прототип объекта невозможно, можно только создать его копию с новым прототипом.
Но есть один нюанс с внутренним слотом [[Extensible]] который указывает на то, возможно ли добавлять к нему новые поля и менять его прототип. Есть несколько функций, которые выставляют этот флаг в false и предотвращают смену прототипа: Object.freeze , Object.seal , Object.preventExtensions . Пример:

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

Если нет поддержки Object.create , но есть __proto__ :

И в случае если отсутствует поддержка всего вышеперечисленного:

Способ основан на логике работы оператора new , о которой поговорим чуть ниже. Но сам способ основан на том, что оператор new берет свойство prototype функции и использует его в качестве прототипа, т.е. устанавливает объект в [[Prototype]] , что нам и нужно.

Функции и конструкторы

А теперь поговорим про функции и как они работают в качестве конструкторов.

Функция Person тут является конструктором и создает два поля в новом объекте, а цепочка прототипов выглядит так:




Откуда взялся Person.prototype ? При объявлении функции, у нее автоматически создается свойство prototype для того чтобы ее можно было использовать как конструктор (note 3), таким образом свойство prototype функции не имеет отношения к прототипу самой функции, а задает прототипы для дочерних объектов. Это позволит реализовывать наследование и добавлять новые методы, например так:



И теперь вызов user.fullName() вернет строку "John Doe".

Что такое new

На самом деле оператор new не таит в себе никакой магии. При вызове new выполняет несколько действий:

  1. Создает новый объект self
  2. Записывает свойство prototype функции конструктора в прототип объекта self
  3. Вызывает функцию конструктор с объектом self в качестве аргумента this
  4. Возвращает self если конструктор вернул примитивное значение, иначе возвращает значение из конструктора

Все эти действия можно сделать силами самого языка, поэтому можно написать свой собственный оператор new в виде функции:

Но начиная с ES6 волшебство пришло и к new в виде свойства new.target, которое позволяет определить, была ли вызвана функция как конструктор с new, или как обычная функция:

new.target будет undefined для обычного вызова функции, и ссылкой на саму функцию в случае вызова через new ;

Наследование

Зная все вышеперечисленное, можно сделать классическое наследование дочернего класса Student от класса Person . Для этого нужно

  1. Создать конструктор Student с вызовом логики конструктора Person
  2. Задать объекту `Student.prototype` прототип от `Person`
  3. Добавить новые методы к `Student.prototype`



Фиолетовым цветом обозначены поля объекта (они все находятся в самом объекте, т.к. this у всей цепочки прототипов один), а методы желтым (находятся в прототипах соответствующих функций)
Вариант 1 предпочтительнее, т.к. Object.setPrototypeOf может привести к проблемам с производительностью.

Сколько вам сахара к классу

Для того чтобы облегчить классическую схему наследование и предоставить более привычный синтаксис, были представлены классы, просто сравним код с примерами Person и Student:

Уменьшился не только бойлерплейт, но и поддерживаемость:

  • В отличие от функции конструктора, при вызове конструктора без new выпадет ошибка
  • Родительский класс указывается ровно один раз при объявлении

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

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

P. P. S.

К сожалению главный кликбейт статьи перестал быть актуальным. В данный момент Chrome (версия 93, на момент обновления статьи) перестал использовать __proto__ для обозначения прототипа, и теперь отображает его как слот [[Prototype]] :

image

Справедливости ради хочу отметить что в Firefox (92) также не используется обозначение __proto__ :

Методы для работы с proto

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

Кроме того, есть ещё один вспомогательный метод:

Прототип proto

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

Свойство __proto__ доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.

Пример кода (кроме IE10-):

  1. Первый alert здесь работает очевидным образом – он выводит свойство jumps объекта rabbit .
  2. Второй alert хочет вывести rabbit.eats , ищет его в самом объекте rabbit , не находит – и продолжает поиск в объекте rabbit.__proto__ , то есть, в данном случае, в animal .

Иллюстрация происходящего при чтении rabbit.eats (поиск идёт снизу вверх):


Объект, на который указывает ссылка __proto__ , называется «прототипом». В данном случае получилось, что animal является прототипом для rabbit .

Также говорят, что объект rabbit «прототипно наследует» от animal .

Обратим внимание – прототип используется исключительно при чтении. Запись значения, например, rabbit.eats = value или удаление delete rabbit.eats – работает напрямую с объектом.

В примере ниже мы записываем свойство в сам rabbit , после чего alert перестаёт брать его у прототипа, а берёт уже из самого объекта:

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

У объекта, который является __proto__ , может быть свой __proto__ , у того – свой, и так далее. При этом свойства будут искаться по цепочке.

Если вы будете читать спецификацию ECMAScript – свойство __proto__ обозначено в ней как [[Prototype]] .

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

Читайте также: