Как замыкания ломают циклы

оригинал

Я люблю замыкания. Это замечательный инструмент, любой Javascript программист должен уметь его использовать. Он позволяет делать фантастические вещи. Но есть в нем и свои сложности. Я опишу пару проблем, на которые наткнулся, чтобы у вас было меньше проблем при написании программ.При выполнении простого цикла

В цикле for нет ничего сложного. И нет ничего сложного в for и в Javascript. И так, зачем нужны замыкания в простейше цикле for? Например, для того чтобы привязать обработку событий внутри цикла.

  1. function Highlighter(ul) {
  2. var that = this;
  3. var subjects = ul.getElementsByTagName('li');
  4. for(var i = 0; i < subjects.length; i++) {
  5. addEvent(subjects[i], 'click', function() {
  6. that.highlight(i);
  7. });
  8. }
  9. }

На первый взгляд - обычный цикл for. Мы перебираем все элементы и назначаем обработчик на событие click, при срабатывании которого элемент подсвечивается. Если мы создаем новый Highlight, и затем щелкнем по элементу, мы получаем ошибку. Отслеживая ошибку мы видем (ого!), что i равно длине списка. Проблема!

Когда мы создаем все эти новые функции в цикле, и назначаем их, мы используем переменную i. Но i не относится к этим функциям, она находиться вне области видимости замыкания, т.е. i может измениться. И изменяется! По окончанию цикла она становиться равная длине списка, что не хорошо. И i не удалиться после окончания цикла, она будет существовать пока есть функции использующие ее. (прочитать больше о замыканиях)

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

  1. function Highlighter(ul) {
  2. var that = this;
  3. var subjects = this.subjects = ul.getElementsByTagName('li');
  4. for(var i = 0; i < subjects.length; i++) {
  5. (function(index) {
  6. addEvent(subjects[index], 'click', function() {
  7. that.highlight(index);
  8. });
  9. })(i);
  10. }
  11. }

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

Следующий шаг.

В функции внутри цикла, так же, используется другая внешняя переменная: subjects. Теоретически, позже я могу написать код, котрый будет менять subjects. Или с ним случиться еще что-нибудь. Быть может, я расширю кончтруктор и изменю этот массив. Или, что более вероятно, измениться NodeList, при изменении DOM, например, добавяться или убавиться элементы списка. Если это случиться, появится несоответсвие переменных.

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

  1. function Highlighter(ul) {
  2. var that = this;
  3. var subjects = this.subjects = ul.getElementsByTagName('li');
  4. for(var i = 0; i < subjects.length; i++) {
  5. (function(list_item, index) {
  6. addEvent(list_item, 'click', function() {
  7. that.highlight(index);
  8. });
  9. })(subjects[i], i);
  10. }
  11. }

Мы передаем в функцию элемент массива. Если в последствии массив измениться, поведение нашей функции останется прежним (в отличие от that.highlight(index), где будет ошибка обращения к массиву)

Фреймворки позволяют избавиться от этой проблемы.

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

В MooTool.

  1. function Highlighter(ul) {
  2. var that = this;
  3. var subjects = this.subjects = ul.getElements('li')
  4. subjects.each(function(el, index) {
  5. addEvent(el, 'click', function() {
  6. that.highlight(index);
  7. });
  8. });
  9. }

С помощью YUI3

  1. YUI().use('node', function(Y) {
  2. function Highlighter(ul) {
  3. var that = this;
  4. var subjects = this.subjects = ul.getElementsByTagName('li');
  5. Y.each(subjects, function(el, index) {
  6. addEvent(el, 'click', function() {
  7. that.highlight(index);
  8. });
  9. });
  10. }
  11. });

Большенство фреймворков имеют свой вариант реализации метода forEach. Однако, если вы не используете фреймворки, вам стоит знать о том как использовать замыкания в циклах.

Rate It! (Average 4.25, 4 votes)

Related Posts

0 Responses to Как замыкания ломают циклы

Leave a Reply

Mail will not be published