Дълбоко гмуркане на JS събития.

Нека разберем обработката на JS събития. stopPropagation и stopImmediatePropagation и preventDefault.

JS събитията изглеждат ясни, но става малко трудно, когато има вложени елементи като div вътре в div, който пътува (или се разпространява) през елементите. Това е моментът, в който се опитваме да разрешим проблема с помощта на stopPropagation() и/или preventDefault(). Нека поговорим подробно за всеки от тях и кога да го използваме.

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

Стилове на събития (заснемане и балончета)

someElement.addEventListener('click', clickHandler, { capture: true | false });

Обектът options е третият параметър, който позволява улавяне на събитие като true или false.

Заснемане на събития

Нека поговорим за манипулатора на събития, който „слуша във фазата на заснемане“? За да разберем това, трябва да видим как възникват събитията и как се движат.

Всички събития започват с прозореца и първо преминават през фазата на заснемане. Когато дадено събитие е изпратено, то започва с прозореца и се движи надолу към първия целеви елемент. Това се случва дори ако слушате във фазата на „бълбукане“.

Помислете за следното HTML маркиране и JS.

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>

Ще добавим слушател на събития към елемента div с идентификатор „C“.

document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('#C was clicked');
  },
  true,
);

Дори ако се опитате да щракнете върху някой от елементите, това ще утеши „#C е щракнато“.

Причината е потокът от събития, който е както следва.

прозорец → документ → Html → тяло → … докато достигне целевия елемент. След това събитието за щракване ще се разпространи директно, докато не бъде открит целевият му елемент.

Ако нещо слуша за събитие с щракване върху елемента body във фазата на заснемане? Ако да, тогава ще се задейства подходящият манипулатор на събития.

И така, в горния пример слушателят на събития е прикачен към идентификатора на елемента „C“, но ще започне от обекта прозорец, след това документ, Html, тяло, #A, #B,#C, тъй като регистрираме фазата на заснемане.

Накратко, улавянето на събития протича отгоре → отдолу.

Сега може би си мислите, че това е краят на събитията. И така, сега идва обратното, когато събитията се връщат обратно към върха, тази фаза се нарича бълбукане на събития.

Възпроизвеждане на събития

Сега браузърът ще попита „Слуша ли нещо за събитие с кликване на #C във фазата на бълбукане?“

Напълно възможно е да се прослушват щраквания (или всеки тип събитие) в и двете фази на заснемане и на бълбукане. И ако бяхте свързали манипулатори на събития и в двете фази (например чрез извикване на .addEventListener() два пъти, веднъж с capture = true и веднъж с capture = false), тогава да, и двата манипулатора на събития абсолютно щяха да се задействат за един и същ елемент.

Но също така е важно да се отбележи, че огънят е в различни фази (една във фазата на улавяне и една във фазата на кипене).

И така, в горния пример слушателят на събития е прикачен към елемента id „C“, но ще започне от#B,#A, тялото, Html, документ и прозорец регистрират фазата на бълбукане.

Накратко, улавянето на събития протича ототдолу → отгоре.

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>

Нека го видим в действие и за двете фази.

document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in capturing phase');
  },
  true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in capturing phase');
  },
  true,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in capturing phase');
  },
  true,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in capturing phase');
  },
  true,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in capturing phase');
  },
  true,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in capturing phase');
  },
  true,
);

document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in bubbling phase');
  },
  false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in bubbling phase');
  },
  false,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in bubbling phase');
  },
  false,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in bubbling phase');
  },
  false,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in bubbling phase');
  },
  false,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in bubbling phase');
  },
  false,
);

Ако видите изявлението на конзолата, то ще се регистрира както е показано по-долу.

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"

Освен това си играете с кода.

Нека се потопим дълбоко в JS събитията сега.

event.stopPropagation()

stopPropagation() Методът може да бъде извикан на (повечето) собствени DOM събития. Събития като focus, blur, load, scroll и няколко други попадат в тази категория.

Така че, ако stopPropagation() се извика някъде във фазата на улавяне, събитието никога няма да стигне до целевата фаза или фазата на кипене.

Ако го извикате във фазата на бълбукане, то вече ще е преминало през фазата на улавяне, но ще спре да „бълбука“ от точката, в която сте го извикали.

Да кажем, че ако извикате stopPropagation на елемент #B, той ще се регистрира както е показано по-долу.

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"

event.stopImmediatePropagation()

Този метод се прилага, когато имате множество събития, свързани към един елемент.

<html>
  <body>
    <div id="B">I am the #B element</div>
  </body>
</html>

Добавяне на слушателя на събитие към #B.

document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('When #B is clicked, I shall run first!');
  },
  false,
);

document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('When #B is clicked, I shall run second!');
    e.stopImmediatePropagation();
  },
  false,
);

document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('When #B is clicked, I would have run third, if not for stopImmediatePropagation');
  },
  false,
);

Това ще регистрира конзолата, както е показано по-долу.

"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"

event.preventDefault()

Мислете за това като за „предотвратяване на действието по подразбиране“.

Нека започнем с много прост пример за разбиране.

Какво очаквате да се случи, когато щракнете върху връзка на уеб страница? Очевидно очаквате браузърът да навигира до URL адреса, посочен от тази връзка.

В този случай елементът е anchor таг и събитието е събитие click. Тази комбинация (<a> + click) има „действие по подразбиране“ за навигиране до href на връзката.

Ами ако искате да попречите на браузъра да изпълнява това действие по подразбиране? Тоест, да предположим, че искате да попречите на браузъра да навигира до URL адреса, определен от атрибута href на елемента <a>?

Това е, което preventDefault() ще направи за вас.

<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
  'click',
  function (e) {
    e.preventDefault();
    console.log('Maybe we should just play some of their music right here instead?');
  },
  false,
);

Така че има няколко случая на употреба, при които се използва preventDefault.

  1. <form> елемент + събитие „изпращане“.
  2. <a> елемент + събитие „щракване“.
  3. document + събитие 'mousewheel'
  4. document + събитие 'keydown'
  5. document + събитие 'mousedown'
  6. <input> елемент + събитие "натискане на клавиш".
  7. document + събитие 'contextmenu'

Въпреки че това не е пълният списък, той ще ви даде добра представа къде да използвате метода за събитие на preventDefault.

Надяваме се, че тази статия може да ви даде някаква представа кога/къде да използвате методите за събития.

Честито кодиране. Продължавай да учиш. Продължавайте да изследвате.