Algorithm
function solution(elements) { let set = new Set() for(let i=1; i<=elements.length; i++) { let result = 0 if(i===1) { elements.forEach(v=> set.add(v)) continue; } if(i===elements.length){ result = elements.reduce((acc,cur)=> acc+cur,0) set.add(result) continue; } for(let j=0; j<=elements.length-1; j++) { let sum = 0 if(j+i>elements.length-1) { let arr1 = elements.slice(j) let arr2 = elements.slice(0,i-arr1.length) sum = arr1.reduce((acc,cur)=>acc+cur,0)+arr2.reduce((acc,cur)=>acc+cur,0) set.add(sum) continue; } let arr = elements.slice(j,i+j) sum = arr.reduce((acc,cur)=>acc+cur,0) set.add(sum) } } return set.size; }
큰 실수를 했다.. 오늘 이상하게 머리가 복잡했는데
concat
을 써서 연속 된 부분을 뒤에 붙여서 계산해야지 했는데 멍하니 코드를 치다보니concat
을 안쓰고 생각나는대로 적어버렸다.
원래 하려던 식은function solution(elements) { const circular = elements.concat(elements); const set = new Set(); for (let i = 0; i < elements.length; i++) { let sum = 0; for (let j = 0; j < elements.length; j++) { sum += circular[i + j]; set.add(sum); } } return set.size; }
이런식으로
concat
으로 배열이 연속할 수 있게 합친 후 반복문을 돌려 연속되게 쭉 합칠 생각이었는데 무슨 생각으로 풀었는지 모르겠다...
날이 너무 더워서 더위를 먹었는지.. 조금 더 집중할 수 있도록 해야겠다
CS
예시부터 먼저 보자면
<div onclick="alert('div에 할당한 핸들러!')">
<em><code>EM</code>을 클릭했는데도 <code>DIV</code>에 할당한 핸들러가 동작합니다.</em>
</div>
위 핸들러는 <div>
에 할당되어 있지만 <em>
이나<code>
같은 중첩 태그를 클릭해도 동작한다.
버블링의 원리는 간단하다
한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작합니다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작한다
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
3개의 요소가 FORM > DIV > P
형태로 중첩된 구조로 각각 핸들러가 할당되어 있다
가장 안쪽의 <p>
를 클릭하면 순서대로 다음과 같은 일이 벌어지는데
1. <p>
에 할당된 onclick
핸들러가 동작
2. 바깥의 <div>
에 할당된 핸들러가 동작
3. 그 바깥의 <form>
에 할당된 핸들러가 동작
4. document
객체를 만날 때까지, 각 요소에 할당된 onclick
핸들러가 동작
이러한 동작 방식 때문에 <p>
요소를 클릭하면 p
->div
->form
순서로 3개의 얼럿창이 뜨게된다
이런 흐름을 이벤트 버블링으로 이벤트가 가장 깊은 곳에 있는 요소에서 시작해 부모 요소를 거슬러 올라가며 발생하는 모양이 물속 거품과 닮았기 때문에 지어진 이름이다
부모 요소의 핸들러는 이벤트가 정확히 어디서 발생햇는지 등에 대한 자세한 정보를 얻을 수 있다
이벤트가 발생한 가장 안쪽의 요소는 타깃 요소라고 부르고 event.target
을 사용해 접근 가능하다
event.target
은 this
(=event.currentTarget
)는 차이가 있다
event.target
은 실제 이벤트가 시작된 '타깃'요소. 버블링이 진행되어도 변하지 않는다this
는 '현재'요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조이벤트 버블링은 타깃 이벤트에서 시작해서 <html>
요소를 거쳐 document
객체를 만날 때까지 각 노드에서 모두 발생한다. 몇몇 이벤트는 window
객체까지 거슬러 올라가기도 함
하지만 핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 명령할 수 있다
이벤트 객체의 메서드인 event.stopPropagation()
을 사용하면 된다.
<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)">
<button onclick="event.stopPropagation()">클릭해 주세요.</button>
</body>
<button>
을 클릭해도 body.onclick
은 동작 하지 않는다
event.stopImmediatePropagation()
한 요소의 특정 이벤트를 처리하는 핸들러가 여러개인 상황에서, 핸드러 중 하나가 버블링을 멈추더라도 나머지 핸들러는 여전히 동작한다.
event.stopPropagation()
은 위쪽으로 일어나는 버블링은 막아주지만, 다른 핸들러들이 동작하는 건 막지 못한다.
버블링을 멈추고, 요소에 할당된 다른 핸들러의 동작도 막으려면event.stopImmediatePropagation()
을 사용해야 한다. 이 메서드를 사용하면 요소에 할당된 특정 이벤트를 처리하는 핸들러 모두가 동작하지 않는다
꼭 필요한 경우를 제외 버블링 막기 X
버블링을 꼭 멈춰야 하는 명백한 상황이 아니라면 막지말자. 아키텍처를 잘 고려해 진짜 막아야 하는 상황에서만 버블링을 막자
event.stopPropagation()
은 추후에 문제가 발생할 수도 잇는데 예시로
1. 중첩메뉴를 만들었다 생각하면 각 서브메뉴에 해당하는 요소에서 클릭 이벤트를 처리하도록 하고 상위 메뉴의 클릭 이벤트 핸들러는 동작하지 않도록stopPropagation
을 적용한다
2. 사람들이 페이지에서 어디를 클릭햇는지 등의 행동 패턴을 분석하기 위해, window내에서 발생하는 클릭 이벤트를 전부 감지한다.
이런 분석 시스템의 코드는 클릭 이벤트를 감지하기 위해document.addEventListener('click'...)
을 사용한다.
3.stopPropagation
로 버블링을 막아놓은 영역에선 분석 시스템의 코드가 동작하지 않아 분석이 제대로 되지 않고 데드 존이 된다.
실제 코드에서 자주 쓰이진 않지만, 종종 유용한 경우가 잇다
표준 DOM이벤트 에서 정의한 이벤트 흐름엔 3가지 단계가 잇는데
1. 캡처링 단계 - 이벤트가 하위 요소로 전파되는 단계
2. 타깃 단계 - 이벤트가 실제 타깃 요소에 전달되는 단계
3. 버블링 단계 - 이벤트가 상위 요소로 전파되는 단계
<td>
를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파(캡처링)되고 , 이벤트가 타깃 요소에 도착해 실행된 후(타깃), 다시 위로 전파(버블링)된다. 이런 과정을 통해 요소에 할당된 이벤트 핸들러가 호출된다.
캡처링 단계를 이용해야 하는 경우는 흔치 않다
on<event>
프로퍼티나 HTML
속성, addEventListener(event, handler)
를 이용해 할당된 핸들러는 캡처링에 대해 전혀 알 수 없다.
두 번째 혹은 세 번째 단계의 이벤트 흐름(타깃 단계와 버블링 단계)에서만 동작하기 때문
캡처링 단계에서 이벤트를 잡아내려면 addEventListener
의 capture
옵션을 true
로 설정해야 한다.
elem.addEventListener(..., {capture: true})
// 아니면, 아래 같이 {capture: true} 대신, true를 써줘도 됩니다.
elem.addEventListener(..., true)
capture
옵션은 2가지 값이 나온다
false
면 (default값) 핸들러는 버블링 단계에서 동작true
면 핸들러는 캡처링 단계에서 동작공식적으론 총 3개의 이벤트 흐름이 있지만, 이벤트가 실제 타깃 요소에 전달되는 단계인 '타깃단계'(2번째) 는 별도로 처리되지 않는다. 캡처링과 버블링 단계의 핸들러는 타깃단계에서 트리거된다
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form>FORM
<div>DIV
<p>P</p>
</div>
</form>
<script>
for(let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alert(`캡쳐링: ${elem.tagName}`), true);
elem.addEventListener("click", e => alert(`버블링: ${elem.tagName}`));
}
</script>
위 예시는 문서 내 요소 전체에 핸들러를 할당해서 어떤 핸들러가 동작하는지 보여주는데
<p>
를 클릭하면
1. HTML
->BODY
->FORM
->DIV
(캡처링단계, 첫번째 리스터)
2. P
(타깃단계,캡처링과 버블링 둘 다에 리스터를 설정했기 때문에 두번 호출된다.
3. DIV
->FORM
->BODY
->HTML
(버블링 단계, 두 번째 리스터)
event.eventPhase
프로퍼티를 이요하면 현재 발생중인 이벤트흐름의 단계를 알 수 있다.
핸들러를 제거할 때 removeEventListener가 같은 단계에 있어야 한다!
addEventListener(..., true)
로 핸들러를 할당해 줬다면, 핸들러를 지울 때,removeEventListener(..., true)
를 사용해 지워야 한다