Registering event handlers in JavaScript when iterating through document elements


This question is not a duplicate, as it affects not only closures, but also other common errors in JS, as well as their elimination using the new features of the ES 2015 specification.

When iterating through elements, it is not possible to correctly register the event handlers click of the elements being iterated. The events of all iterated elements are registered by an anonymous function intended for the most recent element.

By design. 1 When hovering over fieldset the delete icon for each message is highlighted, and when you move the mouse away, the delete icon for the corresponding message fades out again. 2 When you hover over the delete icon, a prompt pops up with the time of the corresponding message. 3 When you click on the icon of the corresponding deletion message, an alert pops up with the time of the corresponding message.

I have. 1 When you hover over any fieldset message, the delete icon of the last message is highlighted, when you move the mouse, the delete icon is highlighted, the last message, fades out again. 2 This is the only thing that works correctly. 3 When you click on the delete icon of any message, an alert pops up with the time of the last message.

What am I doing wrong, what do I not understand here, and how do I do it right?

window.addEventListener('load',function()
{
 var imgs=document.getElementsByClassName('right')[0].getElementsByClassName('del');
 for(var i=0,l=imgs.length;i<l;i++)
 {
  var img=imgs[i];
  var doc=document.getElementById('mes-'+img.dataset.date+'-'+img.dataset.fid);
  doc.addEventListener('mouseover',function(){img.style.opacity=1;});
  doc.addEventListener('mouseout',function(){img.removeAttribute('style');});
  img.setAttribute('title','Delete '+img.dataset.title+' message');
  img.addEventListener('click',function()
  {
   if(!isNaN(img.dataset.date)&&img.dataset.date>0&&!isNaN(img.dataset.fid)&&img.dataset.fid>0&&img.dataset.title)
   alert('Are you sure you want to delete '+img.dataset.title+' message?');
  });
 }
});
*
{
  margin:0;
  padding:0;
  border:0;
  border-radius:5px;
  transition:all 0.2s linear;
}
html,body
{
  height:100%;
  min-height:100%;
}
body
{
  background-color:#fff;
  color:#000;
  font:12px/18px Arial,Helvetica,sans-serif;
  margin:0 auto;
}
fieldset
{
  border:1px solid #ccc;
}
legend
{
  font-weight:bold;
}
div.message
{
  min-width:400px;
  width:400px;
  max-height:400px;
  margin:0 auto;
  padding:10px;
  text-align:center;
}
div.message div.left, div.message div.right
{
  float:left;
  height:100%;
  overflow:auto;
}
div.message div.right
{
  width:250px;
  text-align:center;
}
div.message div.right hr
{
  border-top:1px solid #ccc;
  margin-top:10px;
}
div.message div.right hr+span
{
  background-color:#fff;
  font-weight:bold;
  padding:0 10px;
  position:relative;
  bottom:10px;
}
div.message div.right fieldset
{
  margin:0 0 10px;
  padding:10px;
  text-align:left;
  position:relative;
}
div.message div.right fieldset.from
{
  margin-right:50px;
  border-color:#6f6;
  background-color:#dfd;
  color:#040;
}
div.message div.right fieldset.to
{
  margin-left:50px;
  border-color:#66f;
  background-color:#ddf;
  color:#004;
}
div.message div.right fieldset img.del
{
  width:16px;
  top:0;
  right:8px;
}
div.message img.del
{
  position:absolute;
  opacity:0.3;
}
div.message img.del:hover
{
  cursor:pointer;
  opacity:1;
}
<!DOCTYPE html>
<html lang="ru-ru" dir="ltr">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
		<title>Test</title>
	</head>
	<body>	
		<div class="message">
			<div class="left"></div>
			<div class="right">
				<hr/><span>08.10.2016</span>
				<fieldset class="from" id="mes-1475888362-1">
					<legend>Ilya Indigo&nbsp;<span>03:59</span></legend>
					<img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del"/>
					<p>Тестовое сообщение<br/>новая строка<br/>ещё одна строка<br/>и ещё одна строка.</p>
				</fieldset>
				<fieldset class="from" id="mes-1475888608-1">
					<legend>Ilya Indigo&nbsp;<span>04:03</span></legend>
					<img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del"/>
					<p>Ещё одно.</p>
				</fieldset>
				<fieldset class="to" id="mes-1475889423-2">
					<legend>Ilya Indigo 2&nbsp;<span>04:17</span></legend>
					<img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del"/>
					<p>Отвечаю.</p>
				</fieldset>
				<fieldset class="from" id="mes-1475892511-1">
					<legend>Ilya Indigo&nbsp;<span>05:08</span></legend>
					<img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del"/>
					<p>Ещё одно.</p>
				</fieldset>
			</div>
		</div>
	</body>
