В прошлой статье мы разобрали, что такое замыкание в программировании и зачем оно нужно. Сегодня будет практика — добавим замыкания в свои программы.
Делаем форматированый лог
Часто при отладке программы или для ведения логов в JavaScript используют команду console.log(). Она берёт строку из скобок и просто выводит её на экран, а такие сообщения называются логом. Но иногда бывает так, что нужно вывести ещё порядковый номер каждого сообщения и время, прошедшее с загрузки скрипта.
Если пойти стандартным путём, можно сделать так: завести функцию, которая выводит в лог сообщение, и глобальные переменные, где хранится служебная информация:
// стандартная функция
function log(timespan, lineNumber, msg) { 
	// выводим порядковый номер, время и сообщение
  console.log(lineNumber + "				" + timespan + "				" + msg);
}
// объявляем глобальные переменные для времени старта скрипта и порядкового номера
var start = Date.now();
var lineNumber = 1;
// выводим заголовок лога и линию отбивки
console.log('Номер  Время 		Сообщение')
console.log('---------------------------------')
// вызываем функцию для вывода сообщений
log(Date.now()-start, lineNumber++, "Первое сообщение");
log(Date.now()-start, lineNumber++, "Второе сообщение");Но у такого подхода есть два минуса:
- в нём используется глобальная переменная, которую может поменять любая другая функция;
- и нам нужно передавать при вызове каждый раз много параметров.
Вторую проблему можно решить, перенеся код вычисления даты и номера внутрь функции, но первую проблему это всё равно не решит.
Если сделать то же самое на замыканиях, то и код получится проще, и вызывать станет удобнее. А заодно решится проблема с глобальной переменной — их просто не будет, а значит, меньше шансов, что другая функция их случайно поменяет.
// функция с замыканием, которая будет отвечать за лог
var log = (function () {   
	// две переменные ниже недоступны из программы, 
	// поэтому никто их случайно не поменяет
	// сразу получаем время старта
  var start = Date.now(); 
  // и задаём начальный номер для сообщений
  var num = 1;   
  // выводим заголовок лога и линию отбивки
	console.log('Номер  Время 		Сообщение')
	console.log('---------------------------------')       
  // результат — функция, которая получает только один параметр на входе
  return function (msg) {  
  	// выводим уже отформатированный лог в консоль
  	// сразу же считаем прошедшее время, используя переменную start,
  	// а также увеличиваем на единицу после использования переменную num
    console.log(num++ + "				" + (Date.now()-start) + "				" + msg);
   }
// тут же запускаем первую функцию, чтобы вывести шапку лога и запомнить время старта
})(); 
// теперь можем выводить сообщения в консоль удобным способом
log("Первое сообщение");
log("Второе сообщение");Приватные переменные без ООП
В объектно-ориентированном программировании часто пользуются приватными переменными и методами — к ним можно получить доступ только внутри класса, а снаружи этого сделать не получится. С приватными переменными можно работать только по определённым правилам — это сильно уменьшает риск ошибки при работе программы или при обработке данных.
Работа с приватными переменными похожа на обслуживание в банке или МФЦ — нельзя подойти к окошку и обслужиться без талона, нужно его обязательно получить в автомате на входе.
В ООП всё просто: создаётся класс, в котором с помощью символа # обозначается такая переменная, и пишутся методы, которые позволяют с ней работать:
// создаём новый класс
class counter {
	// приватная переменная 
	#num = 1;
	// увеличиваем значение на 1
  plus() {
    this.#num += 1;
  }
  // уменьшаем значение на 1
  minus() {
  	this.#num -= 1;
  }
  // возвращаем текущее значение переменной
  value() {
  	return this.#num
  }
}
// создаём новый объект на основе класса
var c = new counter;
// выводим значение счётчика
console.log(c.value());
// увеличиваем на единицу
c.plus();
// выводим новое значение счётчика
console.log(c.value());
// а вот так мы не получим ничего
console.log(c.num);Но что если мы не хотим работать в ООП-стиле и не хотим использовать классы? В этом случае можно сделать всё то же самое на замыканиях — и методы, и приватные переменные:
// функция с замыканием
const counter = () => {
  // приватная переменная num
  let num = 1;
  
  // результат — две функции, каждая из которых делает что-то своё
  return {
    // увеличиваем счётчик на 1
    plus() {
      num += 1;
    },
    
    // получаем значение счётчика
    value() {
      return num;
    },
  };
};
// создаём новую переменную на основе функции
var c = counter();
// выводим значение счётчика
console.log(c.value());
// увеличиваем на единицу
c.plus();
// выводим новое значение счётчика
console.log(c.value());
// а вот так мы не получим ничего
console.log(c.num);Получается, что с замыканиями мы можем получить некоторые возможности ООП, не используя классы и логику работы с объектами.
