В прошлой статье мы разобрали, что такое замыкание в программировании и зачем оно нужно. Сегодня будет практика — добавим замыкания в свои программы.
Делаем форматированый лог
Часто при отладке программы или для ведения логов в 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);
Получается, что с замыканиями мы можем получить некоторые возможности ООП, не используя классы и логику работы с объектами.