</html>

Answer: The easiest and fastest way to declare all variables is with let instead of var.

Author: Илья Индиго, 2016-10-11

3 answers

At first glance, it may seem that this is a classic closure problem, and it really is.

But! The solution here is much simpler: since the handler implies working with the clicked element, the closure problem is solved by using this, instead of the img variable. Since, in this case, this - will be just the element that was clicked on.

window.addEventListener('load', function() {
  var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
  for (var i = 0, l = imgs.length; i < l; i++) {
    var img = imgs[i];
    var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
    doc.addEventListener('mouseover', function() {
      // здесь проблема остается, так как обработчик вешается на fieldset
      img.style.opacity = 1;
    });
    doc.addEventListener('mouseout', function() {
      // здесь проблема остается, так как обработчик вешается на fieldset
      img.removeAttribute('style');
    });
    img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
    img.addEventListener('click', function() {
      if (!isNaN(this.dataset.date) && this.dataset.date > 0 && !isNaN(this.dataset.fid) && this.dataset.fid > 0 && this.dataset.title)
        alert('Are you sure you want to delete ' + this.dataset.title + ' message?');
    });
  }
});
legend {
  font-weight: bold;
}
div.message {
  width: 400px;
}
div.message div.right fieldset {
  text-align: left;
  position: relative;
  border-color: #6f6;
  background-color: #dfd;
  color: #040;
}
div.message div.right fieldset.to {
  background-color: #ddf;
}
div.message div.right fieldset img.del {
  width: 16px;
  top: 0;
  right: 8px;
  position: absolute;
  opacity: 0.3;
}
<div class="message">
  <div class="left"></div>
  <div class="right">
    <hr/><span>08.10.2016</span>
    <fieldset class="from" id="mes-1475888362-1">
      <legend>Ilya Indigo&nbsp;<span>03:59</span>
      </legend>
      <img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
      <p>Тестовое сообщение
        <br/>новая строка
        <br/>ещё одна строка
        <br/>и ещё одна строка.</p>
    </fieldset>
    <fieldset class="from" id="mes-1475888608-1">
      <legend>Ilya Indigo&nbsp;<span>04:03</span>
      </legend>
      <img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
      <p>Ещё одно.</p>
    </fieldset>
    <fieldset class="to" id="mes-1475889423-2">
      <legend>Ilya Indigo 2&nbsp;<span>04:17</span>
      </legend>
      <img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
      <p>Отвечаю.</p>
    </fieldset>
    <fieldset class="from" id="mes-1475892511-1">
      <legend>Ilya Indigo&nbsp;<span>05:08</span>
      </legend>
      <img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
      <p>Ещё одно.</p>
    </fieldset>
  </div>
</div>

Reasons mistakes:

Variables declared with the keyword var they have a functional scope. This means that neither are available inside the entire function in which and inside the declared nested functions are declared.

Even though the variable declaration is inside for will be declared only one variable, the value of which will change.

Since the handlers are not executed immediately inside when a certain event occurs, they take the value of the specified variable at the time of execution, and this, in this case, is the value assigned at the last iteration of the loop.

