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

Основная идея - как можно меньше открываться внешнему миру. Сведение к минимуму соединений с внешней средой снижает вероятность непредвиденных изменений. Что мы можем скрыть, мы можем изменить.

Давайте посмотрим, как этого добиться.

Классы

Новым вариантом достижения инкапсуляции является использование символа решетки # для объявления частной переменной внутри class. Эта функция является частью ES2020.

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

Например, ключевое слово this может неожиданно изменить контекст.

setTimeout(counter.increment, 0);
//Cannot read private member #count from an object whose class did not declare it at increment

Проблемы такого рода обычно можно решить с помощью синтаксиса стрелок, но на это следует обратить внимание.

setTimeout(() => counter.increment(), 0);

Еще одна вещь, которую следует учитывать, - это то, что мы можем переопределить реализацию метода в существующем экземпляре класса. Вот пример:

counter.increment = function(){
  console.log('increment');
}
counter.increment();
//"increment"

Заводские функции

Другой вариант инкапсуляции - использование закрытий.

Замыкание - это функция, имеющая доступ к переменным из своей внешней функции даже после выполнения внешней функции.

В следующем примере Counter - заводская функция. Он возвращает два замыкания с одним и тем же частным состоянием, increment и decrement. Два замыкания имеют доступ к переменной count даже после выполнения Counter. Если вам интересно, как это возможно, вы должны знать, что выполнение функции создает объект области действия, в котором хранятся все переменные и параметры из выполняемой функции. Этот объект области доступен только для внутренних функций и будет уничтожен, как и любой другой объект, если на него нет ссылки.

Counter создает объект с частным состоянием. Обратите внимание, что он не использует ключевое слово this и нет необходимости вызывать его с new.

counter.increment = function(){
  console.log('increment');
}
//Cannot assign to read only property 'increment' of object '#<Object>'

Объекты, созданные с помощью Counter, не будут иметь this связанных проблем, потому что this вообще не используется.

Последние мысли

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

Классы могут быть более удобными для разработчиков, переходящих с языков ООП на основе классов.

Фабричные функции создают инкапсулированные объекты без использования ключевого слова this и, таким образом, избегают связанных с ним проблем с изменением контекста.