ООП


Три кита ООП:
  • Инкапсуляция
  • Наследование
  • Полиморфизм
Инкапсуляция, наследование и полиморфизм это три парадигмы объектно-ориентированного программирования.
ООП в JavaScript полностью основано на прототипах.
ООП встречвается на каждом шагу, даже если мы этого не замечаем.

Инкапсуляция


Инкапсуляция - возможность скрывать сложную реализацию за простыми вещими.

Пример: музыкальный плеер.
Чтобы начать проигрывание музыки, нужно послать запрос на сервер, получить файл, добавить его в список воспроизведения, начать проигрывание. Но все эти операции скрыты и запускаются при вызове простой функции play().

Ещё пример

html-разметка

<button id="myButton">Нажми</button>

Скрипт

  const myButton = document.querySelector("#myButton");
 
  myButton.addEventListener("click", () => {
    console.log("кликнули по кнопке");   
  });


// Кстати, без строки const myButton = document.querySelector("#myButton"); код тоже будет работать. Если селектор объявлен через id, автоматически создаётся глобальная переменная с его именем и он может использоваться в JavaScript. Возможно, именно в этом причина убеждения, что JavaScript лучше работает с id чем с классами. На самом деле скорость работы практически не отличается.

Так вот, для того, чтобы исполнить этот несложный код, среда исполнения выполняет десятки разных операций: обрабатывает все действия пользователя, проверяет, не назначены ли для этих действий обработчики событий, проверяет условия, например, не отменено ли действие по умолчанию, погружает событие до целевого элемента и  обеспечивает его всплытие. И все эти действия скрыты за методом addEventListener(). То есть инкапсуляция присутствует не только в том коде, который пишем мы, но и в том, который обеспечивает работу самого JavaScript и браузера.
Те многочисленные методы JavaScript, которые нам доступны и которые мы используем, являтся внешним интерфейсом за которым скрыта достаточно сложная реализация.

К примеру, реализация пузырьковой сортировки на JavaScript занимает 15 строк кода:

function bubbleSort(arr) {

  var length = arr.length - 1;

  for (var i = 0; i < length; i++) {
    var wasSwap = false;
    for (var j = 0; j < length - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        var swap = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = swap;
        wasSwap = true;
      }
    }
    if (!wasSwap) break;
  }
  return arr;
}

var arr = [2,1,5,4,3,7,6,11,21];
bubbleSort(arr)

Но мы не задумываемся ни об одной из этих строк, когда используем метод arr.sort().

// На самом деле в методе arr.sort() используется вовсе не пузырьковая сортировка, а намного более быстрый алгоритм https://studyjavascript.blogspot.com/2019/03/blog-post_14.html

Ещё один пример. Рассмотрим функцию

  const myButton = document.querySelector("#myButton");
  myButton.addEventListener("click", counter);
 
  var count = 0;
  function counter(){
    console.log(count++);
  }


При каждом клике по кнопке счётчик увеличивается на единицу. Всё работает как следует, проблема в том, что переменная count объявлена глобально. Никто не застрахован от того, что где-то в другом месте кода другой разработчик не объявит свою переменную count, присвоив ей значение 100 или 200, или другой алгоритм увеличения значения. То есть инкапсуляция решает ещё и задачу защиты данных от незапланированого изменения.

Пáттерн модуль


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

В отношении счётчика, код которого приводился выше, паттерн модуль выглядит так:

  function Counter(){
    let count = 0;
    return {
      inc(){
        count++;
      },
      get(){
        return count++;
      }
    };  
  }
  const counter = Counter();


  const myButton = document.querySelector("#myButton");
  myButton.addEventListener("click", function(){
    console.log(counter.get());
  });


Мы создаём функцию Counter(), внутри неё, через let или var неважно, объявляем переменную count, возвращаем объект , в котором есть метод inc() (от increment - увеличивать), который при вызове увеличивает  счётчик count на единицу. Другой метод get() возвращает полученное значение.
То есть модуль позволяет решить ещё и третью задачу - не только защитить счётчик count от незапланированных изменений, но и добавить методы для работы с ним.
Кроме того, мы можем объявить другую переменную counter2, которая будет увеличивать счётчик count независимо от первой.


  const counter2 = Counter();
  counter2.inc();
  console.log(counter2.get());


В модуле данные, которые хранятся внутри функции называются приватными данными или защищёнными данными или protect данными. а методы, которые доступны извне функции и позволяют получить или изменить приватные данные называются публичным интерфейсом.

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


Наследование - вторая парадигма ООП, согласно которой один объект может унаследовать методы и свойства, которые присутствуют у другого объекта.

Пример - метод .addEventListener(). Его можно добавить кнопке, картинке, ссылке, любому другому элементу на странице. Но это не означает, что для каждого элемента создаётся отдельный метод .addEventListener(). На самом деле существует глобальный объект EventTarget, одним из методов которого является EventTarget.addEventListener(). 
Механизм наследования позволяет и другим объектам унаследовать этот метод глобального объекта EventTarget.

Существует цепочка наследования.Object -- EventTarget -- Node -- Element -- HTML element -- div, span, img
В этой цепочке каждый следующий элемент наследует от предыдущего его методы и свойства.

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

Наследование в JavaScript прототипное. У объекта есть прототип в котором записаны наследуемые методы, и по цепочке прототипов снизу вверх идёт проверка - существует ли указанный нами метод у объекта или у любого из его прототипов. Если да, он возвращается, если нет, выводит ошибку.

Кроме прототипного наследования, существует наследование на уровне классов.
Если у нас есть класс калькулятор – Calc: мы можем создать класс квадратичный калькулятор SqrCalc и передать ему все методы класса Calc

class SqrCalc extends Calc {



Подробнее: Классы в JavaScript

Полиморфизм


Рассмотрим его на примере метода .addEventListener()., который рассматривали выше. Как было сказано, данный метод можно добавить кнопке, картинке, ссылке, любому другому элементу на странице.То что один метод / функция могут работать с различными типами объектов, является проявлением полиморфизма.

Другой пример - отображение изображений и видео, проигрывание музыки. Если они поддерживают метод play(), мы можем применить его данным файлам. Нам даже не нужно задумываться к какому типу они принадлежат, главное, чтобы они поддерживали данный метод.

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