Possible solutions:

  1. Creating a function that creates a specific handler: in this case, the problem is solved by creating a factory function that will create a handler function based on the parameter. A special case of this approach is the use of IIFE, when the factory function is called immediately upon declaration

    Example with a named factory:

    function createClickHandler(img) {
      return function() {
        if (!isNaN(img.dataset.date) && img.dataset.date > 0 && !isNaN(img.dataset.fid) && img.dataset.fid > 0 && img.dataset.title)
          alert('Are you sure you want to delete ' + img.dataset.title + ' message?');
      };
    }
    
    function createMouseoverHandler(img) {
      return function() {
        img.style.opacity = 1;
      };
    }
    
    function createMouseoutHandler(img) {
      return function() {
        img.removeAttribute('style');
      };
    }
    
    window.addEventListener('load', function() {
      var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
      for (var i = 0, l = imgs.length; i < l; i++) {
        var img = imgs[i];
        var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
        doc.addEventListener('mouseover', createMouseoverHandler(img));
        doc.addEventListener('mouseout', createMouseoutHandler(img));
        img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
        img.addEventListener('click', createClickHandler(img));
      }
    });
    
     
    legend {
      font-weight: bold;
    }
    div.message {
      width: 400px;
    }
    div.message div.right fieldset {
      text-align: left;
      position: relative;
      border-color: #6f6;
      background-color: #dfd;
      color: #040;
    }
    div.message div.right fieldset.to {
      background-color: #ddf;
    }
    div.message div.right fieldset img.del {
      width: 16px;
      top: 0;
      right: 8px;
      position: absolute;
      opacity: 0.3;
    }
    
     
    <div class="message">
      <div class="left"></div>
      <div class="right">
        <hr/><span>08.10.2016</span>
        <fieldset class="from" id="mes-1475888362-1">
          <legend>Ilya Indigo&nbsp;<span>03:59</span>
          </legend>
          <img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Тестовое сообщение
            <br/>новая строка
            <br/>ещё одна строка
            <br/>и ещё одна строка.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475888608-1">
          <legend>Ilya Indigo&nbsp;<span>04:03</span>
          </legend>
          <img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
        <fieldset class="to" id="mes-1475889423-2">
          <legend>Ilya Indigo 2&nbsp;<span>04:17</span>
          </legend>
          <img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Отвечаю.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475892511-1">
          <legend>Ilya Indigo&nbsp;<span>05:08</span>
          </legend>
          <img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
      </div>
    </div>
    
     

    Example with IIFE:

    window.addEventListener('load', function() {
      var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
      for (var i = 0, l = imgs.length; i < l; i++) {
        var img = imgs[i];
        var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
        doc.addEventListener('mouseover', function createMouseoverHandler(imgParam) {
          return function() {
            imgParam.style.opacity = 1;
          };
        }(img));
        doc.addEventListener('mouseout', function createMouseoutHandler(imgParam) {
          return function() {
            imgParam.removeAttribute('style');
          };
        }(img));
        img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
        img.addEventListener('click', function createClickHandler(imgParam) {
          return function() {
            if (!isNaN(imgParam.dataset.date) && imgParam.dataset.date > 0 && !isNaN(imgParam.dataset.fid) && imgParam.dataset.fid > 0 && imgParam.dataset.title)
              alert('Are you sure you want to delete ' + imgParam.dataset.title + ' message?');
          };
        }(img));
      }
    });
    
     
    legend {
      font-weight: bold;
    }
    div.message {
      width: 400px;
    }
    div.message div.right fieldset {
      text-align: left;
      position: relative;
      border-color: #6f6;
      background-color: #dfd;
      color: #040;
    }
    div.message div.right fieldset.to {
      background-color: #ddf;
    }
    div.message div.right fieldset img.del {
      width: 16px;
      top: 0;
      right: 8px;
      position: absolute;
      opacity: 0.3;
    }
    
     
    <div class="message">
      <div class="left"></div>
      <div class="right">
        <hr/><span>08.10.2016</span>
        <fieldset class="from" id="mes-1475888362-1">
          <legend>Ilya Indigo&nbsp;<span>03:59</span>
          </legend>
          <img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Тестовое сообщение
            <br/>новая строка
            <br/>ещё одна строка
            <br/>и ещё одна строка.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475888608-1">
          <legend>Ilya Indigo&nbsp;<span>04:03</span>
          </legend>
          <img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
        <fieldset class="to" id="mes-1475889423-2">
          <legend>Ilya Indigo 2&nbsp;<span>04:17</span>
          </legend>
          <img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Отвечаю.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475892511-1">
          <legend>Ilya Indigo&nbsp;<span>05:08</span>
          </legend>
          <img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
      </div>
    </div>
    
     
  2. Using let/const. Using these keywords, which appeared in ES2015, allows you to declare variables whose scope is the current block. This means that at each iteration of the loop will be created by new variable, so the correct value of {[28] will be used during handler execution]}

    window.addEventListener('load', function() {
      var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
      for (var i = 0, l = imgs.length; i < l; i++) {
        let img = imgs[i];
        var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
        doc.addEventListener('mouseover', function() {
          img.style.opacity = 1;
        });
        doc.addEventListener('mouseout', function() {
          img.removeAttribute('style');
        });
        img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
        img.addEventListener('click', function() {
          if (!isNaN(img.dataset.date) && img.dataset.date > 0 && !isNaN(img.dataset.fid) && img.dataset.fid > 0 && img.dataset.title)
            alert('Are you sure you want to delete ' + img.dataset.title + ' message?');
        });
      }
    });
    
     
    legend {
      font-weight: bold;
    }
    div.message {
      width: 400px;
    }
    div.message div.right fieldset {
      text-align: left;
      position: relative;
      border-color: #6f6;
      background-color: #dfd;
      color: #040;
    }
    div.message div.right fieldset.to {
      background-color: #ddf;
    }
    div.message div.right fieldset img.del {
      width: 16px;
      top: 0;
      right: 8px;
      position: absolute;
      opacity: 0.3;
    }
    
     
    <div class="message">
      <div class="left"></div>
      <div class="right">
        <hr/><span>08.10.2016</span>
        <fieldset class="from" id="mes-1475888362-1">
          <legend>Ilya Indigo&nbsp;<span>03:59</span>
          </legend>
          <img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Тестовое сообщение
            <br/>новая строка
            <br/>ещё одна строка
            <br/>и ещё одна строка.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475888608-1">
          <legend>Ilya Indigo&nbsp;<span>04:03</span>
          </legend>
          <img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
        <fieldset class="to" id="mes-1475889423-2">
          <legend>Ilya Indigo 2&nbsp;<span>04:17</span>
          </legend>
          <img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Отвечаю.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475892511-1">
          <legend>Ilya Indigo&nbsp;<span>05:08</span>
          </legend>
          <img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
      </div>
    </div>
    
     
  3. Use instead of for, functions forEach. Since in this case, the function parameter will be used in the closure, and not a variable, the value at the time of execution of the event handler will be correct. Because the function getElementsByClassName returns not an array, but a collection. You can't use the forEach function for it. But you can convert this collection to an array, or use call.

    Example:

    window.addEventListener('load', function() {
      var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
      [...imgs].forEach(function(img) {
        var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
        doc.addEventListener('mouseover', function() {
          img.style.opacity = 1;
        });
        doc.addEventListener('mouseout', function() {
          img.removeAttribute('style');
        });
        img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
        img.addEventListener('click', function() {
          if (!isNaN(img.dataset.date) && img.dataset.date > 0 && !isNaN(img.dataset.fid) && img.dataset.fid > 0 && img.dataset.title)
            alert('Are you sure you want to delete ' + img.dataset.title + ' message?');
        });
    
      });
    });
    
     
    legend {
      font-weight: bold;
    }
    div.message {
      width: 400px;
    }
    div.message div.right fieldset {
      text-align: left;
      position: relative;
      border-color: #6f6;
      background-color: #dfd;
      color: #040;
    }
    div.message div.right fieldset.to {
      background-color: #ddf;
    }
    div.message div.right fieldset img.del {
      width: 16px;
      top: 0;
      right: 8px;
      position: absolute;
      opacity: 0.3;
    }
    
     
    <div class="message">
      <div class="left"></div>
      <div class="right">
        <hr/><span>08.10.2016</span>
        <fieldset class="from" id="mes-1475888362-1">
          <legend>Ilya Indigo&nbsp;<span>03:59</span>
          </legend>
          <img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Тестовое сообщение
            <br/>новая строка
            <br/>ещё одна строка
            <br/>и ещё одна строка.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475888608-1">
          <legend>Ilya Indigo&nbsp;<span>04:03</span>
          </legend>
          <img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
        <fieldset class="to" id="mes-1475889423-2">
          <legend>Ilya Indigo 2&nbsp;<span>04:17</span>
          </legend>
          <img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Отвечаю.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475892511-1">
          <legend>Ilya Indigo&nbsp;<span>05:08</span>
          </legend>
          <img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
      </div>
    </div>
    
     
  4. Use this in handlers, and search for elements relative to the this element. Since, when adding a handler using addEventListener, inside the handler this will point to the element to which the handler was added, then when you click on the image, you can use this instead of closing it. In the case when the handler is added to fieldset, you can find the desired image inside this, for example, using querySelector

    Example

    window.addEventListener('load', function() {
      var imgs = document.getElementsByClassName('right')[0].getElementsByClassName('del');
      for (var i = 0; i < imgs.length; i++) {
        var img = imgs[i];
        var doc = document.getElementById('mes-' + img.dataset.date + '-' + img.dataset.fid);
        doc.addEventListener('mouseover', function() {
          this.querySelector('img').style.opacity = 1;
        });
        doc.addEventListener('mouseout', function() {
          this.querySelector('img').removeAttribute('style');
        });
        img.setAttribute('title', 'Delete ' + img.dataset.title + ' message');
        img.addEventListener('click', function() {
          if (!isNaN(this.dataset.date) && this.dataset.date > 0 && !isNaN(this.dataset.fid) && this.dataset.fid > 0 && this.dataset.title)
            alert('Are you sure you want to delete ' + this.dataset.title + ' message?');
        });
    
      }
    });
    
     
    legend {
      font-weight: bold;
    }
    div.message {
      width: 400px;
    }
    div.message div.right fieldset {
      text-align: left;
      position: relative;
      border-color: #6f6;
      background-color: #dfd;
      color: #040;
    }
    div.message div.right fieldset.to {
      background-color: #ddf;
    }
    div.message div.right fieldset img.del {
      width: 16px;
      top: 0;
      right: 8px;
      position: absolute;
      opacity: 0.3;
    }
    
     
    <div class="message">
      <div class="right">
        <fieldset class="from" id="mes-1475888362-1">
          <legend>Ilya Indigo&nbsp;<span>03:59</span>
          </legend>
          <img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Тестовое сообщение
            <br/>новая строка
            <br/>ещё одна строка
            <br/>и ещё одна строка.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475888608-1">
          <legend>Ilya Indigo&nbsp;<span>04:03</span>
          </legend>
          <img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
        <fieldset class="to" id="mes-1475889423-2">
          <legend>Ilya Indigo 2&nbsp;<span>04:17</span>
          </legend>
          <img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Отвечаю.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475892511-1">
          <legend>Ilya Indigo&nbsp;<span>05:08</span>
          </legend>
          <img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
      </div>
    </div>
    
     
  5. Event delegation instead of a loop. In this approach, the handler is not hung on a specific the picture, and on the container where all the pictures are. Inside the handler, it is already determined which element was clicked on and the necessary functions are performed.

    Example

    window.addEventListener('load', function() {
      var right = document.getElementsByClassName('right')[0];
      right.addEventListener('click', function(e) {
        if (e.target.className === 'del') {
          if (!isNaN(e.target.dataset.date) && e.target.dataset.date > 0 && !isNaN(e.target.dataset.fid) && e.target.dataset.fid > 0 && e.target.dataset.title)
            alert('Are you sure you want to delete ' + e.target.dataset.title + ' message?');
        }
      });
      [...right.querySelectorAll('img')].forEach(img => img.setAttribute('title', 'Delete ' + img.dataset.title + ' message'));
      var fieldSets = right.querySelectorAll('fieldset');
      for (var i = 0; i < fieldSets.length; i++) {
        fieldSets[i].addEventListener('mouseover', function() {
          this.querySelector('img').style.opacity = 1;
        });
        fieldSets[i].addEventListener('mouseout', function() {
          this.querySelector('img').removeAttribute('style');
        });
      }
    });
    
     
    legend {
      font-weight: bold;
    }
    div.message {
      width: 400px;
    }
    div.message div.right fieldset {
      text-align: left;
      position: relative;
      border-color: #6f6;
      background-color: #dfd;
      color: #040;
    }
    div.message div.right fieldset.to {
      background-color: #ddf;
    }
    div.message div.right fieldset img.del {
      width: 16px;
      top: 0;
      right: 8px;
      position: absolute;
      opacity: 0.3;
    }
    
     
    <div class="message">
      <div class="right">
        <fieldset class="from" id="mes-1475888362-1">
          <legend>Ilya Indigo&nbsp;<span>03:59</span>
          </legend>
          <img class="del" data-date="1475888362" data-fid="1" data-title="03:59" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Тестовое сообщение
            <br/>новая строка
            <br/>ещё одна строка
            <br/>и ещё одна строка.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475888608-1">
          <legend>Ilya Indigo&nbsp;<span>04:03</span>
          </legend>
          <img class="del" data-date="1475888608" data-fid="1" data-title="04:03" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
        <fieldset class="to" id="mes-1475889423-2">
          <legend>Ilya Indigo 2&nbsp;<span>04:17</span>
          </legend>
          <img class="del" data-date="1475889423" data-fid="2" data-title="04:17" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Отвечаю.</p>
        </fieldset>
        <fieldset class="from" id="mes-1475892511-1">
          <legend>Ilya Indigo&nbsp;<span>05:08</span>
          </legend>
          <img class="del" data-date="1475892511" data-fid="1" data-title="05:08" src="http://dolinasnov.com/uploads/delete.png" alt="del" />
          <p>Ещё одно.</p>
        </fieldset>
      </div>
    </div>
    
     
 3
