All Articles

속깊은 JavaScript - 디자인패턴 1탄

image.png

디자인 패턴

이번 시간에는 속깊자 5장 디자인 패턴을 정리해볼건데, 내용이 많으므로 일단 1,2장 내용을 차례로 정리해볼 예정이다.

모듈 패턴

자바스크립트의 소스를 모듈 단위로 관리하거나 라이브러리를 만들때 주로 사용한다.
일반적인 프로젝트에선 잘 안 쓰이지만 서버개발, 라이브러리개발, API개발 등에 유용하게 쓰이는 패턴쓰 여러 함수와 변수를 글로벌 영역에 두고 사용하는게 아니라, 하나의 대표 글로벌 변수 안에 여러 함수를 두는 게 바로 모듈 패턴의 기본!

모듈패턴의 장점

  • 글로벌 변수를 최소화함으로 다른 소스를 가져다 쓸때 충돌 최소화 시킴
  • 모듈간 의존성을 최소화하거나 의존성을 파악하기 쉬움

예시를 한번 살펴보자.

(function(window) {
  var my = {
    helloWorld : function() {
      alert("HEllo World");
    },
    hello : {
      world: function() {
        alert("HEllo World");
      }
    }
  }

  window.my = my
})(window)

console.log(my.helloWorld()) // === HEllo World
console.log(my.hello.world()) // === HEllo World

모듈패턴이 어떻게 활용되는지에 대한 기본틀이 이것이다! 하지만 더 간단하게 생각해보자면, 우리가 React에서 함수를 import 해서 쓰는 것도 하나의 모듈패턴이라고 할 수있다.

제이쿼리에서 변수인 $+ selector로 함수를 호출하는 것도 모듈패턴이다.
속깊자 본문에는 jQuery 예시가 많이 나왔는데 난 제이쿼리 잘알못이니까 생략..!

허선생님의 노드 수업에서 크롤링 배울때 cheerio 쓰면서 $변수를 쓴적 있긴 하다.
아 그러고 보니 크롤링 수업 이어서 해야하는데 큽… 못 듣고 있다 속상…

const scrapNtok = async () => {
    let browser;
  try {
    browser = await puppeteer.launch()

    const page = await browser.newPage() // 얘도 비동기라서 이렇게. 크롬에서 새탭 여는 효과

    await page.goto('https://www.ntok.go.kr/kr/Main/Index')

    const html = await page.content()

    const $ = cheerio.load(html)
    const shows = $('#container > div.contents.clfix > div:nth-child(2) > div > div > div.bx-viewport > ul > li:nth-child(2) > ul > li')
    .toArray()
    .map(e => {
        const $ = cheerio(e)
        const url = $.find('a').attr('href')
        const img = $.find('img').attr('src')
        const title = $.find('.name').text()
        const period = $.find('.prd_info span:nth-child(1)').text()
        const place = $.find('.prd_info span:nth-child(2)').text()
        return { url, img, title, period, place }
      })
    return shows

} catch(err) {
    throw(err)
  } finally {
      browser.close()

  }
}

이벤트 델리게이션

다수의 DOM 모두에 이벤트리스너를 부여하는 것이 아니라, 대표 DOM에만 이벤트를 걸어서 처리하는 패턴을 말한다. 모든 DOM에 일일히 이벤트리스너를 할당하면 초기화 단계에서 컴퓨팅 자원을 많이 소모하게 된다. 그래서 부모DOM을 만들어서 이벤트를 처리하거나, 주요 이벤트가 발생하는 부분별로 나누어서 처리하는 방식을 쓰게 되었다.

이는 HTML에서 이벤트 버블링을 통해 이벤트를 상위 DOM으로 전달할 수 있는 원리를 이용한다. 이벤트가 발생하면 부모와 자식 DOM 사이에서 해당 이벤트를 전파할때 캡처링->대상->버블링의 세 단계를 거친다.

이벤트 버블링과 캡처링

이벤트 버블링은 자식DOM->부모DOM까지 전파 되는 것,
이벤트 캡처링은 부모DOM->자식DOM까지 전파되는 것을 의미한다.

예시를 보자! 책에 있는 코드를 가져다 쳐봤다.

<!DOCTYPE html>
<html>
<head>
  <style>
    div{
      border: 1px solid black;
    }
    .divOutside {
      width: 200px;
      height: 200px;
      background-color: lightgreen;
    }
    .divMiddle {
      width: 150px;
      height: 150px;
      background-color: lightblue;
    }
    .divInside {
      width: 100px;
      height: 100px;
      background-color: pink;
      position: relative;
    }
    .divFloat {
      position: absolute;
      left: 210px;
      height: 50px;
      width: 50px;
      background-color: lightgray;
    }
    .highlight{
      background-color: black;
    }
  </style>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Bubbling && Capturing</title>
