И снова замыкания

Да, не опять, а снова. По крайней мере, где-то в интернете прочитал, что причина в этом, но ничего не понял, причем здесь они.
Вот есть функция setTimeout(function () {
какой-то код;
}, 1000).
И вот этот “какой-то код” требует, чтобы ему передали параметр. Если вставить напрямую в callback setTimeout, код в упор его не видит. А вот если обернуть таймер в ещё одну функцию и передать параметр ей, то все работает. Объясните, пожалуйста, причем здесь замыкания и почему так происходит?

Покажи больше проблемного кода.

Покажешь код? Или вопрос уже неактуальный?

Дима, покажу - болит спина, так что за компом фактически сидеть не могу )) А на потолок ноутбук не знаю, как присобачить )))

        submitBtn.addEventListener("click", function(e) {
          e.preventDefault();
            if (textarea.value.trim()) {
              const dateTime = getCurrentDateTime(),
              newMessage = createMessage(dateTime);
              textarea.value = "";
              scrollWindow();
              changeColor(newMessage);
            }
        });

А вот та функция changeColor:

        function changeColor(newMessage) {
            setTimeout(function() {
              newMessage.classList.remove("chat__messages-item--just-sent");
            }, 0.1);
          }

В таком виде всё работает, а вот если в первый обработчик вставить только

    setTimeout(function(newMessage) {
              newMessage.classList.remove("chat__messages-item--just-sent");
            }, 0.1);

и передать параметр в эту анонимную функцию в таймере, то не работает
P.S. Кнопку форматировать жал несколько раз, код куда-то отодвигается, но не форматируется. Не знаю, как сделать

Ну, ты волшебник!! Как правильно это делать, чтобы код форматировался??

Тут словами описано какими символами оборачивать код чтобы тот подсветился Как формулировать вопрос, подсвечивать код, благодарить.

Еще можно нажать на карандаш рядом с ником (который показывает что пост был отредактирован) и увидеть diff - разницу того что было и что стало.

1 лайк

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

1 лайк

Т.е. вот так НЕ работает:

submitBtn.addEventListener("click", function(e) {
    e.preventDefault();
    if (textarea.value.trim()) {
      const dateTime = getCurrentDateTime(),
        newMessage = createMessage(dateTime);
      textarea.value = "";
      scrollWindow();
      //changeColor(newMessage);
      setTimeout(function(newMessage) {
            newMessage.classList.remove("chat__messages-item--just-sent");
          }, 0.1);
    }
  });

Ошибка: текст ошибки

Этот код описывает следующее: “анонимная функция примет аргумент и запишет его значение в переменную newMessage. И эту анонимную функцию передать аргументом в setTimeout”. Потом setTimeout вызовет функцию-аргумент ожидаемо без аргументов, и значение newMessage будет undefined. Иными словами код работает как и код ниже:

function f(someArg) {
console.log('Arg value is', someArg)
}
//...
setTimeout(f, 999)

В первом случае когда вызывается вот такая changeColor

function changeColor(newMessage) {
    setTimeout(function() {
        newMessage.classList.remove("chat__messages-item--just-sent");
    }, 0.1);
}

то в нее передается значение которое доступно анонимной функции, объявленной внутри changeColor (что и есть определение замыкания).

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

Где-то далеко-далеко понятно, но не совсем. Возьмём такие примеры:

let someArg = 15;
function f(someArg) {
console.log("someArg: " + someArg);
}
f(someArg);
// работает
let someArg = 15;
function f() {
console.log("someArg: " + someArg);
}
f();
// работает
let someArg = 15;
setTimeout(function (someArg) {
console.log("someArg: " + someArg);
}, 1000);
// НЕ работает

Почему? Т.е. в первых двух случаях всё работает независимо от того, получит ли функция параметр или нет - если нет, она просто пойдёт вверх по замыканию и найдёт значение за пределами её. А в таймере она почему-то не может “прорваться” за пределы таймера, хотя если таймер дополнительно обернуть в другую функцию, то снова может…

Я перефразирую первый и третий примеры, просто поменяю имя переменной:

let a = 15;
function f(someArg) {
console.log("someArg: " + someArg);
}
f(a);
// работает
let a = 15;
setTimeout(function (someArg) {
console.log("someArg: " + someArg);
}, 1000);
// НЕ работает

Тут заметна принципиальная разница? После моих изменений никак не изменился порядок выполнения кода. В начальном примере по чистой случайности разные переменные имели одинаковое имя. Если разные переменные “разнести”, дать разные имена, то вопросов о том почему код не работает не должно даже возникнуть.

Хм… Наверное, да: во втором случае нет явного, как в первом, вызова функции с передачей ей нужного значения. А можно ли как-то упростить, чтобы таймер работал без дополнительной обёртки?

Можно убрать обертку

if (textarea.value.trim()) {
	const dateTime = getCurrentDateTime(),
		newMessage = createMessage(dateTime);
	textarea.value = "";
	scrollWindow();
	setTimeout(function () {
		newMessage.classList.remove("chat__messages-item--just-sent");
	})
}
1 лайк

Чу-де-са! Получается, что нужно просто было не передавать параметр в анонимную функцию, и она бы сама нашла то, что нужно… Над этим надо поразмышлять ) В любом случае, весьма признателен за подробные объяснения )

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

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

В целом если нет повода создавать функцию, то создавать ее не надо.

Обычный повод для создания функции

  • разбить большие логические куски на поменьше чтобы было проще читать
  • создать независимый переиспользуемый кусок функциональности

В конкретном примере я не вижу повода создавать отдельную функцию. Я не вижу повода для таймаута тоже.

Прочел про спину, у самого таже беда и вот вспомнил стартап, когда то вдел рекламу.

ээээх!

Модно передавать параметры :)

В любом случае ты можешь их вытянуть из массива arguments внутри функции даже не передавая параметры в функцию… над этим тоже стоит поразмаслить ;)

Ох, шикарное рабочее место для спины!
А по поводу “модно” - это опечатки Т9, конечно же, имелось в виду “моЖно” - Дмитрий это понял )
Поразмыслю обязательно!

1 лайк

Дмитрий, спасибо! Да, убрал сейчас таймер, функцию по смене цвета сообщения - всё работает. Таймер делал, пытаясь пофиксить ошибку, думал, что что-то раньше грузится ) Спасибо ещё раз за объяснения, теперь чётко понимаю, почему были ошибки, а главное - как оно всё работает )

1 лайк