Author: Grundy, 2016-12-12 11:40:00

The answer is here. https://javascript.ru/basic/closure#primer-oshibochnogo-ispolzovaniya And the author's introduction "С вопроса "Почему это не работает?" люди обычно начинают изучение замыканий." Simply, as never before, hits the spot and hits straight ahead!

A working example will iron out like this.

window.addEventListener('load',function()
{
 var imgs=document.getElementsByClassName('right')[0].getElementsByClassName('del');
 for(var i=0,l=imgs.length;i<l;i++)
 {
  var img=imgs[i];
  var doc=document.getElementById('mes-'+img.dataset.date+'-'+img.dataset.fid);
  doc.addEventListener('mouseover',function(x){return function(){x.style.opacity=1;}}(img));
  doc.addEventListener('mouseout',function(x){return function(){x.removeAttribute('style');}}(img));
  img.setAttribute('title','Delete '+img.dataset.title+' message');
  img.addEventListener('click',function(x)
  {
   return function()
   {
    if(!isNaN(x.dataset.date)&&x.dataset.date>0&&!isNaN(x.dataset.fid)&&x.dataset.fid>0&&x.dataset.title)
     alert('Are you sure you want to delete '+x.dataset.title+' message?');
   }
  }(img));
 }
});

But still, the ECMA Script 2015 standard allows you to use a simpler and more elegant method, declaring variables not through var, but through let.

