JavaScript의 이벤트 라우팅 버블링 프로세스 및 위임 메커니즘 이해

黄舟
풀어 주다: 2017-02-27 14:05:52
원래의
1203명이 탐색했습니다.

순수한 CSS를 사용하여 구현할 때. 저는 기능을 완성하기 위해 JavaScript와 스타일 클래스를 사용하기 시작했습니다.

그런 다음 몇 가지 아이디어가 있습니다. 위임된 이벤트(이벤트 위임)를 사용하고 싶지만 종속성을 갖고 싶지 않고 jQuery를 포함한 모든 라이브러리를 연결하고 싶습니다. 이벤트 위임을 직접 구현해야 합니다.

먼저 이벤트 위임이 무엇인지 살펴볼까요? 작동 방식과 이 메커니즘을 구현하는 방법입니다.

좋습니다. 어떤 문제가 해결되나요?

먼저 간단한 예를 살펴보겠습니다.

한 번에 하나의 버튼을 클릭하고 클릭한 상태를 "활성"으로 설정하려는 버튼 세트가 있다고 가정해 보겠습니다. 다시 클릭하면 활성화가 취소됩니다.

그런 다음 몇 가지 HTML을 작성할 수 있습니다.

<ul class="toolbar">
  <li><button class="btn">Pencil</button></li>
  <li><button class="btn">Pen</button></li>
  <li><button class="btn">Eraser</button></li>
</ul>
로그인 후 복사
로그인 후 복사

일부 표준 Javascript 이벤트를 사용하여 위 논리를 처리할 수 있습니다.

var buttons = document.querySelectorAll(".toolbar .btn");
for(var i = 0; i < buttons.length; i++) {
  var button = buttons[i];
  button.addEventListener("click", function() {
    if(!button.classList.contains("active"))
      button.classList.add("active");
    else
      button.classList.remove("active");
  });
}
로그인 후 복사

좋아 보이지만 그렇지 않습니다. 실제로 예상대로 작동합니다.

클로저의 함정

JavaScript 개발 경험이 있다면 이 문제는 분명할 것입니다. <…

첫 번째 루프는 첫 번째 버튼을 가리킨 다음 두 번째 버튼을 가리킵니다. 그러나 클릭하면 버튼 변수가 항상 마지막 버튼 요소를 가리킵니다. 이것이 문제입니다.

우리에게 필요한 것은 안정적인 범위입니다.

var buttons = document.querySelectorAll(".toolbar button");
var createToolbarButtonHandler = function(button) {
  return function() {
    if(!button.classList.contains("active"))
      button.classList.add("active");
    else
      button.classList.remove("active");
  };
};

for(var i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener("click", createToolBarButtonHandler(buttons[i]));
}
로그인 후 복사

참고* 위 코드의 구조는 약간 복잡합니다. 아래와 같이 간단히 클로저를 사용하여 현재 버튼 변수를 닫고 저장할 수도 있습니다.

var buttons = document.querySelectorAll(".toolbar .btn");

for(var i = 0; i < buttons.length; i++) {
  (function(button) {
    button.addEventListener("click", function() {
      if(!button.classList.contains("active"))
        button.classList.add("active");
      else
        button.classList.remove("active");
    });
  })(buttons[i])
}
로그인 후 복사

이제 작동합니다. 보통 . 올바른 버튼을 가리키는 것은 항상

입니다. 그렇다면 이 해결 방법에는 무엇이 문제일까요?

이 솔루션은 괜찮아 보이지만 실제로는 더 나은 방법이 있습니다.

먼저 처리 기능을 너무 많이 만들었습니다. 이벤트 리스너와 콜백 핸들러는 일치하는 각 .toolbar 버튼에 바인딩됩니다. 버튼이 3개만 있으면 이 리소스 할당을 무시할 수 있습니다.

그런데 1,000개면 어떨까요?

<ul class="toolbar">
  <li><button id="button_0001">Foo</button></li>
  <li><button id="button_0002">Bar</button></li>
  // ... 997 more elements ...
  <li><button id="button_1000">baz</button></li>
</ul>
로그인 후 복사

충돌도 발생하지 않지만 최선의 해결책은 아닙니다. 우리는 불필요한 기능을 많이 할당합니다. 잠재적으로 수천 건의 호출을 처리하기 위해 한 번만 연결하고 하나의 함수만 바인딩하도록 리팩토링해 보겠습니다.

당시 클릭한 객체를 저장하기 위해 버튼 변수를 닫는 대신 이벤트 객체를 사용하여 당시 클릭한 객체를 가져올 수 있습니다.

이벤트 개체에는 여러 개의 바인딩이 있는 경우 currentTarget을 사용하여 현재 바인딩된 개체를 얻을 수 있습니다. 위 예제의 코드는 다음과 같이 변경할 수 있습니다.

var buttons = document.querySelectorAll(".toolbar button");

var toolbarButtonHandler = function(e) {
  var button = e.currentTarget;
  if(!button.classList.contains("active"))
    button.classList.add("active");
  else
    button.classList.remove("active");
};

for(var i = 0; i < buttons.length; i++) {
  button.addEventListener("click", toolbarButtonHandler);
}
로그인 후 복사

