Стать программистом. Практика JavaScript для новичков. Занятие 8.

в прошлом году

Доброго времени суток, друзья!

В нашем прошлом занятии мы наконец-то увязали наши кнопки с пополнением наших счетчиков характеристик. Мы познакомились с методом getAttribute(), позволяющий получить значение любого атрибута html-элемента. Кроме того, мы использовали прием сохранения контекста. Помимо этого, мы преобразовали наш код таким образом, что две функциональности по уменьшению счетчиков характеристик и добавлению событий на кнопки теперь инициализируются всего одним методом - startGame().

Для того чтобы от функциональности счетчиков начать двигаться дальше нам осталось решить всего один нюанс. Давайте его рассмотрим.

Стать программистом. Практика JavaScript для новичков. Занятие 8.

Давайте обратим внимание на то, что произойдет с оставшимися двумя счетчиками, когда какой-либо из них уже исчерпает себя:

Обратите внимание, когда один из счетчиков уже исчерпался, два других продолжают уменьшаться. Что это значит с точки зрения исполнения кода? Это означает что продолжает выполнятся как минимум два интервала, которым выполнятся уже не нужно. Другими словами, у нас есть два процесса, которые попросту занимают оперативную память. Для нас как для разработчиков очень важно следить за так называемыми утечками памяти. От них надо избавляться. Это повысит качество созданного вами кода с точки зрения производительности (быстроты исполнения). А производительность очень важна, а в больших проектах критична.

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

Если мы взглянем на метод runProgressBar() мы увидим, что каждый раз, когда мы вызываем его к исполнению, мы создаем переменную interval. В эту переменную мы сохраняем сам интервал для уменьшения счетчика. А когда интервал доводить свойство width до нуля, мы используем метод clearInterval() для остановки интервала, куда и передаём нашу переменную interval. То есть для того чтобы остановить интервал извне, нам требуется иметь доступ к этой переменной. Но она инкапсулирована. Мы не можем к ней достучатся прямым программным доступом. К счастью мы уже решали аналогичную задачу ранее в случае со свойством width, когда вынесли одноименное свойство в каждый из счетчиков. Давайте применим аналогичную логику для решения текущей задачи:

Для начала мы вынесли нужную нам переменную в свойство каждого счетчика. И руководствуясь этим изменением мы вносим корректировки в наш метод runProgressBar():

var Booth = {
    informer: {
        selector: document.getElementById('info'),
        setText: function(text) {
            this.selector.innerText = text;
        }
    },
    timer1: {
        selector: document.getElementById('fatigue'),
        isOrange: false,
        isRed: false,
        interval: null,
        width: 100
    },
    timer2: {
        selector: document.getElementById('satiety'),
        isOrange: false,
        isRed: false,
        interval: null,
        width: 100
    },
    timer3: {
        selector: document.getElementById('mood'),
        isOrange: false,
        isRed: false,
        interval: null,
        width: 100
    },
    /* добавляем события к выбранным кнопкам */
    buttons: document.getElementsByClassName('monitor__btn'),
    addButtonsEvents: function() {
        var context = this; // равен объекту Booth

        for(var i = 0; i < this.buttons.length; i+=1) {
            this.buttons[i].addEventListener('click', function() {
                var action = this.getAttribute('data-state');
                if(action == 'fatigue') {
                    context.refreshProgressBar(context.timer1);
                }
                if(action == 'satiety') {
                    context.refreshProgressBar(context.timer2);
                }
                if(action == 'mood') {
                    context.refreshProgressBar(context.timer3);                     
                }   
            });
        }
    },
    /* Запускаем счетчики парметров */
    runProgressBar: function(timer, seconds) {
        
        var milliseconds = seconds*1000;
        timer.interval = setInterval(function() {
            if(timer.width != 0) {
                timer.width -= 1;
                timer.selector.style.width = timer.width + '%';

                /* смена цвета счетчика*/

                if(timer.isOrange == false && timer.width <= 50) {
                    timer.isOrange = true;
                    timer.selector.setAttribute('class', 'monitor-bar__progress attention');
                }

                if(timer.isRed == false && timer.width <= 25) {
                    timer.isRed = true;
                    timer.selector.setAttribute('class', 'monitor-bar__progress alarm');
                }

            } else {
                clearInterval(timer.interval);
            }
        }, milliseconds);
    },
    /* пополняем счетчик */
    refreshProgressBar: function(timer) {
        timer.width = 100;
        timer.selector.setAttribute('class', 'monitor-bar__progress');
    },
    /* Запускаем наши счетчики */
    startTimers: function() {
        this.runProgressBar(this.timer1, 0.3);
        this.runProgressBar(this.timer2, 0.5);
        this.runProgressBar(this.timer3, 0.7);
    },
    /* Запускаем игру */
    startGame: function() {
        this.addButtonsEvents();
        this.startTimers();
    }
}