window.addEventListener('load',function()
{
 let imgs=document.getElementsByClassName('right')[0].getElementsByClassName('del');
 for(let i=0,l=imgs.length;i<l;i++)
 {
  let img=imgs[i];
  let doc=document.getElementById('mes-'+img.dataset.date+'-'+img.dataset.fid);
  doc.addEventListener('mouseover',function(){img.style.opacity=1;});
  doc.addEventListener('mouseout',function(){img.removeAttribute('style');});
  img.setAttribute('title','Delete '+img.dataset.title+' message');
  img.addEventListener('click',function()
  {
   if(!isNaN(img.dataset.date)&&img.dataset.date>0&&!isNaN(img.dataset.fid)&&img.dataset.fid>0&&img.dataset.title)
   alert('Are you sure you want to delete '+img.dataset.title+' message?');
  });
 }
});
 4
Author: Илья Индиго, 2016-10-11 14:31:39

As an example, I can give the following code:

//функция создает объекты в циле, из интерисующих нас параметров box- контейнер в котором находятся DOM эелементы и plate - сами элементы
function createPlates(box, plate, step, speed, bool) {
    var box = document.querySelector(box);
    if(!box) {
        return false;
    } else {
        var plates =  Array.prototype.slice.apply(box.querySelectorAll(plate));
        //в цикле создаем объекты с помощью конструктора (будет описан ниже)
        for(var i = 0; i < plates.length; i++) {
            plates[i] = new ServisePlate (plates[i], step, speed, bool);
        }
        // на контейнер вешаем обработчик на клик
        box.addEventListener('click', function(event) {  
            // И перебираем массив plates с созданными объектами 
            plates.forEach(function(element, i) {  
                // если объект события совпадает со ссылкой на DOM элемент, сохраненной в объекте, запускаем его метод.
                if(event.target == plates[i].elem) {
                plates[i].disclose();  
                // Здесь проверка на клик по дочернему элементу необходимого нам элемента
                }else if(plates[i].elem.contains(event.target)){
                    for(var j = 0; j < plates.length; j++) {
                        if(plates[j].elem.style.height) {
                            plates[j].disclose();
                        }
                    }
                    plates[i].disclose();
                }else {
                    return false;
                }
            });
        });
    }
}
    createPlates('.servises', '.servises_content', 5, 20, true);  