</head>

<body>
  <div id="divBubblingOutside" class="divOutside">
    <div id="divBubblingMiddle" class="divMiddle">
      <div id="divBubblingInside" class="divInside">
        Bubbling
<div id="divBubblingFloat" class="divFloat"></div>
      </div>
    </div>
  </div>
<div id="divCapturingOutside" class="divOutside">
  <div id="divCapturingMiddle class="divMiddle">
<div id="divCapturingInside" class="divInside">
  Capturing
<div id="divCapturingFloat" class="dicFloat"></div>
</div></div></div>
<script>
(function() {
  document.getElementById("divBubblingOutside")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Outside bubbling");
    this.classList.toggle("highlight");
  });
  document.getElementById("divBubblingMiddle")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Middle bubbling");
    this.classList.toggle("highlight");
  });
  document.getElementById("divBubblingInside")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Inside bubbling");
    this.classList.toggle("highlight");
  });
  document.getElementById("divBubblingFloat")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Float bubbling");
    this.classList.toggle("highlight");
  });
  document.getElementById("divCapturingOutside")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Outside capturing");
    this.classList.toggle("highlight");
  }, true);
  document.getElementById("divCapturingMiddle")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Middle capturing");
    this.classList.toggle("highlight");
  }, true);
 document.getElementById("divCapturingInside")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Inside capturing");
    this.classList.toggle("highlight");
  }, true);    document.getElementById("divCapturingFloat")
  .addEventListener('click', function() {
    this.classList.toggle("highlight");
    alert("Float capturing");
    this.classList.toggle("highlight");
  }, true);
})()
</script>
</body>
</html>

아래는 캡처화면!

image.png

상단의 분홍색 영역(버블링)을 클릭하면 inside->middle->outside 순으로 alert창이 뜬다. 캡처링은 이와 반대로 실행된다.

특정 DOM에서 이벤트가 발생하면 해당 DOM의 dispatchEvent()라는 함수를 통해 이벤트를 전달하게 된다. 이 때 이벤트 전달은 propagation path라는 전파경로에 따라 수행되는데, 크게 세 단계로 이루어진다. 이벤트 캡처->target->이벤트 버블 순이다.
따라서 캡처링과 버블링을 위한 두 가지 이벤트를 같은 DOM에 설정해놓으면 캡처링으로 설정한 이벤트 핸들러가 먼저 호출되고, 이후 버블링이 실행된다.

DOM 수정 시 전달 경로

이벤트가 발생하고 나면 핸들러 안에서 DOM이 수정되더라도 이벤트 전달은 중단되지 않고 원래의 경로를 통해 이벤트가 전파된다. 속깊자 본문에선 아까 썼던 코드에서 divCapturingMiddle의 이벤트 핸들러에서 이벤트 대상인 divCapturingFloat를 삭제하는 코드를 예시로 썼다. 이렇게 하면 divCapturingFloat는 DOM에서 삭제되어 보이지 않지만 이벤트 핸들러는 그대로 호출이 된다.

즉, 전달 경로 중 DOM이 수정되어도 이벤트 핸들러 호출은 변경이 없다는 걸 알 수 있다.

이벤트 전달 세 단계

  • 캡처링 단계: 이벤트가 발생한 대상의 부모들을 window 객체부터 시작해 순서대로 호출한다. 부모가 캡처링으로 정의되어 있다면 이벤트가 target에 도달하기 전에 호출되어야 한다.
  • 대상 단계 : 이벤트가 target에 도달했을 때 버블링 할 것인지 정할 수 있고, 만약 버블링 하지 않을거라면 이후 버블링 단계는 생략된다.
  • 버블링 단계 : 다시 이벤트 대상부터 tree를 따라서 부모 DOM 요소를 따라 이벤트 핸들러를 처리

이벤트 델리게이션 패턴은 캡처링(부모부터 시작)에서 먼저 이벤트를 잡아서 처리하면 성능상으로 조금 더 효율적으로 구현할 수 있다.
이래서 예로부터 부모님 말씀을 잘 들으면 자다가도 콩고물이 떨어진다는 말이 있나보다!

하지만 구버전 브라우저에선 캡처링을 지원 안 할수도 있다고 하는데..
이제 책이 출간된지 꽤 되었으니까 대부분 되지 않을까 싶다~

Reference

  • 속깊은 자바스크립트 (양성익 저)