Такого рода изменения дают нам возможность останавливать счетчики, когда нам это требуется, вызвав clearInterval() метод, передав в параметр свойство interval, которое теперь присутствует в каждом из счетчиков. Давайте проверим в браузере:

Как видим, мы действительно можем останавливать счетчики, когда нам требуется. Теперь, чтобы решить изначальную задачу, все что нам требуется – это останавливать все три счетчика, вместо одного, когда какой-либо из них уменьшиться до нуля. Давайте преобразуем код в соответствие с этой логикой.

var Booth = {
    informer: {
        selector: document.getElementById('info'),
        setText: function(text) {
            this.selector.innerText = text;
        }
    },
    timer1: {
        selector: document.getElementById('fatigue'),
        isOrange: false,
        isRed: false,
        interval: null,
        width: 100
    },
    timer2: {
        selector: document.getElementById('satiety'),
        isOrange: false,
        isRed: false,
        interval: null,
        width: 100
    },
    timer3: {
        selector: document.getElementById('mood'),
        isOrange: false,
        isRed: false,
        interval: null,
        width: 100
    },
    /* добавляем события к выбранным кнопкам */
    buttons: document.getElementsByClassName('monitor__btn'),
    addButtonsEvents: function() {
        var context = this; // равен объекту Booth

        for(var i = 0; i < this.buttons.length; i+=1) {
            this.buttons[i].addEventListener('click', function() {
                var action = this.getAttribute('data-state');
                if(action == 'fatigue') {
                    context.refreshProgressBar(context.timer1);
                }
                if(action == 'satiety') {
                    context.refreshProgressBar(context.timer2);
                }
                if(action == 'mood') {
                    context.refreshProgressBar(context.timer3);                     
                }   
            });
        }
    },
    /* Запускаем счетчики парметров */
    runProgressBar: function(timer, seconds) {
        var self = this; // равен объекту Booth
        var milliseconds = seconds*1000;
        timer.interval = setInterval(function() {
            if(timer.width != 0) {
                timer.width -= 1;
                timer.selector.style.width = timer.width + '%';

                /* смена цвета счетчика*/
                if(timer.isOrange == false && timer.width <= 50) {
                    timer.isOrange = true;
                    timer.selector.setAttribute('class', 'monitor-bar__progress attention');
                }

                if(timer.isRed == false && timer.width <= 25) {
                    timer.isRed = true;
                    timer.selector.setAttribute('class', 'monitor-bar__progress alarm');
                }

            } else {
                /* Останавливаем все счетчики */
                clearInterval(self.timer1.interval);
                clearInterval(self.timer2.interval);
                clearInterval(self.timer3.interval);
            }
        }, milliseconds);
    },
    /* пополняем счетчик */
    refreshProgressBar: function(timer) {
        timer.width = 100;
        timer.selector.setAttribute('class', 'monitor-bar__progress');
    },
    /* Запускаем наши счетчики */
    startTimers: function() {
        this.runProgressBar(this.timer1, 0.3);
        this.runProgressBar(this.timer2, 0.5);
        this.runProgressBar(this.timer3, 0.7);
    },
    /* Запускаем игру */
    startGame: function() {
        this.addButtonsEvents();
        this.startTimers();
    }
}

Итак, теперь вместо остановки одного интервала, мы останавливаем все три (обведены зеленой рамкой). Так же обратите внимание на то, что мы снова воспользовались приемом сохранения контекста, для того, чтобы иметь точною ссылку на объект {Booth} и передавать параметры для метода clearInterval() в нужном нам контексте (линия, стрелки и рамка оранжевого цвета). Давайте теперь проверим в браузере достигли ли мы своей цели, и действительно ли в случае обнуления одного счетчика остановятся все.

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

А на сегодня всё. Продолжение следует…

Ссылки на предыдущие занятия:

Практика JavaScript для новичков. Занятие 1,
Практика JavaScript для новичков. Занятие 2,
Практика JavaScript для новичков. Занятие 3,
Практика JavaScript для новичков. Занятие 4,
Практика JavaScript для новичков. Занятие 5,
Практика JavaScript для новичков. Занятие 6,
Практика JavaScript для новичков. Занятие 7

Ссылки на предыдущий курс:

Урок 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. Глобальная и локальная области видимости.
Урок 29 - Объектно-ориентированное Программирование. Введение.
Урок 30 - Объектно-ориентированное Программирование. Часть II. Полиморфизм.
Урок 31 - OОП. Наследование, Часть I. Оператор new.
Урок 32 - ООП. Наследование, Часть II. PROTOTYPE.
Урок 33 - ООП. Часть II. Полиморфизм.
Урок 34 - ООП. Часть III. Инкапсуляция.

Авторы получают вознаграждение, когда пользователи голосуют за их посты.
Голосующие читатели также получают вознаграждение за свой голос.