The actual constructor itself:

function ServisePlate(plate, step, speed, headSet) {
        this.elem = plate;
        this.header = plate.querySelector('h3');
        this.body = plate.querySelector('div');
        this.link = plate.querySelector('a');
        if(this.link) {
            this.bodySize = this.body.offsetHeight + this.link.offsetHeight;
        }else{
            this.bodySize = this.body.offsetHeight;
        }
        this.settings = {
            step: step,
            speed: speed
        }
        this.initialSize = this.elem.offsetHeight;
        var self = this;
        function setHead() {
            if(29 < self.header.innerHTML.length) {
            self.header.classList.add('servises_content_header--p18');
            }
            if(59 < self.header.innerHTML.length) {
                self.header.classList.remove('servises_content_header--p18');
                self.header.classList.add('servises_content_header--p1');
            }
        }
        if(headSet === true) {
            setHead();
        }
    }  

And its disclose method, stored in the prototype:

ServisePlate.prototype.disclose = function() {
        function showBody() {
            if(this.elem.offsetHeight < (this.bodySize + this.initialSize)) {
                this.elem.style.height = this.elem.offsetHeight + this.settings.step + 'px';
                setTimeout(showBody.bind(this), this.settings.speed);
            }

        }
        function hideBody() {
            if(this.elem.offsetHeight > this.initialSize) {
                this.elem.style.height = this.elem.offsetHeight - this.settings.step + 'px';
                setTimeout(hideBody.bind(this), this.settings.speed);
            }else {
                this.elem.style.height = '';
            }
        }
        if(this.elem.offsetHeight == this.initialSize || this.elem.offsetHeight >= (this.bodySize + this.initialSize)) {
            if(this.elem.offsetHeight == this.initialSize) {
                showBody.call(this);
            } else {
                hideBody.call(this);
            }
       }
    }    

True, some parts of the code I myself have suspicions of correctness, since I already said that my js pulls on 3 with a minus. But the point is not in the correctness of certain sections, but in the approach as a whole.

 3
Author: pepel_xD, 2016-10-11 09:53:04