good ! 그러나 이는 단일 함수를 단순화하고 더 읽기 쉽게 만들 뿐이지만 여전히 여러 번 바인딩됩니다.

하지만 우리는 더 잘할 수 있습니다.

이 목록에 일부 버튼을 동적으로 추가한다고 가정해 보겠습니다. 그런 다음 이러한 동적 요소에 대한 이벤트 바인딩도 추가하고 제거합니다. 그런 다음 이러한 처리 기능과 현재 컨텍스트에서 사용되는 변수를 유지해야 하는데 이는 신뢰할 수 없는 것처럼 들립니다.

어쩌면 다른 방법이 있을 수도 있습니다.

먼저 이벤트가 어떻게 작동하고 DOM에서 어떻게 전달되는지 완전히 이해해 봅시다.

이벤트 작동 방식

사용자가 요소를 클릭하면 사용자에게 현재 동작을 알리는 이벤트가 생성됩니다. 이벤트가 전달되는 세 단계는 다음과 같습니다.

    캡처 단계: 캡처
  • 트리거 단계: 대상
  • 버블링 단계: 버블링
  • 이 이벤트는 문서 이전부터 시작하여 현재 이벤트가 클릭한 개체를 찾기 위해 끝까지 진행됩니다. 이벤트가 클릭한 객체에 도달하면 전체 DOM 트리를 종료할 때까지 원래 경로(버블링 프로세스)를 따라 반환됩니다.

HTML 예는 다음과 같습니다.

<html>
<body>
  <ul>
    <li id="li_1"><button id="button_1">Button A</button></li>
    <li id="li_2"><button id="button_2">Button B</button></li>
    <li id="li_3"><button id="button_3">Button C</button></li>
  </ul>
</body>
</html>
로그인 후 복사

버튼 A를 클릭하면 이벤트 경로는 다음과 같습니다.

START
| #document  \
| HTML        |
| BODY         } CAPTURE PHASE
| UL          |
| LI#li_1    /
| BUTTON     <-- TARGET PHASE
| LI#li_1    \
| UL          |
| BODY         } BUBBLING PHASE 
| HTML        |
v #document  /
END
로그인 후 복사

이는 이벤트 경로를 클릭하여 생성된 이벤트를 캡처할 수 있음을 의미합니다. 우리는 이 이벤트가 상위 요소인 ul 요소를 통과할 것이라고 확신합니다. 이벤트 처리를 상위 요소에 바인딩하고 솔루션을 단순화할 수 있습니다. 이를 이벤트 위임 및 프록시(위임된 이벤트)라고 합니다.

참고* 실제로 Flash/Silverlight/WPF에서 개발한 이벤트 메커니즘은 매우 유사합니다. Silverlight 3는 버블링 단계만 있는 이전 버전의 IE 이벤트 모델을 사용한다는 점을 제외하면 기본적으로 다음 세 단계로 구성됩니다. (이전 버전의 IE와 SL3의 이벤트 처리는 트리거 객체에서 루트 객체까지 버블링하는 과정만 있었는데, 이는 이벤트 처리 메커니즘을 단순화하기 위함일 수 있습니다.)

  事件委托代理

  委托(代理)事件是那些被绑定到父级元素的事件,但是只有当满足一定匹配条件时才会被挪。

  让我们看一个具体的例子,我们看看上文的那个工具栏的例子:

<ul class="toolbar">
  <li><button class="btn">Pencil</button></li>
  <li><button class="btn">Pen</button></li>
  <li><button class="btn">Eraser</button></li>
</ul>
로그인 후 복사
로그인 후 복사

  因为我们知道单击button元素会冒泡到UL.toolbar元素,让我们将事件处理放到这里试试。我们需要稍微调整一下:

var toolbar = document.querySelector(".toolbar");
toolbar.addEventListener("click", function(e) {
  var button = e.target;
  if(!button.classList.contains("active"))
    button.classList.add("active");
  else
    button.classList.remove("active");
});
로그인 후 복사

  这样我们清理了大量的代码,再也没有循环了。注意我们使用了e.target代替了之前的e.currentTarget。这是因为我们在一个不同的层次上面进行了事件侦听。

  • e.target 是当前触发事件的对象,即用户真正单击到的对象。

  • e.currentTarget 是当前处理事件的对象,即事件绑定的对象。

  在我们的例子中e.currentTarget就是UL.toolbar。

  注* 其实不止事件机制,在整个UI构架上FLEX(不是Flash) /Silverlight /WPF /Android的实现跟WEB也非常相似,都使用XML(HTML)实现模板及元素结构组织,Style(CSS)实现显示样式及UI,脚本(AS3,C#,Java,JS)实现控制。不过Web相对其他平台更加开放,不过历史遗留问题也更多。但是几乎所有的平台都支持Web标准,都内嵌有类似WebView这样的内嵌Web渲染机制,相对各大平台复杂的前端UI框架和学习曲线来说,使用Web技术实现Native APP的前端UI是非常低成本的一项选择。

  

 以上就是理解JavaScript中的事件路由冒泡过程及委托代理机制的内容,更多相关内容请关注PHP中文网(www.php.cn)!


원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