×
Ви дійсно бажаєте відкрити доступ до тестування за курсом JavaScript Базовый 2015 на 40 днів?
ВІДЕОУРОК № 2. Конструкторы и прототипы
- Основные конструкторы – Object(), Array(), Function(), Date(), String(). Принцип работы конструкторов, назначение ключевого слова this в конструкторе.
- Создание пользовательских конструкторов.
- Что такое прототип, использование прототипов и добавление свойств и методов в прототип.
- Работа с конструктором Object
- Объектно-ориентированные техники в языке JavaScript. Реализация наследования в JavaScript.
- Способы подключения JavaScript сценариев к HTML документу.
- Создание сценариев (модулей), которые добавляют минимальное количество глобальных переменных.
- Использование свойств объекта document. Методы для получения объектов со страницы.
- DOM – Document Object Model, примеры создания новых узлов, манипулирование существующими узлами, удаление узлов.
- Создание и использование таймеров, использование функций setInterval и setTimeout в языке JavaScript
- Использование Location
- Использование объекта Navigator
- Создание всплывающих окон с помощью JavaScript кода.
- Работа с типом данных string. Методы для работы со строковыми значениями.
- Регулярные выражения в языке JavaScript. Синтаксис и методы, которые могут работать с регулярными выражениями.
- Основы работы с CSS. Создание CSS правил и подключение правил к HTML документам.
- Рассмотрение отдельных CSS свойств, которые часто используются при создании динамических страниц.
- Способы изменения CSS стилей через JavaScript код. Работа с вычисляемыми стилями (computed styles).
- Модель обработки события DOM Level 0. Варианты создания обработчиков, преимущества и недостатки.
- Модель обработки события DOM Level 2. Маршрутизация события, контроль распространения события по дереву разметки с помощью методов stopPropagation() и preventDefault()
- Модель обработки событий Internet Explorer.
- Интерфейс объекта события (Event)
- События мыши.
- Обработка событий клавиатуры.
- Примеры обработки событий.
- Элемент form, его назначение и способы получения к нему доступа с помощью JavaScript кода.
- Элемент input, свойства и типы элементов.
- Примеры проверки (валидации) данных введенных пользователем в форму.
- Пример использования объекта Date для работы с датой и временем.
- Назначение cookies рассмотрение принципов хранения данных на стороне клиента.
- Свойство cookie объекта document. Примеры создания, удаления и изменения значений.
- Другие механизмы хранения данных на стороне клиента - WebStorage, использование свойств localStorage и sessionStorage.
- Элемент img, способы получения объекта элемента с изображением и основные его свойства.
- Примеры предварительной загрузки изображений с сервера.
- Создание графики на стороне клиента с помощью CSS.
- Создание графики на стороне клиента с помощью SVG.
- Создание графики на стороне клиента с помощью Canvas(HTML5).
- Основы работы веб приложений. Разбор протокола HTTP. Использование приложения Fiddler для откладки HTTP запросов.
- AJAX – Asynchronous JavaScript And XML.
- Использование объекта XMLHttpRequest для создания синхронных и асинхронных HTTP запросов.
- Использование XMLHttpRequest для отправки данных с POST и GET запросами.
- Примеры простого AJAX приложения.
Здравствуйте! Я рад приветствовать вас на курсе JavaScript для профессионалов. В этом курсе мы изучим много интересных тем, связанных с разработкой клиентских сценариев. В этом курсе вы узнаете что такое конструкторы и прототипы. Вы научитесь взаимодействовать с документом, работать с окнами браузера. Также в этом курсе вы увидите, как с помощью JavaScript кода можно менять стили, менять оформление документа. Мы изучим с вами, какие есть способы создания обработчиков событий, детальнее рассмотрим событийную модель в JavaScript коде. Разберем с вами как можно создавать формы. Как можно валидировать формы. Как можно на стороне клиента сохранять данные в куки-файлах или используя последние возможности HTML5. В завершении курса мы разберем что такое Ajax, как можно создавать страницы, которые обновляются не полностью, а обновляются частично. Этот курс является продолжением курса JavaScript Базовый, по этому если вы не сталкивались с JavaScript кодом, если вы не знаете основ программирования на этом языке, рекомендую в начале просмотреть видео-курс посвященный азам работы с этим языком программирования.
Темя сегодняшнего урока Конструкторы и прототипы. Я уверен, что многие из вас не раз использовали конструкторы и возможно вы не знали, что вызываете конструктор. Сейчас мы с вами разберем, какие существуют стандартные конструкторы в языке JavaScript, а потом рассмотрим как можно самостоятельно создавать свои собственные конструкторы. Разберем для чего это нужно и какие правила работы конструкторов в языке JavaScript. Давайте сейчас рассмотрим несколько примеров использования стандартных конструкторов. Мы не будем разбирать все конструкторы, потому что их достаточно много. Мы рассмотрим только основные некоторые конструкторы, для того, чтобы вы понимали смысл использования, суть самого применения конструкторов в JavaScript коде. Конструкторы – это специальные функции, задачей которых является заполнить пустой объект свойствами и методами. То есть конструктор – это функция, которая конфигурирует объект для дальнейшего использования. На строке 14 мы создаем переменную с именем point и эту переменную инициализируем вот таким вот выражением. Используем ключевое слово new, за которым идет имя функции. Когда вы видите выражение с использованием ключевого слова new вы можете быть уверены, что это создание нового объекта с использованием конструктора. Ключевое слово new указывает на то, что мы создаем новый пустой объект. То есть это ключевое слово создает объект, в котором нету ничего. Когда мы вызываем вот эту функцию Object() – это означает, что в пустом объекте, который создался ключевым словом new появляются некоторые свойства и методы, которые добавляет вот эта функция –конструктор. Получается, что слово ключевое создает объект. Этот объект передается в функцию. Функция этот объект конфигурирует и настроенный готовый объект возвращается в переменную point. Если мы пользуемся конструктором Object() – это означает, что создаваемый объект будет пустым, то есть в нем не будет ничего кроме стандартных методов. Какие именно методы перечислены в Object(), какие методы добавляет эта функция-конструктор мы с вами разберем немножко позже в этом уроке. Но пока представляйте, что point он абсолютно пустой, в нем ничего нет. То есть конструктор Object() создает пустой объект. Далее на 17 строке мы обращаемся к свойству х, которого не существует в объекте point и это приводит к тому, что свойство х добавляется к объекту point. Потом 19 строчка, точно таким же образом мы добавляем свойство у. И на строке 22, 23 мы отображаем результат на экран. Давайте сейчас посмотрим как будет выглядеть наш код. Как он будет работать. Видите, что значение свойства х равно 10, значение свойства у равно 15. То есть в этом примере мы с помощью конструктора Object() создали пустой объект и заполнили его какими-то свойствами. В следующих примерах мы научимся создавать свои собственные конструкторы, которые будут уже создавать объекты с каким-то определенным количеством свойств и методов. Пока мы рассмотрели самый простой конструктор, который есть в JavaScript. Это конструктор Object().
Следующий конструктор, который я думаю вы использовали. С его помощью мы можем создавать объекты, которые являются объектами описывающими дату и время. На 12 строке мы создаем переменную используя конструктор Date(). Этот конструктор берет локальное время пользователя, время, которое установлено на машине пользователя и на основе этого времени создает дату. То есть в этой переменной будет находится объект с свойствами и методами, которые будут хранить месяц, день, год, часы, минуты, секунды и т.д. Этот созданный объект мы нас троке 14 отображаем в тело документа. Посмотрите, какую информацию этот объект выводит. Вот видите, выводится такое значение.
Следующий конструктор, это конструктор Array(). C его помощью мы можем создавать массивы. На 8 строке, используя конструктор Array() мы создаем массив, в котором ничего не находится. То есть пустой массив. Если мы вызываем конструктор Array() передавая в него несколько значений – это означает, что мы создаем массив в котором определены вот эти элементы. И если мы создаем массив с одним параметром – это означает, что мы создаем массив на 10 элементов и во всех этих элементах не будет никаких значений. Обратите внимание, что здесь мы вызываем конструктор у которого не ключевого слова new. Если вы пользуетесь системными конструкторами, они обычно реализованы таким образом, чтобы создавать объекты без этого ключевого слова. Но если вы пишите свои собственные конструкторы и не предоставляете в этих конструкторах специальной функциональности, которая будет проверять использовалось ключевое слово new или нет. То в таком случае ваши сценарии будут работать неправильно, если вы вызываете конструктор без ключевого слова. Лучше всегда при вызове конструктора использовать ключевое слово new, чтобы не проверять правильно ли сделан конструктор. По этому эти примеры, лучше все-таки их немножко переделать. В этот примере лучше переписать те операции, в которых мы используем конструктор Array(). Лучше всегда дописывать ключевое слово new перед вызовом конструктора.
В следующем примере мы рассматриваем использование конструктора String(). C его помощью мы можем создавать объекты строкового типа. В языке JavaScript мы можем создать строковое значение двумя способами. Это либо используя литерал, как на строке 8. Либо используя конструктор, как на строке 11. Когда мы используем конструктор на 11 строке, мы создаем по сути объект строкового типа. А на строке 8 мы создаем переменную строкового типа, строковое значение. В зависимости от того, каким образом мы создали переменную у нас сильно меняется то, как мы дальше сможем с этой переменной работать и что конкретно с этой переменной мы сможем делать. После определения двух переменных на строке 13 определяется функция, которая принимает один параметр, и отображает значение этого параметра в тело документа. Эту функцию мы используем на 18 и 20 строке для того, чтобы проверить значение, которое находится в переменной simleStr и objectStr. То есть значение этих переменных мы отображаем в документ первые два раза. А теперь мы проверяем в чем будет заключатся разница между сроковой переменной и переменной, в которой находится объект типа string. На 23 строке мы обращаемся к объекту. Обращаемся к свойству customProperty и указываем, что мы этому свойству присваиваем значение 123. Так как этого свойства пока не существует в объекте objectStr, это свойство появляется, оно создается, и на строке 24 мы это свойство отображаем в тело документа. То есть выводим значение customProperty и видим значение 123. А на 27 строке ту же самую операцию мы производим над объектом simleStr. Именно simleStr, в которой находится просто сроковое значение, не объект, а строка. Добавив значение свойства на строке 28 мы это свойство отображаем в тело документа. Давайте посмотри к чему это приводит. Первый раз, когда мы вызывали эту функцию newline, видите, что у нас отобразились значения, которые находятся в строковых переменных. Потом на строке 23 мы добавили свойство со значением 123 и вот это свойство на строке 24 оно у нас отобразилось в тело документа. А на 27 и 28 строчке, когда мы добавляли свойство к переменной, в которой хранится просто строковое значение, первый раз у нас никаких ошибок не было, вроде мы свойство добавили. Но когда на 28 строке мы пытаемся это свойство прочитать, мы видим, что результатом у нас выдается значение undefined вместо значения 123, на которое мы рассчитывали. Вот этот пример показал, что использование объекта и использование срокового значения оно в принципе имеет разницу. То есть когда вы создаете new String() вы можете к полученной переменной, к проинициализированной переменной добавлять свойства, убирать свойства, удалять свойства из этого объекта, то есть вы можете работать с переменной именно как с объектом. В случае, если вы создаете переменную, в которой находится просто строковое значение, то добавлять свойство в эту переменную у вас не получится, вы не сможете ее расширять. Почему вы не можете расширять переменную, которая создана на 8 строке в то время, как переменная на 11 строке поддерживает расширение. Это связано с тем, что каждый раз, когда вы в JavaScript коде обращаетесь к простому значению, обращаетесь к целочисленному значению, строковому или логическому, то интерпретатор временно это значение превращает в объект для того, чтобы вы могли с этим объектом взаимодействовать. Вот например сейчас на строке 27, когда мы обращаемся к значению строкового типа, интерпретатор по сути создает тут new String(), передает в этот конструктор строковое значение, которым мы пользуемся, и вот этот вот появившийся объект строкового типа, мы для этого объекта добавляем свойства. Вот по сути почему мы можем создать в JavaScript коде значение строкового типа, и после этого значения поставить точку и увидеть список методов, которые характерны для строковых переменных, для строкового объекта. Это связано с тем, что выполнив, добавив такой литерал, когда мы к этому литералу будем обращаться, интерпретатор временно этот литерал преобразует в объект. И на этом объекте у нас появится доступ ко всем методам, которые есть у строковых переменных. Вот получается, что эта же операция произошла у нас на 27 строке, когда мы обратились к строковому значению и добавили в него свойства. То есть мы фактически добавили свойство в этот объект, но этот объект нигде у нас не сохранился, не зафиксировался. Он создался и дальше на следующей инструкции, на 28 строке об этом объекте наш сценарий уже не видит, он про него не знает, объект будет удален из памяти. Когда на 28 строке мы обращаемся к simpleStr, к этой переменной повторно, для того, чтобы прочитать значение, которое было создано выше, то вот в этом случае интерпретатор еще раз это простое значение превращает в строковой объект. И получается, что теперь, так как это новый строковой объект, то в этом новом строковом объекте никакого свойства customProperty не существует. Соответственно на 28 строке у нас выводится значение undefined. Вот это такая особенность работы JavaScript интерпретатора, каждый раз, когда мы в коде работаем с каким-то простым значением: строковым, вещественным, логическим. Каждый раз это значение преобразовывается в объект, для того чтобы у нас была возможность взаимодействовать с какими-то определенными методами, характерными для определенного типа данных.
Последний пример из первой директории по основным функциям показывает использование конструктора Function(). C его помощью мы можем создавать переменные типа function, то есть мы сможем создавать объекты функций. На 15 строке, используя конструктор Function() мы создаем функцию, которая принимает два аргумента х и у. И в теле этой функции происходит вот такая операция: return x+y. Используя конструктор Function() мы можем создавать функции и последний переметр, который передается в конструктор Function() он всегда является телом функции, а все параметры, которые идут до последнего являются аргументами, которые будут передаваться в функцию. Можна сказать, что мы сейчас на лету создаем функцию сложения и на строке 16 этой функцией начинаем пользоваться. Вызываем функцию, передавая два аргумента, которые в итоге суммируются и возвращаются в переменную result. Получается, что на строке 18 мы увидим сумму. 20+10 = 30. На 21 строке мы используем конструктор Function() для того, чтобы создать функцию, которая будет просто отображать сообщение alert(“Hello”). И эту функцию на строке 22 вызываем и видим результат работы этой функции. Мы видим первый, что результат сложения 30. Второй раз мы видим, что выполнилась функция fun2 и вывелось сообщение Hello. Вот с помощью конструктора Function() мы можем на лету создавать JavaScript функции. Использование этого конструктора не является желательным, потому что если вы будете злоупотреблять применением конструктора Function(), то ваш сценарий может замедлится, скорость работы вашего кода упадет. И еще если вы будете не правильно пользоваться этой функцией-конструктором, если вы будете получать данные из которых создается сама функция извне пользователей, то вы можете сделать брешь в безопасности своего приложения, потому что если пользователь передаст вам какие-то вредоносные данные, передаст код, который нежелательно выполнять на странице, этот код, вот если он попадет в тело функции, он выполнится и может привести к каким-то нежелательным последствиям в вашем документе. По этому использование конструктора Function() оно нежелательно, потому что вы теряете скорость выполнения своего сценария и так же у вас могут возникнуть проблемы с безопасностью. Но вот такая интересная возможность создания функций налету в JavaScript коде имеется. Так же, я думаю, некоторые из вас слышали метод eval. C его помощью мы также можем указать строку, которая будет выполнятся как JavaScript код. То есть если мы выполним eval вот такую вот строчку – это по сути создание переменной а со значением 10. Но тоже использование функции eval является небезопасным. По этому эти примеры разбираем, но не рекомендуется использовать такой код в своих сценариях.
Мы сейчас рассмотрели первую часть примеров их этого урока. Задачей этих примеров было – показать, что в JavaScript есть ряд стандартных системных функций-конструкторов, которые позволяют нам создавать и конфигурировать объекты. Объекты, которые будут хранить дату и время, объекты, которые будут хранить строки, функции или массивы данных. Теперь мы с вами перейдем к следующим примерам, в которых разберемся как можно создавать свои собственные функции конструкторы.
Давайте еще раз дадим определение конструктору. Конструктор – это функция, которая создает пустой объект и определяет его свойства и методы. То есть конструктор наполняет пустой объект данными. И функция конструктор в языке JavaScript является эквивалентом типов данных в других языках программирования. Например, если вы работали с языком С++, Java или C# или любым другим языком, вы знаете, что у вас есть возможность создать тип данных, и потом пользуясь эти типом создавать экземпляры этого типа. Но в случае с языком JavaScript, такой возможности у нас нет. Когда мы создаем переменную, она может быть одного из следующих типов. Переменная может быть либо String, Number, Bool – простые типы данных. Тривиальные типы, такие как undefined и null? Тоже два типа, которые есть в языке JavaScript. И такие типы как Object, Array и Function. То есть создавая переменную мы можем выбрать только один из перечисленных типов. Если мы хотим создать свою категорию объектов, например, в нашем приложении должен находится объект user, в котором хранится информация о пользователе. Объектов пользователь в нашем сценарии будет допустим 5 штук. И получается, что каждый объект имеет одинаковую структуру, одинаковые свойства, одинаковые методы. И получается, что создавать объекты постоянно создавая пустой объект и заполняя его свойствами по одному и тому же принципу будет очень неудобно. Если мы создаем 5 абсолютно одинаковых объектов, используя 10 инструкций, которые добавляют 10 свойств в эти объекты, то мы можем допустить где-то ошибку, и один из объектов будет отличатся от остальных на одно свойство, на один какой-то дополнительный метод, который мы просто случайно добавили. И вот для того, чтобы избавится от потребности по одному и тому же шаблону штамповать объекты, просто дублируя код, мы можем воспользоваться функциями конструкторами. Можем создать свою собственную функцию-конструктор, которая будет создавать объект по некоторому шаблону. Если вы посмотрите на слайд, который сейчас открыт перед нами, вот если мы собираемся объявить функцию конструктор, мы должны создать обычную функцию, дать имя этой функции с заглавной буквы. Есть такое правило: обычные функции начинаются их букв в нижнем регистре, имя функций конструкторов – это первая буква в верхнем регистре. Далее, что отличает эту функцию от обычной функции. Функция-конструктор от обычной функции отличается тем, что в этой функции мы пользуемся ключевым словом this и посредством этого ключевого слова к объекту добавляем различные свойства и методы. Вот если взять сейчас функцию, когда она будет запущена, сюда будет передано два значения х и у. И эти два значения будут на строке вот этой записаны в свойство х объекта this, и значение у будет записано в свойство у объекта this. Вот что делает эта функция? Она в объект this добавляет два свойства. Вот это то, что отличает эту функцию от обычной функции. То есть функция-конструктор в своем теле использует ключевое слово this для того, чтобы в объект добавить какие-то свойства. Откуда берется это ключевое слово this? Когда мы пользуемся функцией-конструктором и запускаем ее, мы ее запускаем с использование ключевого слова new. Вот как раз это ключевое слово создает пустой объект, передает этот пустой объект в функцию и внутри функции к объекту мы можем получать доступ посредством ключевого слова this. То есть ключевое слово this будет всегда ссылаться на пустой объект, который был создан в момент запуска функции. С помощью ключевого слова new. И вот создали пустой объект, бросили его в функцию-конструктор. Функция-конструктор добавила пустому объекту два свойства, этот объект с двумя свойствами был записан в переменную р. Если мы сейчас продублируем эту строчку 10 раз, 10 раз вызовем new Point, то мы создадим 10 объектов имеющих абсолютно одинаковую структуру, одинаковое наполнение, одинаковые свойства и одинаковые методы. И вот получается использование функций конструкторов – это и есть эквивалент использования типов в других языках программирования. То есть что такое тип данных? Как мы отличаем, что этот объект относится к одному типу, а этот к другому типу. Мы смотрим по состояниям и поведению объекта. Если два объекта имеют одинаковый набор свойств и одинаковые методы – это означает, что эти объекты относятся к одному типу. Можно сказать, что в языке JavaScript вместо типов используются виды объектов. Есть объекты, которые принадлежат одному виду, объекты использующие один набор свойств и один набор методов. Например, объекты принадлежащие к виду Point они будут иметь свойство х и у. Объекты, принадлежащие к виду User будут иметь свойства login и password и т.д. Давайте теперь посмотрим, как мы можем в коде работать с функциями конструкторами, какие есть особенности работы с JavaScript кодом.
Вот в первом примере мы посмотрим с вами ту функцию конструктор, которая была показана на слайде. На 15 строке объявляем функцию-конструктор. Имя начинаем с заглавной буквы, для того чтобы было понятно что это функция-конструктор. В теле функции мы используем ключевое слово this и пустому объекту добавляем свойство х и у, устанавливая в эти свойства значения, которые были получены через аргументы конструктора. На строке 22 создаем переменную leftTop, которая создается с использованием функции-конструктора Point(). Обязательно указываем ключевое слово new, то есть пишем new Point(), указываем координаты. Вот leftTop – это верхняя левая точка с координатами 0,0. topPoint создаем тоже с помощью конструктора new Point(), указывая координаты 15, 30. rightBottom – это тоже переменная типа Point(). Получается все вот эти вот переменные – это объекты, которые были созданы с помощью функции-конструктора Point(). В большинстве языков программирования мы бы сказали, что эти переменные типа Point(), но на самом деле эти переменные в случае JavaScript – это объекты в которых есть по два свойства: свойство х и у. Эти объекты по своему содержимому абсолютно одинаковы, поэтому мы можем их привести к одному виду объектов. Не правильно говорить, что в языке JavaScript существуют классы, но очень часто разработчики говорят, что создаем переменную класса Point. Если вы слышите такую фразу, то это имеется ввиду, что создается переменная с помощью функции-конструктора. Ну и для того, чтобы доказать, что все переменные, которые мы здесь создали имеют одинаковые свойства, ниже с 28 по 30 строчку мы обращаемся к свойству х на каждом объекте и свойству у на каждом объекте. Вот видим, что действительно свойства у нас отображаются.
В следующем примере мы посмотрим как создать функцию-конструктор, которая создает объект не только с свойствами, а еще и с методами. На строке 9 определяется функция-конструктор Human(). Опять даем ей имя с заглавной буквы. Эта функция теперь принимает только один параметр – имя того человека, которого мы создаем, имя объекта Human. Далее на 11 строке мы указываем что в пустой объект добавляется свойство firstName, куда записывается значение полученные из аргументов. А на строке 14 мы добавляем еще одно свойство с именем sayHello, но так как в это свойство мы записываем анонимную функцию, то sayHello будем у нас не свойством, а методом. Помните с предыдущего курса? Когда мы в объект помещаем свойство, а этому свойству в качестве значение задаем функцию, то вот эта новая конструкция, которая появится в объекте будет называется методом. Функция, принадлежащая объекту называется методом. На строке 15 то что буде происходить, когда мы запустим sayHello – это вывод сообщения «Hello! My name is » и данные, которые находятся в свойстве firstName. То есть на вот этой 15 строчке мы обращаемся к свойству firstName и обращаемся к значению, которое там находится. И вот сейчас на 21 строке и на 22 строке мы создаем две переменные h и r. Эти переменные инициализируем используя свой собственный конструктор – конструктор Human(). Первый Human будет с именем Homer, второй Human будет с именем Rocky. На строке 25 и 26 мы на двух этих переменных h и r вызываем методы sayHello. И получается, что каждый объект выводит «Hello! My name is » и имя, которое находится в этом объекте. Вот первое сообщение и второе сообщение.
В следующем примере мы разберем такое интересное поведение, которое можно задать с помощью кода в языке JavaScript. В большинстве языков программирования, если мы создаем объект, то этот объект у нас всегда имеет постоянное поведение. А от в случае с языком JavaScript мы можем создать объект у которого количество методов или поведений этих методов будет зависеть от каких-то внешних факторов. Например, сейчас в объекте, который мы будем создавать функции-конструктора в зависимости от входящего параметра будет меняться поведение задающееся через метод. Вот на 7 строке мы создаем переменную friend. Кстати – это еще одни из способов созданий функций-конструкторов. Мы создаем переменную, и этой переменной присваиваем функцию-литерал. Так как эта функция литерал в себе содержит ключевое слово this через которое мы добавляем новые свойства, то можно понять, что эта функция-литерал является конструктором. Эта функция-литерал в будущем будет доступна через переменную friend. То есть на строке 32 мы эту функцию будем запускать. С помощью ключевого слова new будем вызывать функцию и создавать новый объект. То есть этот вариант использования конструкторов тоже приемлемый и никаких ошибок в этом коде нет. На строке 9, когда функция-конструктор будет запущена, первое что мы сделаем – добавим свойство mood. Настроение вот нашего виртуального друга. На 12 строке мы проверим, если свойство mood текущего объекта nice – это означает, что мы добавляем для текущего объекта метод talk, который выводит сообщение «Hello what's up?». Если же настроение объекта будет bad, то тогда функция talk будет в себе содержать уже совсем другой метод. Точнее метод talk будет содержать в себе совсем другую функцию. И на строке 25 во всех остальных случаях, если mood был задан не корректно, то тогда будет выводится сообщение «Hi». То есть вот так будет работать метод. Теперь на 31 строке мы получаем от пользователя ввод и на строчке 32 создаем переменную с именем David. Используем конструктор, для того чтобы создать new Friend(), по сути типа friend. И указываем для этого объекта в качестве параметра значение, которое было получено выше. И в зависимости от этого значения метод talk, который будет находится в объекте David будет по-разному работать. То есть если мы будем помещать сюда значение nice, то метод talk будет выводить – «Привет! Как дела?» Если значение bad, метод talk у этого объекта будет иметь уже совсем другое поведение. Вот такое интересное динамичное поведение языка JavaScript. Создаваемые объекты могут в зависимости от параметров, в зависимости от каких-то лишних факторов менять свое количество методов или поведение методов. Давайте запустим и проверим. Вводим nice и объект говорит: «Hello! What’s up?» Вводим bad и объект выводит совсем другое сообщение.
В завершение примеров использования пользовательских конструкторов разберем с вами такие понятия как свойства экземпляра, методы экземпляра и понятие свойства функции-конструктора и методы конструктора. На строчке 7 мы создаем функцию-конструктор Point(). Эта функция в объект добавляет два свойства х и у. И 14 строка – в объект добавляется один метод print(), который выводит на экран сообщение в круглых скобочках значение х и у через запятую. Вот это – функция-конструктор, которая создает объекты. Каждый объект, который будет создан с помощью функции конструктора Point() будет содержать два свойства и один метод. Когда свойства и методы привязаны к какому-то конкретному объекту, к экземпляру объекта, то мы можем сказать, что это у нас свойства экземпляра и методы экземпляра. Получается, что если мы имеем 10 объектов, у каждого объекта будет свое свойство х, свое свойство у, и свой метод Print(). Но так же есть возможность сделать свойства и методы, которые являются общими, которые не принадлежат каждому объекту по-отдельности, а являются какими-то общими. На строке 20 мы берем функцию-конструктор Point(). То есть обращается не к какому-то конкретному объекту, а к самой функции, потому что функции в языке JavaScript – это тоже объекты. Вот мы берем функцию Point() и указываем, что в этой функции появляются свойства maxPointCount. И в этом свойстве находится значение 100. И вот это значение 100 оно у нас уже не принадлежит какому-то конкретному экземпляру, оно у нас общее. И по этому оно называется свойством функции-конструктора. Если вы работали с другими языками программирования, например с C#, то это по сути является аналогом статического свойства. На 23 строке мы для функции-конструктора Point() добавляем метод getOrigin(). И по сути, этот метод тоже является общим. Он не находится в конкретном объекте, он принадлежит сразу всем объектам. Любой может к нему обратится. Задача метода getOrigin() вернуть нам новый объект, созданный функцией Point() с координатами 0, 0. То есть вернуть точку начала системы координат. Это задача метода getOrigin(). Нет смысла этот метод помещать в каждый объект Point, потому что функция, которая возвращает начало системы координат она у всех объектов будет работать абсолютно одинаково. Вот чтобы не тратить место на то, чтобы дублировать ее в каждом объекте. Мы эту функцию выносим в конструктор, чтобы она была общей. Теперь как происходит обращение к функциям, методам и свойствам экземпляра и к методам и свойствам методов-конструкторов. На 28 строке создаются переменные p1 в которую записывается объект, созданный через функцию-конструктор Point(). У этого объекта появляются свойства и методы, экземплярные свойства и методы. Видите? Мы можем обратится на объекте к переменной х, у и вызвать метод print() и увидеть сообщение, что координаты точки р1 это 300 и 400. Также можем создать вторую точку с координатами 100, 200 и вызвав метод print() увидеть, что он выводит данные, которые хранятся вот в этом экземпляре. То есть вот мы сейчас пользуемся экземпярными свойствами и методами. Если же мы пользуемся методами и свойствами, которые находятся в функции-конструкторе, то вызов их будет происходить вот в таком вот ключе. Мы обращаемся к конструктору, мы обращаемся к свойству, и выводим значение. Вот как происходит изменение свойства. Для того, чтобы вызвать метод, мы тоже обращаемся к конструктору, вызываем метод. Вы помните, наш метод возвращает объект Point со значениями координат 0, 0. Вот мы этим выражением получаем Point с координатами 0, 0. И сразу же на нем вызываем метод print(). Эти координаты отобразились в документе. Вот мы видим результат. В этой части урока мы рассмотрели с вами как можно используя собственные конструкторы создать что-то на подобии типов данных. Сделать так, чтобы по какому-то шаблону можно было создавать несколько объектов, имеющих одинаковые свойства и одинаковые методы.
Мы дошли до той части урока, где будут рассматриваться прототипы. Перед тем, как давать определение прототипу давайте рассмотрим простой пример использования функции конструктора. А потом разберемся как с помощью прототипа можно решить проблему, которая возникает при работе с этой функцией. На строке 8 создается функция-конструктор Rectangle(). C ее помощью мы будем создавать в своем приложении прямоугольники. Каждый прямоугольник будет состоять из двух свойств: ширина и высота. И также в каждом прямоугольнике будет хранится метод getArea(), задача которого вернуть площадь текущего прямоугольника. В принципе, абсолютно нормальная функция-конструктор. Ничего плохого в этом коде нет. На строчке 21 мы создаем переменную rect с помощью функции-конструктора Rectangle(), записываем в эту переменную объект. Задаем этому объекту значение свойства width = 100, а для height значение 50. И на 22 строке выводим в тело документа информацию о том, какая площадь у этого прямоугольника. Выводим значение, который возвращает метод getArea(). В этом случае 5000. Сейчас этот код абсолютно нормальный и работает корректно. Но мы бы могли его оптимизировать. В чем заключается недостаток этой функции конструктора? Представьте, что мы в своем сценарии сделаем 10 переменных и зададим этим переменным объекты, созданные с помощью функции-конструктора Rectangle(). В таком случае у нас в памяти появится 10 объектов и каждый объект в себе будет содержать три свойства. Свойства width и height, и свойство, которое указывает на функцию. То есть мы будем хранить метод. Хранить свойства в каждом объекте будет оправдано. Свойства height и width должны присутствовать в каждом объекте, и у каждого объекта должны быть свои свойства, потому что от объекта к объекту значение ширины и высоты могут манятся. Но вот функциональность, которая позволяет посчитать площадь, она одинаковая для любого из 10 созданных объектов. И получается, что бессмысленно эту функциональность хранит непосредственно в объекте, то есть не правильно будет метод getArea создавать как метод экземпляра. Потому что в таком случае мы просто лишний раз будем тратить оперативную память, тратить место на то, чтобы дублировать одну и ту же функциональность из объекта к объекту. Если наших объектов еще будет больше чем 10, то соответственно мы еще больше будем тратить места для того, чтобы хранить функциональность. Давайте сейчас посмотри на презентацию, а потом плавно перейдем к прототипам. Видите, что на четвертом слайде у нас отображается тот кусочек кода, который мы только что разобрали, и вот на диаграмме показано, что произойдет, если мы выполним создание двух объектов Rectangle(). Мы с помощью функции конструктора создадим два объекта и в каждом объекте будут свои свойства width и height и также свой метод area, который будет просто повторятся. И вот эта вот часть наших объектов, она и есть неоптимальной частью. Мы сейчас хотим избавится от дублирования методов. Также на этой диаграмме вы видите, что функция-конструктор она связана с еще какой-то функцией с сущность, которую мы с вами еще не разбирали. Каждая функция-конструктор связана с прототипом. И вот сейчас мы с вами подробней разберем прототип и обсудим как может прототип решить проблему с этим дублирование методов. Давайте подробнее с вами разберем, что такое прототипы. На прошлых занятиях, когда мы разбирали функции, мы говорили, что в языке JavaScript функция – это тип данных. То есть по сути создавая функцию мы создаем объект, в котором хранится поведение. Если брать большинство языков программирования, то функции – это синтаксические единицы, позволяющие выделить блок кода для повторного использования. А вот в случае с языком JavaScript функция – это объект, сущность, которая в себе содержит поведение и так же содержит ряд свойств. Давайте посмотрим, что происходит в коде, когда мы определяем функцию. Откроем сейчас Paint и попробуем схематически указать, что будет происходить, если мы создадим функцию конструктор. Вот мы определяем функцию, такую же как в наших примерах. С помощью этой функции-конструктора мы собираемся создавать объекты состоящие из двух свойств: из свойства ширина, и из свойства высота. Как только вы определяете функцию, вместе с этой функцией появляется еще один объект, который будет у нас называться прототипом. Как только создается функция и создается этот объект у нас появляются ссылки. У функции свойство, у каждой функции есть такое свойство, prototype, сейчас мы это свойство в примерах посмотрим. А каждый прототип, который связан с функцией содержит в себе свойство constructor. И вот получается, что эта созданная функция она у нас без нашего вмешательства, это происходит автоматически, это свойство функция-конструктор связывает с прототипом, а свойство-конструктор наоборот – прототип связывает обратно с функцией. То есть тут у них есть такие две обратные ссылки. Каждая созданная функция содержит в себе ссылку на прототип, а прототип в свою очередь содержит ссылку на конструктор. Мы сейчас с вами увидим в примерах как это поможет нам избавится от дублирования методов, которые мы рассмотрели в первом примере. Вот значит создали функцию-конструктор. Теперь давайте разберем что произойдет в коде, когда мы выполним вот такие вот операции. Когда мы создадим переменную var a и вызовем функцию конструктор для этой переменной и точно также создадим переменную var b. Когда мы используем функции конструкторы, по сути мы создаем два новых объекта. У каждого будет свое свойство width, свое свойство height. Если смотреть на функцию-конструктор, то здесь тоже самое: высота, ширина. То есть это будет у нас объект а, а это будет у нас объект b. Но кроме того, что мы создаем объекты вот здесь в этом примере. Также здесь неявно это ключевое слово new кроме того, что создает новый объект и функцией-конструктором заполняет этот объект свойствами, также ключевое слово new устанавливает еще ссылку. Специальное спрятанное свойство, которое называется __proto. То есть в каждом объекте в JavaScript коде есть такое свойство, которое связывает этот объект с его прототипом. Когда мы функцией-конструктором создаем объект, в этом объекте автоматически свойство __proto ссылается, устанавливается ссылка на прототип, с которым была связана функция-конструктор. То есть и в объекте а, и в объекте b есть свойство __proto, и это свойство оно у нас ведет на наш прототип. У двух этих объектов сюда ведут ссылки. Сейчас мы с вами увидим, какое это нам дает преимущество. Давайте мы вернемся в Visual Studio и посмотрим на второй пример.
Во втором примере функция-конструктор изменилась. По сути она теперь создает объект такой же, как на нашей схеме. Вот объект с двумя свойствами width и height. Но смотрите, что мы делаем после функции-конструктора. На строке мы берем функцию-конструктор и с помощью свойства прототип, вызванной на функции-конструкторе, мы получаем доступ к прототипу функции, к пустому объекту. И говорим что в этом объекте должен появится метод getArea(), который будет возвращать площадь прямоугольника. Выполняя вот такую операцию мы вот здесь в прототипе устанавливаем свойство, и в этом свойстве устанавливаем функцию, которая выполняет какие-то операции. И теперь если мы в коде напишем, что обращаемся к объекту а и вызываем на объекте а метод getArea(), что у нас происходит? Интерпретатор идет в объект а и пытается здесь найти метод здесь. Здесь метода нету. Поэтому интерпретатор автоматически берет свойство __proto, ссылку на прототип. И по этой ссылке поднимается к прототипу и начинает искать метод getArea() в этом прототипе. Если метод getArea здесь будет найден, мы его запустим. Если здесь метода getArea не будет, и нас будет еще один прототип, можно сделать, чтобы этот прототип, имел ссылку на другой прототип, на другой объект, находящийся выше иерархии. То есть мы можем с этого прототипа перепрыгнуть на более высокий прототип и на следующий прототип и взять функциональность в нем, или получить ошибку, если метод нигде и не будет найден. В нашем случае мы будем делать только один переход: из объекта перепрыгивать в прототип и в прототипе искать метод. Если его здесь не будет, тогда у нас будет ошибка на этапе выполнения. То же самое будет происходить, если мы будем обращаться к объекту b и вызовем на нем метод getArea(). Точно так же мы сначала попытаемся найти метод в объекте b, если его здесь не будет, мы возьмём ссылку на прототип, перейдем в прототип и попытаемся вызвать метод уже на прототипе. Давайте сейчас проверим, как это работает у нас в примере. Вот прототип мы определили, точнее метод для прототипы мы определили и на строке 20 создали объект, в котором хранится по сути только два свойства: ширина и высота. А на 21 строчке на этом же объекте мы вызываем метод getArea(). Если сейчас мы метод getArea() не находим в самом объекте, поднимаемся выше к прототипу. Прототип мы берем из функции-конструктор, которым был создан объект rect. Объект кусе был создан функцией-конструктором Rectangle(). Соответственно, прототип который находится в этом объекте, он является функцией-конструктором Rectangle(). Вот этот прототип мы устанавливали. Вот в прототипе есть метод getArea(). По этому, обращаясь к объекту мы по сути поднимаемся к прототипу и выполняем вот этот вот участок кода. И при этом, обратите внимание, если в прототипе вы используете ключевое слово this, это ключевое слово всегда ссылается на тот объект, с которым вы работали, когда запускали метод. То есть this – это не ссылка на прототип. This – это будет ссылка на тот объект, на котором был произведен вызов метода. Поэтому этот метод getArea() получает именно площадь прямоугольника, объекта rect, а не именно площадь самого прототипа. Если мы запустим пример, то увидим, что пример работает точно так же как и предыдущий пример, в котором мы использовали метод как часть самого объекта rectangle. Но в этом примере мы теперь экономим память. Если в предыдущем примере у нас была проблема, из-за того, что метод дублировался во всех объектах. То в этом примере метод у нас есть только один в прототипе. И любой объект, пусть их будет сотня, он будет обращаться к прототипу и брать метод из прототипа.
Давайте разберем еще один пример, чтобы лучше понять, как работают прототипы. В третьем примере мы определяем такую же функцию-конструктор, как и прошлом примере. Функция будет создавать объект с двумя свойствами. Для прототипа устанавливаем метод getArea(). И так же для прототипа на 19 строке мы устанавливаем свойство name. Получается, что сейчас у нас в прототипе появляется свойство name со значением Rectangle. И теперь если мы будем обращаться к объекту а, b или к другому любому объекту созданному через функцию-конструктор. Если мы на объекте будем вызывать свойство name, то по прототипу, по ссылке __proto мы будем подниматься к прототипу и обращаться к тому свойству, которое лежит в прототипе. Дальше в коде это у нас показано. На строке 21, 22 мы создаем объекты rectangle, потом вызываем методы, которые считают нам площадь, выводим площади двух объектов, и потом проверяем значения свойств name двух объектов, и первого и второго. Давайте запустим и проверим. Вот мы вывели площади, вот мы увидели свойства rectangle. Теперь посмотрите что происходит дальше. На строке 32 мы берем объект rect1 и обращаемся к свойству name этого объекта. Указываем, что свойство name теперь равно First rectangle. Что происходит, когда мы выполняем вот эту операцию? Мы по сути сейчас обращаемся не к прототипу а к объекту rect1 и просим, чтобы свойство name изменилось у него. Данная операция она не меняет значение, которое находится в прототипе. Мы по сути сейчас в один из объектов, к которому мы обратились, мы установили, что name = First rectangle. То есть если мы обращаемся к объекту и устанавливаем свойства или методы, которые существуют в прототипе, то мы просто перекрываем тот метод или свойство, которые лежат в прототипе. Мы не меняем сам прототип. Если мы сейчас обратимся к объекту а и попросим вывести значение свойства name, то так как свойство name интерпретатор сразу же найдет в свойстве а, интерпретатор не будет подниматься выше к прототипу. Интерпретатор возьмет свойство непосредственно у объекта. Но если мы будем работать с другими объектами, для которых мы свойства name не меняли, то другие объекты заставят интерпретатор подняться к прототипу и прочитать свойство name у прототипа. Поэтому в нашем примере, если мы сейчас обращаемся к rect1.name мы видим новое свойство name, видим First rectangle, А вот rect2.name возвращает нам значение rectangle, которое лежит в прототипе. Потому что по сути у rect2 свойства name не существует. Интерпретатору приходится подняться выше, чтобы получить доступ к прототипу. Ну и в браузере это видно. Вот мы второй раз выводя данное свойство name мы видим First rectangle и просто rectangle. А вот если мы с вами раскомментируем строку 33 и закомментируем 32, то смотрите, что здесь происходит. Мы меняем уже не объект какой-то конкретный, мы меняем значение прототипа. То есть меняем данные, которые находятся у нас вот здесь, которые находятся в прототипе. И получается, что в таком случае, если мы меняем прототип, то меняются сразу же все объекты, которые были созданы от этого прототипа. И вот при таком коде, при изменении прототипа видите что происходит с свойствами name, когда мы к ним обращаемся после смены прототипа. Оба свойства выводят одинаковое значение.
И давайте разберем последний пример из примера с прототипами. Использование свойств конструктора. В этом примере мы разберем как можно получить информацию о том, с помощью какого конструктора был создан объект. В начале примера мы создаем несколько переменных. И переменные создаются с использованием различных функций конструкторов. Вначале мы создаем массив используя функцию Array(), потом создаем дату, используя функцию Date(), потом переменную maString, которая используется со строковым конструктором, пустой объект создаем. Создаем функцию, через конструктор Function(), также мы определяем свой собственный конструктор MyCtor(), который создает объект с двумя свойствами. И создаем этот объект используя только что определенную функцию-конструктор. Далее на строке 27 мы определяем функцию, с помощью которой мы будем выводить информацию о том какой объект каким конструктором был создан. На строке 28 мы выводим в начало в документ имя объекта, которое попадает к нам в параметрах, а потом выводим свойство конструктор, которое берется на объекте, который передается через первый аргумент. Вот на строке 32, например, мы вызываем функцию showCtor(). Обращаемся к этой функции. Первым параметром передаем myArray, переменную, которая была проинициализирована на 12 строке, а вторым параметром передаем текст, который мы должны показать в документ. Получается что на 28 строчке будет выводится сообщение: «Конструктор объекта myArray это obj.constructor». obj будет в себе содержать по сути ссылку на объект myArray. А конструктор, это свойство будет в себе содержать ссылку на функцию, которую мы использовали на 12 строке, когда инициализировали переменную myArray. То же самое мы сделаем на 33 строке, на 34 и далее. То есть мы покажем какой функцией-конструктором был создан объект, каждый из объектов. Давайте сейчас запустим этот пример, и вот что мы видим. Что переменная myArray была создана с помощью функции Array(), и мы не видим непосредственно код, который использовался для создания, потому что это native code браузера, родной код самого браузера. То же самое касается функции Date(), String(), Object() и Function(). Все эти функции для нас закрыты. А вот когда мы выводили на 37 строке информацию о конструкторе объекта myCtorObject, мы видим именно полностью функциональность, которая была определена у нас в сценарии, мы видим непосредственно конструктор и его тело. Вот когда мы обращались к свойству на строке 28, вот мы получили ссылку на непосредственно всю функцию. Так же с помощью свойства constructor мы по сути можем создавать объекты того же типа, что и какой-то существующий объект. Например на 41 строке мы создаем переменную someNewObject, а потом вызываем new, используем ключевое слово new и вызываем функцию-конструктор, которой был создан объект myDate. То есть мы берем myDate, просим, чтобы нам вернули конструктор, которым был создан myDate, и этот конструктор запускаем. Если посмотреть выше, то myDate был создан с помощью конструктора Date(). По этому по сути на строке 41 мы запускаем конструктор Date(), создаем сегодняшнюю дату и время, и результат записываем в эту переменную, отображая переменную пользователю. В этой части урока мы рассмотрели с вами что такое прототип. Мы узнали с вами, что каждый раз когда мы создаем функцию, эта функция самостоятельно связывается с прототипом, а прототип обратно связывается с функцией-конструктором. И каждый раз, когда мы с помощью функции-конструктора создаем новый объект, то в каждом новом объекте появляется ссылка на прототип. Если мы работаем с эти новым объектом, созданным объектом, то обращаясь к свойствам или методам этого объекта, если свойства и методы не находятся в самом объекте, то интерпретатор пытается найти их в прототипе.
В таких языках программирования, как C# или например Java любой класс, системный или пользовательский наследуется от базового типа. От типа Object. И это означает, что все типы данных, которые есть в языке программирования имеют общую базовую функциональность, которая по наследству перешла от класса Object. В языке JavaSctipt происходит тоже самое. Хоть мы и не можем сказать, что у нас здесь есть полноценное наследование, мы не можем сказать, что у нас есть типы данных, которые мы создаем, но все-таки мы создавая любой объект, создавая свою собственную функцию-конструктор, либо используя существующую функцию, создавая объект мы делаем так, что у этого объекта появляется ряд каких-то базовых методов. Везде, во всем JavaScipt коде вы можете на объекте вызвать один из нескольких методов, которые мы сейчас с вами рассмотрим. Все объекты, которые создаются в языке JavaScript наследуются от Object и наша сейчас с вами задача – посмотреть, понять что именно есть в Object и для чего каждый метод используется. Для того, чтобы убедится, что у нас действительно существует наследование, давайте сейчас запустим первый пример и посмотри как он работает под отладчиком. Нажмем в Chrome F12. Откроем исходный код текущего документа и поставим breakpoint на той строчке, где создается объект. Вот например на 21. Поставим breakpoint, обновим документ. И сейчас, если мы наведем на rect1, мы видим, что пока rect1 – undefined, потому что по сути конструктор не выполнился. Если мы нажмем на F10 или Step Over, то мы перепрыгнем на следующую строчку, и наведя на rect1 мы увидим, что rect1 проинициализировался. Это объект, состоящий из свойства height и width, и свойства __proto. Свойство __proto содержит в себе объект, который состоит из свойства-конструктора, метода getArea() и метода toString(). Вот эти два метода getArea() и toSring() мы разработали самостоятельно. Сейчас посмотри, что они делают. Но обратите внимание, что даже вот этот прототип, он содержит в себе еще одну ссылку на прототип. То есть у прототипа Rectangle есть еще один прототип, который является объектом. Если мы посмотри вот в этот прототип, то мы увидим, что здесь есть ряд методов, которые сейчас мы разберем, мы не будем рассматривать все методы. Чаще всего используется метод toString(), valueOff() и hasOwnProperty(). Сейчас эти методы мы с вами разберем в следующих примерах. Что означает наличие вот этого объекта? Мы по сути наследование здесь не используем. Мы используем здесь прототипы. Если в других языках программирования наследование подразумевает копирование какой-то функциональности из родительского класса, то по сути здесь мы просто указываем, что один объект связан с другим объектом посредством прототипов. То есть у одного объекта есть прототип в виде другого объекта. И вот Object является прототипом всех объектов, которые существуют в языке JavaScript. И получается, что если мы начнем обращаться к rectangle и скажем, что хотим прочитать значение свойства width. Вот свойство width? Мы его найдём прямо в rectangle. Если мы скажем, что хотим вызвать метод getArea(). В rectangle мы getArea не найдем, поэтому пойдем в прототип и найдем getArea в прототипе. Но если мы скажем, что на rectangle хотим вызвать метод hasOwnProperty(), то этого метода нет ни у rectangle, нету у прямого прототипа rectangle, но за то когда интерпретатор будет искать методу этого прототипа и не найдет его, интерпретатор перейдет к следующему прототипу, к прототипу Object. А у прототипа Object есть метод hasOwnProperty(). То есть этот метод будет запущен, если мы обратимся к нему на любом объекте. Так как Object является прототипом любого объекта в JavaScript, то на любом объекте вы можете вызвать hasOwnPropety() и получить функциональность, которая заложена в этом методе. Давайте мы сейчас разберем какие методы есть в Object о которых вы обязательно должны знать.
В первом примере мы разбираем метод toString(). C его помощью мы можем превратить объект в строковое представление. На 7 строке создается конструктор, создающий rectangle с двумя свойствами: ширина и высота. Для прототипа конструктора мы устанавливаем метод getArea(). Этот метод будет возвращать площадь, как и в предыдущих примерах. На 17 строке мы указываем, что у прототипа появляется метод toString(), и задача этого метода вывести вот такое вот значение: прямоугольник W и значение высоты, H и значение ширины. То есть если мы захотим сделать так, чтобы у нас объект превратился в строку, и эту строку было удобно вывести в пользовательский интерфейс, мы можем переопределить метод toString(). Не совсем переопределить, а заместить его, сделать так, чтобы метод toString() находился у прототипа rectangle. Это приведет к тому, что когда мы начнем обращаться, вот если мы начнем обращаться к rectangle и вызывать на rectangle toString() что произойдет? У rectangle метода нету, мы пойдем в прототип rectangle и найдем метод toString(), потому что вот он определен в прототипе. Но если бы мы не написали toString() для прототипа, то в таком случае мы бы поднялись еще выше, точнее спустились к прототипу вот этому, и нашли бы метод toString() вот здесь, он определен в Object. Поэтому, если мы вызываем toString() на каком либо объекте в JavaScript коде мы всегда получим какой-то результат, у нас всегда есть метод toString(), потому что он находится в объекте. Но в некоторых объектах метод toString может быть замещен. Мы можем создать у этого прототипа можем создать метод toString() и тогда в случае с использованием объекта rectangle мы не будем доходить до toString(), который заложен в Object, мы будем пользоваться toString() вот этим. И давайте сейчас посмотрим, что получится в результате. Когда мы создали два объекта и на 4 и на 5 строке выводим эти объекты на экран, смотрите что происходит. Когда мы вызываем rect1 +”
”, здесь неявно срабатывает метод toString(), и получается, что мы в тело документа выводим сообщение, вот это выражение оно превратится в Прямоугольник с шириной 100 и высотой 200. А на строке 25 мы вызываем rect2.toString() и получаем тоже самое, что и на предыдущей строке. Давайте посмотрим как это выглядит. Вот у нас прямоугольник, его высота и ширина, и второй прямоугольник. Вот и получается, что с помощью метода toString() мы сделали возможным превращать объект в строковое представление.
Следующий метод, который есть в Object – это метод valueOf(). C помощью метода valueOf() мы можем получить значение объекта. Если toString() превращает объект в строковое представление, то valueOf() возвращает простое значение, которое представляет из себя объект. Вот допустим наш прямоугольник, он является объектом, у которого есть высота, ширина, возможно еще какие-то параметры. Но значение прямоугольника, это его площадь. Если, например, взять объект string. У объекта String есть методы, чтобы разбить строку по каким-то символам на массив, сделать все символы строки в верхнем или в нижнем регистре. То есть объект string содержит в себе много различного поведения, каких-то методов. Но если мы вызовем на объекте string valueOf() – мы получим просто строковое значение, которое хранится в объекте. Вот задача метода valueOf() вернуть значение, представляющие сложный объект. И вот мы сейчас определили такую же функцию-конструктор, как и в прошлом примере, определили метод getArea() в прототипе, метод toString() перекрыли, метод, который лежит в Object. И на 22 строке перекрыли метод valueOf(). Этот метод есть в Object, а это значит, что он есть во всех объектах. И в этом методе мы на 23 строчке указываем, что если его кто-нибудь вызовет, то в ответ получит площадь текущего объекта. Теперь смотрите, что происходит после того, как мы создаем два объекта с помощью конструктора rectangle. Когда на 29 строке мы вызываем rect1 + “br /”, на самом деле на объекте rect1 вызывается не метод toString(), а метод valueOf(). Просто если метод valurOf() не реализован, то тогда, при вызове метода valueOf() вызывается метод toString(). Поэтому при отсутствии метода valueOf() он работает точно также как метод toString(). Но сейчас мы метод valueOf() переопределили на этой строке. Поэтому когда на строке 29 мы выводим rect1 + “br /”, мы получаем значение площади. На 30 строке, когда мы явно вызываем метод toString() у нас выводится сообщение, которое мы сформировали на 18 строке. На строке 34, 35 мы явно вызываем методы valueOf() и видим площади rect1 и rect2. И так же методы valueOf() вызываются неявно, когда мы выполняем операцию сложения и берем rect2 + rect1. То есть выполняя сложение мы также вызываем valueOf(), получаем простые значения из сложных объектов, и выполняем действия над этими простыми значениями. Вот вы видите, что на 29 строке, когда мы вызвали write и указали конкатенацию rect1 и «
». Вот у нас вывелась площадь. Хотя в предыдущем примере, когда valueOf() мы не реализовывали, то метод valueOf(), когда он вызывался, он взял функциональность, которая находилась в методе toString(). Далее явно вызываем toString() и видим значение, которое возвращает метод toString(). А вот на строке 34, 35, когда мы явно вызывали valueOf() мы увидели площадь первого и второго прямоугольника, а на 36 строке, когда rect2 + rect1, выполнили сложение, неявно вызвались valueOf() и мы увидели сумму площадей двух прямоугольников.
Следующий метод, который есть в Object – это метод hasOwnProperty(). C его помощью мы можем проверить существует ли свойство в конкретно этом объекте, или оно находится где-то выше у прототипов. Сейчас функция-конструктор создает объект у которого есть только два свойства: это width и height. Далее мы указываем, что для прототипа rectangle мы устанавливаем метод getArea() и устанавливаем свойство name. Создаем один объект с помощью конструктора и теперь тестируем использование метода hasOwnProperty(). На 21 строке мы отображаем rect1.hasOwnProperty(“width”) . Метод hasOwnProperty() возвращает значение true, если свойство width находится в объекте rect1. Сейчас действительно в объекте rect1 есть свойство. Вот оно определено. На 22 строке мы вызываем rect1.hasOwnProperty(“name”). По сути, если мы попытаемся обратится к свойству name на объекте rect1 – ошибки не будет. Мы прочитаем свойство name, потому что мы сможем подняться к прототипу и найти это свойство у прототипа. Но это свойство прототипа а не конкретного экземпляра rect1. Поэтому на 22 строчке hasOwnProperty() возвращает нам значение false. Давайте запустим и проверим. Видите, width существует, с свойство name у объекта не существует. Это свойство прототипа. В прошлом курсе мы также разбирали ключевое слово in. Используя это ключевое слово вот в таком ключе, мы сначала устанавливаем имя свойства, потом ключевое слово in, а потом объект. И тем самым проверяем существует ли в этом объекте свойство width. И существует ли в объекте свойство name. Когда мы используем ключевое слово in, то поиск происходит не только в текущем объекте, но и во всех прототипах. То есть полностью производится проход по цепочке прототипов. Если где-нибудь в каком-то из прототипов свойство будет найдено, то тогда мы получим значение true. И тогда вы видите, что и width и name при использовании ключевого слова in вернули нам результат true.
Следующие два метода, которые мы рассмотрим в примерах они не являются частью Object, но их часто создают по аналогии с другими языками программирования. Первый метод, это метод equals(), который используется для того, чтобы сравнить два объекта на равенство. Сейчас первая часть примера не отличается от предыдущих. Мы создаем объект с такими же методами, как и ранее с двумя свойствами: высота и ширина. Мы сейчас хотим сделать так, чтобы объекты можно было сравнивать. Если у нас есть два прямоугольника с одинаковой высотой и шириной, чтобы эти прямоугольники считались равными. Вот на 27, 28 строчке мы создали два прямоугольника. Одинаковая ширина и высота. На строке 31 мы создали переменную res и в эту переменную записали выражение rect1 = rect2. То есть по сути сейчас мы должны были бы получить значение true. В переменную res записать результат, который будет равен true и вывести этот результат. Если мы сейчас запустим пример, то увидим, что при сравнении rect1 и rect2 мы получили результат false. А на строке 34, когда мы создали переменную rect3 и поместили в эту переменную rect1, значения из этой переменной, а потом на 35 строке сделали сравнение rect3 и rect1, вот в такой ситуации мы получили true. Почему так произошло? Когда на строке 27 мы написали new Rectangle() мы в памяти создали объект. У этого объекта появилось два свойства. И в переменную rect1 мы записали ссылку на этот объект. То есть по сути в переменной rect1 хранится адрес, например 0xA1. Вот это адрес первого объекта. Когда мы написали на 28 строке new Rectangle(), то мы создали еще один объект, у которого появилось тоже два свойства. Значение свойств и этого и этого объекта абсолютно одинаковые. Но в переменную rect2 у нас записалась ссылка уже на этот объект и адрес этого объекта у нас уже другой, например 0xB1. И получается, что на строке 31, когда мы выполняем это сравнение, на самом деле мы сравниваем не сами объекты, не их содержимое, а сравниваем два вот этих адреса 0хА1 и 0хВ1. То есть у нас есть два разных адреса, и получается, что сравнение дает нам результат false, потому что мы сравнили два разных адреса. Мы не сравнивали само наполнение объекта. А вот на 34 строчке, когда мы написали rect3 == rect1, то мы по сути значение адреса, которое находилось в rect1 скопировали в переменную rect3. И получается, что rect3 сейчас тоже ссылается на объект, на который ссылалась переменная rect1. Получается, что в rect3 тоже находится адрес 0хА1, как и в переменной rect1. Поэтому, вот когда мы делаем здесь сравнение, мы в результате получаем значение true, т.к. переменные ведут на один и тот же объект в оперативной памяти. Если мы хотим в своих сценариях сравнивать именно объекты, не ссылки а именно объекты, в таком случае мы можем разработать специальный метод и назвать его equals. Во многих языках программирования базовый класс Object предоставляет этот метод для сравнения объектов. Вот как мы этот метод реализовываем. На 27 строке мы в прототип помещаем метод equals. Этот метод принимает один параметр. Этим параметром будет являться второй объект, с которым мы сравниваем текущий объект. На строчке 28 мы ставим вот такое условие. Мы проверяем если this.width, то есть ширина текущего объекта совпадает с шириной объекта который пришел в параметрах, и если то же самое происходит с высотой. Если высота текущего и полученного в параметрах одинаковая, то тогда мы возвращаем результат true. Метод equal скажет нам true. Скажет что у нас объекты равны. Или если у объектов какое-то из свойств не совпадет, 31 строчка, мы вернем значение false. И вот мы проверяем два объекта. Создаем rect1 и rect2, заполняем эти объекты одинаковыми значениями и на 39 строке в переменную res записываем rect1.equals(rect2). Вот мы сравниваем два этих объекта. Если они одинаковые, то в переменную res будет записано значение true. Вот в такой ситуации, видите, оба выражения, когда мы сравнивали два разных объекта и когда мы сравнивали одинаковые ссылки мы видели на экране результат true. Кстати, этот метод можно было немножко упростить, можно убрать вот эти лишние условия и просто написать, что мы возвращаем результат, return и вот это выражение. То есть получается, что мы возьмем и сравним это, выполним эту проверку и эту проверку. В результате с помощью оператора и получим значение либо true, либо false и вот этот результат мы вернем. Поэтому условие оно здесь по сути не обязательное. Ну для того, чтобы было проще понять работу этого примера, вот мы написали в таком виде метод equals.
И еще один пример с реализацией метода compareTo(). Этот метод не является частью Object, но мы его можем добавить если в приложении стоит задача сравнивать два объекта. Проверять какой объект больше, а какой меньше. Есть несколько способов сравнения объектов. Первый способ – это просто определить метод valueOf(). Вот сейчас в нашем прямоугольнике метод valueOf() возвращает площадь. И смотрите, как мы можем использовать метод valueOf() при сравнении объектов. Вот на 59 строке, когда мы создаем условие rect1 больше rect2. Вот здесь у нас неявно вызывается метод valueOf(), который возвращает целочисленное значение, в итоге мы видим результат, мы видим по сути сравнение двух целочисленных переменных. То же самое происходит на 62 строке, на 65 и на последующих строках. Видите, что сейчас rect1 и rect2 они у нас одинаковые. Размеры высота и ширина совпадают. Но смотрите что происходит, когда мы используем метод valueOf() он у нас корректно срабатывает, если мы выполняем сравнение меньше равно, больше равно, меньше, больше, но при этом выражение на 71 строчке, оно у нас почему-то не сработало. Хотя значения равны. Но на строчке 74 выражение «не равно» оно у нас выполнилось и вывело сообщение, что rect1 не равен rect2. Почему так произошло? Потому что оператор «равно» и «не равно» проверяет по сути ссылки. Проверяет куда ведут переменные rect1 и rect2, на какие объекты в памяти. А от операторы «меньше» и «больше» они ссылки не могут проверять ссылки. Поэтому они берут преобразовывают объекты в значения вызывая метод valueOf(). Поэтому для того, чтобы правильно работали выражения на строке 71 и 74 нам необходимо добавить вызов метода valueOf() и проверять непосредственно сами значения. Сами значения объектов. Если мы так переделаем пример, то корректно отрабатывает последняя часть и rect1 равен rect2. Но что делать в тех ситуациях, когда метод valueOf() отсутствует, когда мы не можем взять объект и превратить его в какое-то простое значение. Когда объект сложный и простого представления просто не существует. В таких ситуациях мы можем реализовать метод compareTo(). И этот метод можно создать вот по такому шаблону. Когда мы определяем метод compareTo(), его лучше всего поместить в прототип, лучше всегда методы помещать в прототип, а не в конкретный объект. В методе compareTo() у нас находится такая простая функциональность. Мы берем сейчас и проверяем площадь текущего объекта и того объекта, который попал к нам в аргументах. В других ситуациях вы можете проверять ряд каких-то свойств, и после проверок, когда вы вычислили, что текущий объект больше, чем тот который попал к вам в параметрах, вы должны вернуть значения больше нуля. Единицу или больше. Если вы, сделав проверку, увидели, что текущий объект меньше, чем тот, который попал к вам в параметры, то вы должны вернуть значение меньше нуля. Например, минус один или более меньшее значение. И если у вас объект текущий, и тот который пришел в аргументах одинаковы, то тогда вы должны возвращать значение 0. Получается, что задача метода compareTo() вернуть цифру, которая будет являться соотношением между объектами. Если объект наш больший, то мы возвращаем больше нуля, если наш объект меньший, то мы возвращаем меньше нуля. Если объекты равны – возвращаем значение 0. Чтобы не создавать такие громоздкие условия мы можем поменять метод compareTo() на такое простое выражение. Мы можем просто взять площадь текущего объекта минус площадь объекта, который пришел к нам в параметрах. Если, например, площадь текущего объекта 100, а второго объекта 50. 100-50 – получается значение, которое больше нуля. Как раз то, что и нам нужно возвращать. Если площадь текущего объекта 10, а того, с которым мы производим сравнение 50. 10 – 50 = -40. Соответственно мы вернем значение меньше меня и тоже будем корректно выполнять условия работы метода compareTo(). Если вы работали с языками Java, или с языком C#, то я уверен, что вы сталкивались с таким методом. Этот метод есть в специальных интерфейсах, которые надо реализовать, чтобы объекты поддерживали сравнение. И как теперь этим методом пользоваться? Теперь он у нас возвращает просто целочисленное значение, и теперь, чтобы сравнить объекты нам надо выполнять вот такую вот операцию. Мы берем rec1.compareTo(rect2). Если сравнение этих объектов через compareTo() возвращает это значение, если это значение больше 0 – это означает, что rect1 больше чем rect2. Если мы выполняем сравнение compareTo() двух объектов, то при сравнении мы получаем меньше нуля, то это означает, что rect1 меньше чем rect2. То если в итоге мы выполняем вызов оператора compareTo() и результат используем с оператором необходимым нам и сравниваем просто со значением ноль. Тем самым мы получаем корректный ответ. Больший объект, или меньший, чем тот с которым сравниваем. Вот такой способ реализовать сравнение двух объектов. Либо мы используем метод valueOf(), либо мы используем метод compareTo().В завершение сегодняшнего урока мы разберем несколько простых техник, которые покажут такие известные всем парадигмы ООП как инкапсуляция, наследование и полиморфизм. Полноценных реализаций инкапсуляции, наследования и полиморфизма в языке JavaScript нет. Но с помощью разных уловок, разных хитростей, техник, мы можем реализовать необходимое нам поведение. Начнем мы с инкапсуляции. Инкапсуляция – это сокрытие реализации и данных объекта. Получается, что объект может прятать от внешнего мира методы или свойства, чтобы другие не могли этими методами или свойствами пользоваться. Для того, чтобы реализовать инкапсуляцию в языке JavaScript мы должны использовать вот такой подход. При создании функции-конструктора, наша функция конструктор сейчас на 11 строке и называется она MyClass(). При создании функции-конструктора мы должны определить переменную, локальную переменную этой функции и этой переменной задать значение либо функции. То есть в зависимости от того, что мы хотим создать. Если мы хотим создать закрытый метод, то переменной мы присваиваем функцию. Если хотим создать закрытое свойство, то переменной мы присваиваем значение. На первом курсе, на базовом курсе мы говорили, что в JavaScript есть две области видимости. Если глобальная область видимости и локальная область видимости, которая создается для каждой функции. Переменная MyClass будет доступна везде. В этом элементе script и в любом другом элементе script, который будет подключен к текущему документу. А вот переменная на 14 строчке privatMethod, т.к. она была создана в MyClass она будет доступна только внутри этой функции. За пределами функции MyClass она никому не видна, никто ее не может использовать. Но внутри функции MyClass к этой переменной можно без проблем обратится. Вот и получается, что мы создали переменную, которую видит только функция MyClass(). На 19 строке эта функция, она в объект добавляет publicMethod(). И в этом методе у нас находится поведение. В нем находится сообщение «Открытый метод». А потом на 21 строчке обращаемся к privateMethod(). Запускаем функцию, которая лежит на 14 строке. Вот получается, что в самой функции MyClass мы без проблем можем пользоваться этими переменными, но за пределами, когда мы уже на 25 строке создали объект с помощью конструктора MyClass(). Потом на 26 строчке вызвали publicMethod(). Это у нас код нормально сработал. Но вот на 28 строке если мы попытаемся вызвать privateMethod() – это у нас приведет к ошибке, потому что privateMethod просто не существует у объекта, который мы создали. Если publicMethod() – это у нас свойство, потому что видите какой синтаксис мы используем, то вот privateMethod() – это у нас просто локальная переменная для функции и ее никто не видит. Вот это и есть один из вариантов создания закрытых свойств, закрытых методов. Просто создание переменной, которая находится в функции конструкторе. Вот мы проверим, если мы запустим. Видите? Открытый метод, закрытый метод. Вывелось оба сообщения. Вначале сообщение на 20 строке, а на 21 мы запустили метод и вывели сообщение с 15 строки. Если мы расскомментируем 28 строку, то это ни к чему не приведет. То есть на самом деле на 28 строке у нас прекратил работать JavaScript код, и на 29 строке если бы находился еще какой-то код, этот бы код уже не выполнялся.
Второй пример покажет, как мы можем использовать наследование в языке JavaScript. Наследование – это механизм, позволяющий передать поведение и свойства от одного объекта к другому объекту. То есть от одного класса, от класса родительского к производному классу, к классу наследнику. Но так как в языке JavaScript у нас нету понятия классов, то наследование здесь происходит через подмену прототипов. Давайте посмотрим как это выглядит. На 17 строке создается конструктор Human(). Конструктор, который будет создавать объекты типа Человек. У объекта Human будет два свойства: это свойство name, куда будет записано имя человека и метод talk() с помощью которого человек будет выводить сообщение «Hello! My name is» плюс свое имя. У нас будут два производных класса. Классы, которые наследуются от класса Human. Это Student и Worker. Хот я их называю классами, это на самом деле будут только объекты созданные с помощью конструкторов Studen() и Worker(). На строке №24 Student будет содержать в себе свойство name, на 25 строчке он будет содержать свойство school. На 28 строчке Worker будет отличатся тем от Student, что у него будет свойство speciality, то есть его специальность. Теперь как происходит само наследование. На строке 34 мы создаем объект с помощью конструктора Human(). Потом на строке №38 и 39 мы указываем, что Human, который был создан выше, он является прототипом для конструктора Student() и прототипом для конструктора Worker(). То есть теперь созданные объекты с помощью конструктора Student() и Worker() они будут использовать не прототип Object, а прототип Human, который в свою очередь будет использовать прототип Object. И если мы попытаемся сейчас взять на объектах, созданных с помощью конструкторов Student() и Worker() вызвать свойство name или свойство talk. Если свойство не будет найдено внутри Student или Worker, тогда мы поднимемся выше в Human и попытаемся там найти уже какие-то необходимые нам методы или свойство. Вот мы сейчас это проверим. На строке 41 и 42 мы создаем объекты с помощью конструктора Student() и Worker(). И на 46 строчке на объекте Alex, который создался с помощью Student() вызываем метод talk(). Этого метода talk() как вы видите в Student нету. Но из-за того, что у нас есть установленный прототип мы, поднимаясь к Student не находим здесь необходимого метода и через прототип поднимаемся выше к Human, находим метод у Human и вызываем его уже в прототипе. И тоже самое мы делаем на 51 строчке, когда обращаемся к объекту Andrew. Так как Andrew был создан с помощью конструктора Worker, а конструктор использует прототип Human, то метод talk доступен для Andrew. Запускаем. Вот мы видим, что вывелось сообщение с помощью метода talk() для Student, и такое же сообщение с помощью метода talk для Worker. Есть очень много различных способов создания наследования. Это очень примитивный пример как мы можем реализовать наследование в языке JavaScript. Я рекомендую вам приобрести книгу Стояна Стефанова “JavaScript – Шаблоны”. В этой книге есть глава, в которой детально описаны какими способами вы можете реализовать наследование в языке JavaScript. Подробнее список литературы, который рекомендуется для этого курса и для более подробного изучения языка JavaScript мы рассмотрим с вами в самом конце этого урока.
Последняя концепция объектно-ориентированных языков, которую мы сейчас разберем – это полиморфизм. Но, к сожалению, полноценного полиморфизма в языке JavaScript нет. Все, что мы можем сделать, это просто замещать методы. Что такое полиморфизм? Полиморфизм – это возможность создать несколько объектов, имеющих одинаковый интерфейс, но разное поведение. То имеется ввиду, что мы создаем несколько объектов, которые имеют одинаковые методы, но при этом вызывая эти методы у нас происходит выполнение различного поведения. Если в других языках программирования у нас есть такое понятие как приведение к базовому типу. И используя полиморфизм и приведение к базовому типу мы можем делать очень шибкий и расширяемый код, то в случае с JavaScript – это не работает. У нас нет приведения к базовому типу, т.к. нет такого понятия как тип данных. На строке 13 вы видите, что задается конструктор Human(). Этот конструктор создаст объект со свойством name и методом talk(). У нас будет два производных класса – это Student и Worker. Student получит по наследству метод talk(). Видите? В Student будут свойства name и school и метод talk() который мы получим от нашего родительского класса, а вот Worker получив метод talk() заменит его на свой метод talk(). На 29 строке для Worker мы создаем свойство, которое по сути свойство, установленное через прототип. И вот в этом свойстве talk() у нас находится функция, которая будет выводить сообщение на русском языке. В базовом классе у нас «Hello! My name is …», а в производном классе у нас «Привет! Мое имя …». Ну и теперь мы создали объект Human, и в этот объект установили как прототип и для Studen и для Worker. Но Worker перекроет метод talk, который лежит в Human. И поэтому, когда мы создав два объекта начинаем этими объектами пользоваться. Когда мы вызываем talk() мы поднимаемся к прототипу и берем метод прототипа, когда мы вызываем talk() на объекте Andrew – мы переходим в объект Andrew, находим метод talk() и вызываем его непосредственно у объекта. Выше к прототипу мы уже не поднимаемся. Поэтому мы и видим, что Student выводит сообщение на английском языке, Worker выводит сообщение на русском языке. Если в вашем проекте стоит задача использовать объектно-ориентированные техники, то вместо того чтобы прибегать к написанию больших объёмов коде, которые позволят вам реализовать концепции ООП, рекомендую найти какой-нибудь фреймворк, который позволяет быстро использовать объектно-ориентированные техники. Один из фреймворков, который я могу вам порекомендовать – это фреймворк mootools. В этом фреймворке есть функция класс, которая позволяет создать класс, указать базовый класс, указать свойства, методы класса. Рекомендую ознакомится с этим фреймворком, если вы хотите использовать ООП вместе с языком JavaScript.
У нас остается еще несколько примеров из папки ООП в которых мы рассмотрим несколько ключевых слов, которые скорее всего нам пригодятся при разборе дальнейших примеров. Первое ключевое слово – это instanceof. C его помощью мы можем проверить с помощью какого конструктора был создан объект. На 11 строке создается переменная х, которая является массивом, т.к. мы вызываем конструктор Array(). На строке 12 мы устанавливаем условие: если x instanceof Array, то строка 13 – выводим alert. По сути, если дословно перевести на русский язык: если х является экземпляром Array – то вывести сообщение. И получается эта вот жёсткая проверка на принадлежность переменной к определенному виду. Сейчас запустив этот пример мы увидим, что отображается сообщение, потому что х действительно был создан с помощью конструктора Array. Но если мы скажем, что х – это Date, то при запуске примера ничего не произойдет.
Другое ключевое слово, которое тоже позволяет проверить тип объекта работает немножко по другому принципу. С помощью ключевого слова typeof мы можем узнать тип объекта но не конструктор, которым был создан объект. У нас в языке JavaScript есть следующие типы данных. Это string, number, boolean, undefined, null, и это object и function. Вот это по сути 7 типов данных, которые мы сможем с вами встретить в сценариях. Если мы создаем что-то сложное, допустим Date, массив или какой-нибудь свою собственную функцию-конструктор, то typeof для таких сложны составных объектов всегда будет возвращать значение Object. Значит ключевое слово typeof возвращает нам строку, которая хранит в себе тип объекта. И вот на 11 строке создается переменная test, куда записывается значение Hello. Получается, что переменная test – это у нас тип данных string. На 12 строке когда мы вызываем typeof test и результат отображаем в документ – мы видим сообщение string. Давайте запустим и проверим. Видите? Первое сообщение – это string. Дальше на строке 15 создаем test2 = 123. То есть это number. Выводим typeof и видим, что это действительно number. Потом test3 со значением true. Test3 – это у нас boolean. Мы в этом убеждаемся, видим, что это boolean. Теперь смотрите на 23 строке мы создаем new Array() и на 27 строке создаем new Date(). И в итоге, когда мы вводим typeof массива и typeof Date, видим, что обе переменные – это объекты. Вот мы еще раз с вами убедились, что массивы и объекты – это одно и то же самое. Массвы – это пронумерованные объекты, объекты с пронумерованными значениями. А обычные объекты, сущности в них проименованы. Они имеют имя. И на 31 строке последняя операция, которую мы выполняем – это вывод typeof test5.toString. test5 – это переменная, которая была создана выше, но когда мы обращаемся к toString() то мы по сути обращаемся к прототипу в котором находится tostring и toString() это получается у нас метод и при выводе typeof toString то получаем function, потому что функция, которая находится в объекте она у нас называется методом. Эта обычная функция. Как мы можем использовать ключевое слово typeof. В каких ситуациях его применяют?
Вот есть шестой пример, в котором показано один из вариантов использования этого ключевого слова. Когда вы хотите сделать метод, и хотите так, чтобы в этот метод приходили параметры какого-то определенного типа данных, который вам необходим – вы можете использовать ключевое слово typeof. Функция на 12 строке называется printMessage(). Принимает параметр и на 14 строке функция проверяет какого типа шел параметр. Если Typeof параметра не равен string, то тогда мы выводим alert, выводим сообщение, что метод не может работать. Допустим больше ничего не делаем. Но если message – это строковой тип данных, то 18 строка, мы в тело документа отображаем это сообщение, и отображаем какое-то конкретное действие. Ну и проверка этого метода, правильно ли он у нас работает. На 22 строке вызывая printMessage() мы передаем в параметры массив. И при этом мы видим alert, потому что массив – это не тот тип данных, который ожидается в message. Но строка 23 у нас корректно отрабатывает и Hello World отображается в тело документа. Первый раз отображается сообщение, потому что передали массив. Второй раз выводится текст в документ. Вот пример использования ключевого слова typeof. Для того, чтобы глубже изучить темы, которые мы рассматриваем в данном курсе рекомендую приобрести следующую литературу. Это книга JavaScript: подробное руководство. Автор Дэвид Флэнаган. Книга JavaScript оптимизация производительности. Автор Николас Закас. И JavaScript Шаблоны Стоян Стэфанов. Это очень хорошая литература, в которой детально рассматриваются возможности языка JavaScript и техники, которые рекомендуется применять при разработке сценариев. Также не забываете что в каждом уроке есть дополнительное описание, в котором вы можете найти домашнее задание и дополнительные ссылки на ресурсы, где можно узнать подробнее о тех темах, которые были пройдены в уроке. На этом мы заканчиваем урок конструкторы и прототипы. Спасибо за внимание. До новых встреч.