프론트엔드 면접 질문 정리 [JS #1 event Propagation & Delegation]

전창현·2020년 8월 23일
0

Event bubbling and capturing

  1. Bubbling
  2. Capturing
  3. Event delegation

1. Bubbling?

  • element에서 event가 발생할 때, 해당 element의 handler를 실행시키고, html/document/window element까지의 ancestor elements를 따라 동일한 event에 대해 등록된 handler를 순차적으로  실행시킨다.
<div onclick="console.log('lv3')">
	<p onclick="console.log('lv2')">
		hi im <em onclick="console.log('lv1')">Jun</em>
	</p>
</div>

lv1을 클릭하면, lv1 > lv2 > lv3 handler가 순차적으로 실행되며

물에서 거품이 생겨나듯이 inner El로부터 parent Els에 대해 event가 bubble된다하여 이를 event bubbling이라고 한다.

이 떄, 최초로 event를 촉발시킨 element를 target element라 하며, 외부 handler에서는  event.target으로 access할 수 있다. (이는 event.currentTarget = this 와 구분된다. )

가령 lv3의 inner element에서 발생한 event를 시작으로 bubbling에 의해 lv3의 onclick handler가 실행될 때,

handler 함수 내에서 event.target은 event를 최초로 발생시킨 inner element가 되는 반면,

event.currentTarget(this)는 항상 lv3 Element를 가리킨다.

ex) lv2 클릭에 의해 lv3 handler가 실행될 경우

// in lv3 handler function,

event.target === lv2Element //true

event.currentTarget === lv3Element //true

ex) lv3 클릭에 의해 lv3 handler가 실행될 경우

// in lv3 handler,

event.target === lv3Element //true

event.currentTarget === lv3Element //true

Bubbling을 멈추는 방법

event bubbling에 의해 의도치 않은 outer element의 handler function이 실행될 수 있는데,

이는 event.stopPropagation() 메소드를 통해 방지할 수 있다.

const input = document.querySelector("input");

input.addEventListener("click", () => {

	console.log("hi");
	
	event.stopPropagation();

});

event.stopPropagation()은 필요 시에만 사용해야 한다.

Capturing?

DOM Events 표준에서는 event propagation을 3단계로 나누어 설명한다.

1. Capturing phase : event가 inner element로 go down.

2. Target phase : capturing(going down)에 의해 target element에 event가 도착.

3. Bubbling phase : target element를 기점으로 event가 bubbles up.

<div>
	<form>
		<input />
	</form>
</div>

input을 클릭할 경우,

1. Capturing phase

window/document/html에서부터 input까지 event가 DOM tree를 따라 순차적으로 전달되며,

2. target phase

event가 target element에 도달할 경우

3. Bubbling phase

event가 bubbling에 의해 window까지 전달되며 각 element에서 handler function을 실행한다.

browser는 default option으로 bubbling phase에서만 event handler를 실행한다. 

따라서 capturing phase는 invisible하기 때문에, 이 개념을 사용하는 경우는 드물다.

만약 bubbling phase가 아닌 capturing phase에서 event handler를 실행해야될 경우에는 다음과 같이 addEventListener에서 capture option을 설정한다.

<body>

<div>
	<form>
		<input />
	</form>
</div>

<script>

for (let i of document.body.querySelectorAll('*')) {

	console.log(i);

	i.addEventListener('click', e => {
		alert(`capturing: ${i.tagName}`)
	}, { capture: true });

	i.addEventListener('click', e => console.log(`bubbling: ${i.tagName}`));
}

</script>

</body>

input을 클릭하면, capture: true로 설정된 EventListener에 의해 capturing phase에서 핸들러를 실행해 window부터 e.target element까지 동일한 event에 등록된 handler를 실행한다.

capturing option으로 등록된 handler를 remove할 경우, removeEventListener(..., true)를 사용한다.

Event delegation

event propagation을 이용해

childs에서 발생할 수 있는 event에 대한 handler function을 각 child에 모두 등록하지않고,

childs의 ancestor element에 handler를 등록해 하위 childs의 event에 대한 handler를 처리하게끔 설계할 수 있다.

<div>
	<input value="1" />
	<input value="2" />
	<input value="3" />
	<input value="4" />
	<input value="5" />
	<input value="6" />
	<input value="7" />
</div>

<script>

document.querySelector('div').addEventListener('click', (e) => {

	console.log(e.target.value);
	console.log(e.currentTarget, e.target);
	
})

document.querySelector('div').addEventListener('click', (e) => {

	console.log(e.target.value);
	console.log(e.currentTarget, e.target);
	
}, { capture: true })

</script>

위의 구조에서 div에 event handler를 등록하면

input(e.target)을 클릭했을 때, bubbling/capturing에 의해 div(e.currentTarget)의 handler가 실행된다.

이 때, e.target은 event가 발생한 곳의 most inner Element가 된다.

reference

https://javascript.info/event-delegation

0개의 댓글