Доброго времени суток, друзья!
В нашем прошлом уроке мы c Вами рассмотрели возможности одну из самых важных деталей языка JavaScript – контекст this, значение контекста this в глобальной и локальной области видимости, а также - пять случаев ответственных за содержание контекста this внутри кода.
Сегодня, после долгого перерыва, мы продолжим наш курс, и затронем такую интересную парадигму как Объектно-ориентированное программирование.
Итак, давайте начнем.
Урок 29. Объектно-ориентированное Программирование. Введение.
Для дальнейшего продвижения к нашей цели нам необходимо познакомится с логикой Объектно-ориентированного программирования или ООП Несмотря на длинное название в ней нет ничего сверх сложного и все что он нас потребуется для начала – это вникнуть в саму суть этой темы. ООП - это парадигма. Другими словами, это совокупность терминов и понятий, который определяют стиль написания кода для программы. Если совсем просто ООП - это просто организация кода. Уже само название данной парадигмы говорит о том, что ориентироваться при её использовании нужно на Объекты (Оbjects), которые мы с Вами рассматривали начиная с урока № 22. Давайте для наглядности приведем пример кода, написанного с помощью обычных функций, а потом постараемся привести этот код к некоторому подобию Объектно-ориентированного.
/* Сложение */
function add(a, b) {
console.log(a + b);
}
/* Вычитание */
function sub(a, b) {
console.log(a - b);
}
/* Умножение */
function mult(a, b) {
console.log(a * b);
}
/* Деление */
function dev(a, b) {
console.log(a / b);
}
Мы создали четыре функции ( урок 18, урок 19, урок 20 ) и как видно из их названия они выполняют операции сложения, вычитания, умножения и деления с двумя числами, которые мы передадим им как параметры, а результаты этих вычислений выводят в консоль браузера. Давайте убедимся в этом, вызвав по одному разу каждую из них:
/* Сложение */
function add(a, b) {
console.log(a + b);
}
/* Вычитание */
function sub(a, b) {
console.log(a - b);
}
/* Умножение */
function mult(a, b) {
console.log(a * b);
}
/* Деление */
function dev(a, b) {
console.log(a / b);
}
/* Вызов функций */
add(1, 2);
sub(2, 3);
mult(3, 4);
dev(4, 5);
Мы добавили вызовы, передали каждой функции цифры в виде параметров и можем наблюдать результат их работы в консоли браузера:
И вот пред нами код. Организован он в принципе неплохо: небольшой, функции выполняют понятную работу и даже комментарии присутствуют. Есть небольшой минус в том, что все функции находятся в Глобальном пространстве. Это легко проверить если обратится к каждой из них через Объект Window:
Как видим все четыре функции попали в глобальную область видимости и доступны из консоли. В профессии программиста, как и в любой другой присутствую правила хорошего тона. Одно из таких правил советует нам держать Глобальную Область Видимости в относительном порядке и не засорять его без надобности. К примеру, сейчас у нас всего четыре функции которые попали в эту область, и в общем-то это не такая уж большая проблема. Однако, если в процессе работы наш код будет разрастаться и количество создаваемых нами функций будет увеличиваться, то при таком подходе все они тоже неминуемо попадут в Глобальную Область Видимости.
Как мы можем сократить присутствие функций в Глобальной Области видимости. Мы можем воспользоваться Объектом (Object).
Давайте посмотрим на функции и подумаем, что у них общего. Это просто, все они совершают математические операции. Тогда мы можем объединить их в одну группу в рамках одного объекта и назвать этот объект так, чтобы нам или тому, кто будет работать с нашим кодом дальше сразу было понятно, что он содержит в себе инструменты математической направленности (например, ** mathematics (математика)**). Давайте преобразуем наш код:
var mathematics = {
add: function(a, b) {
console.log(a + b);
},
sub: function(a, b) {
console.log(a - b);
},
mult: function(a, b) {
console.log(a * b);
},
dev: function(a, b) {
console.log(a / b);
}
}
Мы создали Объект mathematics и присвоили наши функции этому объекту как его методы (мы же еще помним что функции внутри Объектов называются методами Объекта ;) ). Чего мы этим добились?
Во-первых, мы немного разгрузили нашу Глобальную Область Видимости и вместо четырех функций теперь мы имеем там только один объект. Кроме того, если количество функций будет увеличиваться, то в Глобальной области видимости ситуация не изменится, там, как и раньше останется все тот же один объект.
Во-вторых, мы сгруппировали функции и теперь если вдруг нам явно понадобятся именно математические функции мы точно знаем где их найти, и относительно точно можем рассчитывать, что все функции такого плана находятся как бы в одном единственном месте – Объекте mathematics.
Однако данная структура кода хоть и оперирует Объектом, но не имеет стопроцентного соответствия парадигме ООП. Почему?
Дело в том, что как уже упоминалось парадигма – это совокупность свойств, и ООП как настоящая парадигма имеет четко обозначенный свойства, которые позволяют определить её. Свойства эти носят названия Наследование, Полиморфизм и Инкапсуляция. Только не спешите пугаться. Не смотря на такие необычные названия, эти свойства вполне поддаются осмыслению, и мы постараемся разобрать их таким образом, чтобы все стало понятным и доступным. И для этого начнем мы из далека. Сначала рассмотрим для каждого из названных свойств логику их работы, а уже после этого посмотрим как они реализуются в JavaScript’e при помощи кода.
Наследование.
Есть еще одно правило в программирование. Если Вы научитесь его соблюдать, то как минимум Ваш код станет практичнее. На английском языке это правило имеет аббревиатуру DRY что расшифровывается как Don’t Repeat Yourself и переводится как Не повторяйте сами себя. В целом оно сводится к следующему: в идеале, код, который Вы пишите не должен дублироваться. Вы должны внимательно следить за своей работой, и если заметите что какие-то строки кода выполняют одно и то же действие, то правильным будет вынести эти строки в отдельную функцию, или метод объекта и использовать уже эту функцию (или метод) а не дублировать код. Давайте приведем небольшой пример. Допустим у нас есть две функции сложения и вычитания. Но не простого сложения и вычитания, а так, чтобы сначала числа, которые будут переданы как параметры, умножились между собой, а уже только после этого между их произведением (результатом умножения) произошли операции сложения или вычитания со вторым параметром функции. В коде это может выглядеть так:
function doAdd(a, b) {
var d = a * b;
return d + b;
}
function doSub(a, b) {
var d = a * b;
return d - b;
}
Две простых функции. Функция doAdd
складывает произведение своих параметров со своим вторым параметром. Функция doSub
вычитает второй параметр из произведения. Как мы можем видеть код var d = a * b;
общий, он повторяется и делает ровным счетом одно и тоже. И если мы вдруг захотим, чтобы числа не умножались, а делились, тогда нам придётся менять код в двух местах. А если подобных функций двадцать, и в каждой дублировалась данная строка, то нам придется это делать в двадцати разных местах. Проще всего такое дублирование будет вынести в отдельную функцию и вместо дублирования строк пользоваться ей. Давайте преобразуем код и уберём дублирование:
function preProcess(a , b) {
return a * b;
}
function doAdd(a, b) {
var d = preProcess(a , b);
return d + b;
}
function doSub(a, b) {
var d = preProcess(a , b);
return d - b;
}
Мы вынесли повторяющуюся строку в отдельную функцию preProcess
, тем самым убрав дублирование. Кроме того, если мы теперь захотим, чтобы вместо умножения с параметрами функции сначала происходило деление, мы должны будем сделать исправление только в одном единственном месте. Это конечно примитивный пример, но суть он все же отражает.
В разрезе ООП и работы с Объектами дублирование кода помогает избежать Наследование. Название Наследование полностью передает суть процесса. Это свойство, благодаря которому одни объекты могут наследовать от других объектов атрибуты или методы по умолчанию. Так что нам не надо повторять их для каждого объекта в отдельности. Давайте попробуем представить себе двух разных, но очень похожих животных: кота и собаку. Это хоть и разные животные, однако у каждого из них есть четыре лапы, один хвост, они могут ходить, спать и кушать. Есл бы мы описывали их с помощью объектов в JavaScript? То получили бы примерно следующее:
var cat = {
tail: 1,
paws: 4,
walk: function() {
},
sleep: function() {
},
eat: function() {
}
}
var dog = {
tail: 1,
paws: 4,
walk: function() {
},
sleep: function() {
},
eat: function() {
}
}
Как видно из скриншота, объект cat
имеет абсолютно одинаковый набор атрибутов и методов с объектом dog
. И на лицо у нас явное дублирование. Наследование позволяет избежать такого рода ситуации. Дело в том, что мы можем подумать о коте и о собаке не как о представителе своего рода а как о некой общности. Оба они животные. Тогда мы можем сначала создать объект животное, описать в нем все повторяющиеся атрибуты и сделать его общим и для кота, и для собаки, благодаря чему и кот и собака уже с самого начала будут иметь нужные им свойства и методы. Схематично это можно представить так:
То есть у нас есть некий общий Родитель в нашем случае это Animal
и от него происходят два Потомка в нашем случае это cat
и dog
. Оба этих потомка будут сразу со своего рождения иметь общие черты, которые они УНАСЛЕДУЮТ от своего Родителя. В этом и заключается суть такого свойства в ООП как Наследование.
А на сегодня все. В следующем уроке мы разберем, что такое Полиморфизм.
Ссылки на предыдущие уроки:
Урок 1 - Окружение.,
Урок 2 - Некоторые особенности синтаксиса.,
Урок 3 - Переменные.,
Урок 4 - Типы переменных, как основа для их взаимодействия.,
Урок 5 - Операции с переменными одного типа.,
Урок 6 - Операции с переменными одного типа. Часть II.,
Урок 7 - Взаимодействие переменных с разными типами.,
Урок 8 - Взаимодействие переменных разного типа. часть II.,
Урок 9 - Взаимодействие переменных разного типа. Часть III.,
Урок 10 - Другие возможные взаимодействия между переменными.,
Урок 11 - Другие возможные взаимодействия между переменными. Часть II.,
Урок 12 - Другие возможные взаимодействия между переменными. Операторы присваивания.,
Урок 13 - Другие возможные взаимодействия между переменными. Операторы сравнения.,
Урок 14 - Сложные переменные. Array и Object.,
Урок 15 - Условные операторы.),
Урок 16 - Циклы.,
Урок 17 - Циклы. Часть II.,
Урок 18 - Функции.,
Урок 19 - Функции. Часть II.,
Урок 20 - Профилирование. Функции, часть III.,
Урок 21 - Функции, Часть IV. Аргументы.,
Урок 22 - Objects (Объекты).,
Урок 23 - Встроенные функции и объекты.,
Урок 24 - Встроенные функции и Объекты, Часть II. Глобальные функции и переменные.,
Урок 25 - Встроенные функции и Объекты, Часть III. Document Object Model.,
Урок 26 - Встроенные функции и Объекты, Часть III. Document Object Model.
Урок 27 - Встроенные объекты. Объект Style, Events, Часть II.
Урок 28 - Встроенная переменная this. Глобальная и локальная области видимости.
Хорошо простыми словами объяснена суть наследования. Вот только, несмотря на кажущуюся полезность, пользоваться им приходится очень редко.
Спасибо.
Интересная мысль. В небольших приложениях вполне можно, я думаю, обойтись и без него. Однако большие проекты с "толстым" front-end'ом потеряют очень много в производительности без использования Наследования. В наших проектах, без него никуда.