์ด๋ฒคํŠธ

@choi2021 ยท December 15, 2022 ยท 18 min read

๐Ÿงจ ์ด๋ฒคํŠธ

์ด๋ฒคํŠธ๋Š” ๋ง ๊ทธ๋Œ€๋กœ ์‚ฌ๊ฑด์„ ์˜๋ฏธํ•œ๋‹ค. ์–ธ์ œ ์–ด๋–ค ์ˆœ์„œ๋กœ ๋ฐœ์ƒํ•  ์ง€ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ณ , ๊ฐ์ง€ํ•˜๊ณ  ๋‚˜์„œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌ ํ• ์ง€์— ๋Œ€ํ•ด ์ •ํ•ด์•ผ ํ•œ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•  ๋•Œ ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•จ์œผ๋กœ์จ ์ด๋ฒคํŠธ๋ฅผ ์œ„์ž„ํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜๋ฉด ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ์— ๋“ฑ๋กํ•œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•จ์œผ๋กœ์จ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์ ์ ˆํ•œ ์ด๋ฒคํŠธ๋กœ ๋” ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด ๋ณด๊ณ ์ž ํ•œ๋‹ค.

๐Ÿ™‚ ์ด๋ฒคํŠธ ํƒ€์ž…

์ด๋ฒคํŠธ๋Š” ์‚ฌ์šฉ์ž์™€ ๋‹ค์–‘ํ•œ interaction์ด ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ’๋ถ€ํ•œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋งŒ๋“ค์–ด ์ค„ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋„ˆ๋ฌด ๋งŽ์€ ์ด๋ฒคํŠธ๋กœ ์„ฑ๋Šฅ์„ ๋–จ์–ด๋œจ๋ฆฌ๊ฑฐ๋‚˜, ์›ํ•˜์ง€ ์•Š์€ ๊ณณ์— ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ๋„ ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋ฒคํŠธ ์ž์ฒด์— ๋Œ€ํ•ด์„œ๋„ ์ดํ•ด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๊ฐ™์€ ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ๋”๋ผ๋„ mouseenter์™€ mousemove๋Š” ํ•ด๋‹น ์š”์†Œ์— ๋งˆ์šฐ์Šค๊ฐ€ ๋“ค์–ด๊ฐ”์„ ๋•Œ์™€ ์›€์ง์ผ ๋•Œ๋กœ ๋™์ž‘์„ ๋ณด๋ฉด ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์•„ ๋ณด์ด์ง€๋งŒ, ๋งŒ์•ฝ api fetchingํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋“ฑ๋ก๋˜์–ด ์žˆ๋‹ค๋ฉด mouseenter ์˜ ๊ฒฝ์šฐ ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์€ ๋น„์šฉ์ด ๋“ค๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋ชฉ์ ์— ๋งž๊ฒŒ ์ด๋ฒคํŠธ์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ๋„ˆ๋ฌด๋‚˜ ์ค‘์š”ํ•˜๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>sensor</button>

    <script>
      const btn = document.querySelector("button")
      // btn.addEventListener('mousemove', () => {
      //   console.log('move');
      //   console.log('data fetching'); // ์„ผ์„œ ์•ˆ์—์„œ ๋งˆ์šฐ์Šค๊ฐ€ ์›€์ง์ผ ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ
      // });
      btn.addEventListener("mouseenter", () => {
        console.log("enter")
        console.log("data fetching") // ์„ผ์„œ๋กœ ๋งˆ์šฐ์Šค๊ฐ€ ๋“ค์–ด๊ฐˆ ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ
      })
    </script>
  </body>
</html>

์ ์ ˆํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋ถ™์—ฌ์ค˜์„œ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ ๋ฌดํ•œ ์Šคํฌ๋กค๊ณผ ์Šคํฌ๋กค ์ด๋ฒคํŠธ์— api ํ˜ธ์ถœ์„ ์—ฐ๊ฒฐํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค. ์ด๋Ÿด ๋•Œ๋Š” Throttle๊ณผ Debounce ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ด์šฉํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค.

Debouncing

๋””๋ฐ”์šด์‹ฑ์€ ์—ฐ์†์ ์œผ๋กœ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ค‘์—์„œ ์ฒ˜์Œ ๋˜๋Š” ๋งˆ์ง€๋ง‰์— ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜๋งŒ ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๊ฒ€์ƒ‰ ์ฐฝ์— ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ api๋ฅผ ํ˜ธ์ถœํ•ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜จ๋‹ค๋ฉด input์˜ onchange๋กœ ๊ทธ๋ƒฅ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ํ•œ ๊ธ€์ž, ํ•œ ๊ธ€์ž ๋ชจ๋‘์— api ํ˜ธ์ถœ์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆํ•„์š”ํ•œ ๋น„์šฉ์ด ๋ฐœ์ƒํ•œ๋‹ค.

<body>
  <input id="input" />
  <script>
    document.querySelector("#input").addEventListener("input", function (e) {
      console.log("์—ฌ๊ธฐ์— ajax ์š”์ฒญ", e.target.value)
    })
  </script>
</body>

๋””๋ฐ”์šด์‹ฑ์ „
๋””๋ฐ”์šด์‹ฑ์ „

์ด๊ฒƒ์„ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ ๋””๋ฐ”์šด์‹ฑ์„ ์ด์šฉํ•ด ์ž…๋ ฅํ•˜๋Š” ์ค‘์— 200ms๋™์•ˆ ์ž…๋ ฅ์ด ์—†๋‹ค๋ฉด ์ž…๋ ฅ์ด ๋๋‚ฌ๋‹ค๊ณ  ๊ฐ„์ฃผํ•˜๊ณ  api๋ฅผ ํ˜ธ์ถœํ•˜๊ฒŒ ํ•œ๋‹ค๋ฉด, api ํ˜ธ์ถœ ๋น„์šฉ์„ ์•„๋‚„ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ž…๋ ฅ์ด ๋  ๋•Œ๋งˆ๋‹ค timer์— ํ•จ์ˆ˜๊ฐ€ ์žˆ๋Š”์ง€ ์ฒดํฌํ•˜๊ณ , ์žˆ๋‹ค๋ฉด ์ดˆ๊ธฐํ™” ์‹œ์ผœ ์ƒˆ๋กญ๊ฒŒ ์ž…๋ ฅํ•œ๋‹ค. 200ms ๋™์•ˆ ์ž…๋ ฅ์ด ์—†๋‹ค๋ฉด timer์˜ callback ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

<html lang="en">
  <body>
    <input id="input" />
    <script>
      let timer
      document.querySelector("#input").addEventListener("input", function (e) {
        if (timer) {
          clearTimeout(timer)
        }

        timer = setTimeout(function () {
          console.log("api ์š”์ฒญ", e.target.value)
        }, 200)
      })
    </script>
  </body>
</html>

๋””๋ฐ”์šด์‹ฑํ›„
๋””๋ฐ”์šด์‹ฑํ›„

Throttling

Throttling์€ ์ผ์ • ์‹œ๊ฐ„ ๋™์•ˆ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์‹คํ–‰ ํšŸ์ˆ˜์— ์ œํ•œ์„ ๊ฑฐ๋Š” ๋ฐฉ์‹์ด๋‹ค. ์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋˜ ๋ฌดํ•œ ์Šคํฌ๋กค๊ณผ ๊ฐ™์ด ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋กœ api๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค๋ฉด ๊ณ„์†ํ•ด์„œ ๋‚ด๋ฆด ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์™€์•ผ ํ•œ๋‹ค. ์ด๋•Œ Throttling์„ ์ด์šฉํ•œ๋‹ค๋ฉด ์ผ์ • ์‹œ๊ฐ„ ๋‹น ํ•œ๋ฒˆ๋งŒ api๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <body style="height: 150vh">
    <script>
      let waiting = false
      document.querySelector("body").addEventListener("wheel", function (e) {
        if (!waiting) {
          console.log("API ํ˜ธ์ถœ")
          waiting = true
          setTimeout(() => {
            waiting = false
          }, 200)
        }
      })
    </script>
  </body>
</html>

๐Ÿ“ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก๊ณผ ์ œ๊ฑฐ

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ๋กํ•ด๋†“์€ ํ•จ์ˆ˜๋‹ค. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” 3๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•œ๋‹ค.

1. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ attribute

Attribute์˜ ๊ฒฝ์šฐ on์ ‘๋‘์‚ฌ์— ์ด๋ฒคํŠธ ํƒ€์ž…์„ ๋ถ™์—ฌ์„œ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฆฌ์•กํŠธ์—์„œ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค. html์—์„œ ํ• ๋‹นํ•  ๋•Œ๋Š” ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ์ „๋‹ฌํ•ด ์ค„ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ž์—ด๋กœ ์—ฐ๊ฒฐํ•˜๋Š”๋ฐ ์•”๋ฌต์ ์œผ๋กœ attribute๊ฐ’์„ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ํ•จ์ˆ˜ ๋ชธ์ฒด๋กœ ํŒŒ์‹ฑํ•ด์„œ ํ• ๋‹นํ•ด์ค€๋‹ค. vanilla JS์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค๋ฉด javascript์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒŒ ๋” ์ ์ ˆํ•˜์ง€๋งŒ ๋ฆฌ์•กํŠธ์—์„œ๋Š” jsx๋ฌธ๋ฒ•์œผ๋กœ javascript๋กœ html์„ ๋งŒ๋“ค๊ธฐ ๋•Œ๋ฌธ์— attribute๋กœ ์ „๋‹ฌํ•ด ์ค€๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button onclick="sayhi()">sensor</button>

    <script>
      function sayhi() {
        console.log("hi")
      }
    </script>
  </body>
</html>
function onclick(event) {
  sayhi() // ํ• ๋‹น๋œ ํ•จ์ˆ˜
}

2. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์†์„ฑ

DOM ๋…ธ๋“œ์—๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์•ž์„œ ์ •๋ฆฌํ•œ attribute์ฒ˜๋Ÿผ on์ ‘๋‘์‚ฌ์— ์ด๋ฒคํŠธ ํƒ€์ž…์„ ๋ถ™์ธ ํ›„์— ํ•จ์ˆ˜๋ฅผ ๋ฐ”์ธ๋”ฉ ํ•ด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ ์†์„ฑ์œผ๋กœ ๋“ฑ๋กํ•œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ํ•˜๋‚˜๋งŒ ๋“ฑ๋ก๋  ์ˆ˜ ์žˆ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>sensor</button>

    <script>
      const btn = document.querySelector("button")
      btn.onclick = function () {
        console.log("ํด๋ฆญ") // ์—†์–ด์ ธ
      }
      btn.onclick = function () {
        console.log("ํด๋ฆญ2") // ์žฌํ• ๋‹น๋˜์–ด ์‹คํ–‰
      }
      btn.onclick = null
    </script>
  </body>
</html>

DOM element์˜ ์†์„ฑ์ด๊ธฐ ๋•Œ๋ฌธ์— ์—†์•จ ๋•Œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ btn.onclick=null๋กœ ํ•ด๋‹น ์†์„ฑ์„ ์—†์•  ์ค„ ์ˆ˜ ์žˆ๋‹ค.

3. addEventListener

addEventListner ๋ฉ”์†Œ๋“œ๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ด๋ฒคํŠธ ํƒ€์ž…, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ, ์„ธ ๋ฒˆ์งธ ์ธ์ž๋กœ ์บก์ฒ˜๋ง๋‹จ๊ณ„์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์บ์น˜ํ•  ์ง€๋ฅผ ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์†์„ฑ๊ณผ๋Š” ๋‹ฌ๋ฆฌ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๊ณ  ์ˆœ์„œ๋Œ€๋กœ ํ˜ธ์ถœ๋œ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>sensor</button>

    <script>
      const btn = document.querySelector("button")
      const click = () => {
        console.log("ํด๋ฆญ")
      }
      btn.addEventListener("click", click)
      btn.addEventListener("click", () => {
        console.log("ํด๋ฆญ2")
      })
      btn.removeEventListner("click", click)
    </script>
  </body>
</html>

์‚ญ์ œํ•  ๋•Œ๋Š” removeEventListner๋ฅผ ์ด์šฉํ•˜๋ฉด ๋˜๋Š”๋ฐ ์ด๋•Œ addEventListenr์— ๋“ฑ๋กํ•œ ํ•จ์ˆ˜์™€ ๋™์ผํ•œ ํ•จ์ˆ˜๋ฅผ ์ฐธ์กฐํ•˜๊ฒŒ ํ•ด์•ผ ํ•œ๋‹ค.

โœจ ์ด๋ฒคํŠธ์˜ ํ๋ฆ„

DOM ์š”์†Œ์— ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด DOM ํŠธ๋ฆฌ๋ฅผ ๋”ฐ๋ผ ์—ฐ์‡„์ ์ธ ๋ฐ˜์‘์ด ์ผ์–ด๋‚˜๋Š”๋ฐ ์ด๊ฒƒ์„ event propagation์ด๋ผ ํ•˜๊ณ  window์—์„œ event target์œผ๋กœ ์ „ํŒŒ๋˜๋Š” ๊ฒƒ์„ event capturing์ด๋ผ ๋ถ€๋ฅด๊ณ  event target์—์„œ window๋กœ ์ „ํŒŒ๋˜๋Š” ๊ฒƒ์„ event bubbling์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค. ํ•ญ์ƒ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ๋ณ„๋„์˜ ์ฒ˜๋ฆฌ๊ฐ€ ์—†๋‹ค๋ฉด ์บก์ฒ˜๋ง๊ณผ ๋ฒ„๋ธ”๋ง์ด ์ˆœ์ฐจ์ ์œผ๋กœ ๋ฐœ์ƒํ•œ๋‹ค.

addEventListener์—์„œ ์„ธ๋ฒˆ์งธ ์ธ์ž๋ฅผ true๋กœ ํ•˜๊ฒŒ ๋˜๋ฉด ์บก์ฒ˜๋ง์—์„œ event๋ฅผ ์บ์น˜ํ•  ์ˆ˜ ์žˆ๊ณ , false๊ฑฐ๋‚˜ ์ƒ๋žต์‹œ์—๋Š” ํƒ€๊ฒŸ ๋‹จ๊ณ„์™€ ๋ฒ„๋ธ”๋ง์—์„œ event๋ฅผ ์บ์น˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <body>
    <p>๋ฒ„๋ธ”๋ง๊ณผ ์บก์ฒ˜๋ง<button>๋ฒ„ํŠผ</button></p>
    <script>
      document.body.addEventListener("click", () => {
        console.log("body")
      })
      document.querySelector("p").addEventListener(
        "click",
        () => {
          console.log("pagragraph")
        },
        true
      )
      document.querySelector("button").addEventListener("click", () => {
        console.log("button")
      })
    </script>
  </body>
</html>

// ๊ฒฐ๊ณผ: // paragraph // button // body

์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ๋‘ ๋ฒˆ์งธ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ ์„ธ๋ฒˆ์งธ ์ธ์ž๊ฐ€ true์—ฌ์„œ ์บก์ฒ˜๋ง ๊ณผ์ •์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์บ์น˜ํ•˜๊ณ , ๋‚˜๋จธ์ง€๋Š” ํƒ€๊ฒŸ์ด๋‚˜ ๋ฒ„๋ธ”๋ง ๊ณผ์ •์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์บ์น˜ํ•˜๋Š” ์ƒํ™ฉ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์บก์ฒ˜๋ง์—์„œ ์ด๋ฒคํŠธ๋ฅผ ์บ์น˜ํ•˜๋Š” paragraph๊ฐ€ ๋จผ์ € ํ˜ธ์ถœ๋˜๊ณ  ๋‘ ๋ฒˆ์งธ๋กœ ํƒ€๊ฒŸ ๋‹จ๊ณ„์ธ button, ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฒ„๋ธ”๋ง ๊ณผ์ •์—์„œ body๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์ˆœ์„œ๋กœ ๋‚˜ํƒ€๋‚œ๋‹ค.

์บก์ฒ˜๋ง, ํƒ€๊ฒŸ, ๋ฒ„๋ธ”๋ง์˜ ์ด๋ฒคํŠธ ํ๋ฆ„์„ ์ดํ•ดํ•˜๋ฉด์„œ ์ด๋ฒคํŠธ๊ฐ€ ์ƒ์œ„ DOM์š”์†Œ์—์„œ ์บ์น˜๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ด์ ์„ ํ™œ์šฉํ•˜์—ฌ ์ผ์ผ์ด ๋ชจ๋“  ์ž์‹์—๊ฒŒ ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ถ€๋ชจ์š”์†Œ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ์ด๋ฒคํŠธ ์œ„์ž„์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.

์ด๋ฒคํŠธ ์œ„์ž„

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      #fruits {
        display: flex;
        list-style-type: none;
        padding: 0;
      }
      #fruits li {
        width: 100px;
        cursor: pointer;
      }
      #fruits .active {
        color: red;
        text-decoration: underline;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul id="fruits">
        <li id="apple" class="active">Apple</li>
        <li id="banana">banana</li>
        <li id="orange">orange</li>
      </ul>
    </nav>
    <div>์„ ํƒ๋œ ์•„์ดํ…œ: <em class="msg">apple</em></div>
    <script>
      const $fruits = document.getElementById("fruits")
      const $msg = document.querySelector(".msg")

      function activate({ target }) {
        ;[...$fruits.children].forEach($fruit => {
          $fruit.classList.toggle("active", $fruit === target)
          $msg.textContent = target.id
        })
      }

      document.getElementById("apple").onclick = activate
      document.getElementById("banana").onclick = activate
      document.getElementById("orange").onclick = activate
    </script>
  </body>
</html>

์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด activate๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ liํƒœ๊ทธ๋งˆ๋‹ค ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋กœ ์—ฐ๊ฒฐํ•ด์ฃผ๊ณ  ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์š”์†Œ๊ฐ€ ๋™์ ์œผ๋กœ ์ •ํ•ด์ง€๊ฑฐ๋‚˜ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๋œ๋‹ค๋ฉด ์ผ์ผ์ด ์ž์‹ ์š”์†Œ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ถ™์—ฌ ์ฃผ๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ด๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ถ€๋ชจ์ธ ul์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋“ฑ๋กํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      #fruits {
        display: flex;
        list-style-type: none;
        padding: 0;
      }
      #fruits li {
        width: 100px;
        cursor: pointer;
      }
      #fruits .active {
        color: red;
        text-decoration: underline;
      }
    </style>
  </head>
  <body>
    <nav>
      <ul id="fruits">
        <li id="apple" class="active">Apple</li>
        <li id="banana">banana</li>
        <li id="orange">orange</li>
      </ul>
    </nav>
    <div>์„ ํƒ๋œ ์•„์ดํ…œ: <em class="msg">apple</em></div>
    <script>
      const $fruits = document.getElementById("fruits")
      const $msg = document.querySelector(".msg")

      function activate({ target }) {
        if (!target.matches("#fruits > li")) return

        ;[...$fruits.children].forEach($fruit => {
          $fruit.classList.toggle("active", $fruit === target)
          $msg.textContent = target.id
        })
      }
      $fruits.onclick = activate
    </script>
  </body>
</html>

์œ„์ฝ”๋“œ์—์„œ๋Š” activate๊ฐ€ ul์—๋งŒ ๋“ฑ๋ก์ด ๋˜์—ˆ๋‹ค. ์ƒ์œ„ DOM์š”์†Œ์—์„œ ์ž์‹ ์š”์†Œ์˜ event๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•ด๋‹น ์ด๋ฒคํŠธ๊ฐ€ ์ž์‹์—์„œ ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ๊ฐ€ ๋งž๋Š”์ง€ ์šฐ์„  ์ฒดํฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— if (!target.matches('#fruits > li')) return;๋กœ ๋จผ์ € ํ™•์ธํ•˜๊ณ  ๊ฐ ์ž์‹ ์š”์†Œ์— ์ฒ˜๋ฆฌํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค. ๋ฒ„๋ธ”๋ง์„ ์ด์šฉํ•œ ์ด๋ฒคํŠธ ์œ„์ž„์„ ์ด์šฉํ•˜๋ฉด ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ™…โ€โ™‚๏ธ ์ด๋ฒคํŠธ ๋ฉˆ์ถฐ PreventDefault()์™€ StopPropagation()

preventDefault()๋Š” ์ด๋ฒคํŠธ์˜ ๋ฉ”์†Œ๋“œ๋กœ DOM์š”์†Œ์˜ ๊ธฐ๋ณธ ๋™์ž‘์„ ๋ง‰๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์ฃผ๋กœ form์œผ๋กœ POST์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์ƒˆ๋กœ ๊ณ ์นจ์ด ์ผ์–ด๋‚˜๊ฒŒ ๋˜๋Š”๋ฐ, SPA์—์„œ๋Š” ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ๊ธฐ๋ณธ ๋™์ž‘์„ ๋ง‰๊ธฐ ์œ„ํ•ด preventDefault()๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <body>
    <a href="https://google.com">go</a>
    <input type="checkbox" />
    <form><button>์ œ์ถœ</button></form>
    <script>
      document.querySelector("a").onclick = e => {
        e.preventDefault()
      }
      document.querySelector("input[type=checkbox]").onclick = e => {
        e.preventDefault()
      }
      document.querySelector("form").onsubmit = e => {
        e.preventDefault()
      }
    </script>
  </body>
</html>

์œ„ ์ฝ”๋“œ์—์„œ aํƒœ๊ทธ์˜ ๊ธฐ๋ณธ ๋™์ž‘์ธ ํŽ˜์ด์ง€ ์ด๋™์„ ๋ง‰๊ณ , checkbox input์˜ ๊ธฐ๋ณธ ๋™์ž‘์ธ ์ฒดํฌ,ํ•ด์ œ๋ฅผ ๋ง‰๋Š”๋‹ค.

stopPropagation์€ ์ด๋ฒคํŠธ ์ „ํŒŒ๋ฅผ ์ค‘์ง€ ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ๋ถ€๋ชจ ์š”์†Œ๋กœ ๋ฒ„๋ธ”๋ง ๋˜์ง€ ์•Š๊ฒŒ ๋ง‰๋Š”๋‹ค. ํƒ€๊ฒŸ ๊ณผ์ •์—์„œ๋งŒ ์ด๋ฒคํŠธ๋ฅผ ์บ์น˜ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ด๋ฒคํŠธ ํ๋ฆ„์„ ๋ง‰๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด ์กฐ์‹ฌํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <body>
    <div class="container">
      <button class="btn1">button1</button>
      <button class="btn2">btn2</button>
      <button class="btn3">btn3</button>
    </div>
    <script>
      document.querySelector(".container").onclick = ({ target }) => {
        if (!target.matches(".container>button")) return
        target.style.color = "red"
      }

      document.querySelector(".btn2").onclick = e => {
        e.stopPropagation()
        e.target.style.color = "blue"
      }
    </script>
  </body>
</html>

์œ„ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด btn1,2,3 ๋ชจ๋‘ ํด๋ฆญ ์‹œ ๊ธ€์ž์ƒ‰์ด ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ๋ณ€ํ•ด์•ผ ํ•˜์ง€๋งŒ btn2์˜ stopPropagation()์œผ๋กœ btn2์˜ ์ด๋ฒคํŠธ๋Š” ๋ถ€๋ชจ๋กœ ๋ฒ„๋ธ”๋ง๋˜์ง€ ์•Š์•„ ์œ„์ž„์ด ๋˜์ง€ ์•Š๊ณ  ํŒŒ๋ž€์ƒ‰์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ’ก Event ํ•ธ๋“ค๋Ÿฌ์˜ this

์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์†์„ฑ์œผ๋กœ ์ „๋‹ฌ๋˜๊ฑฐ๋‚˜ addEventListener()๋กœ ๋“ฑ๋ก๋œ ํ•จ์ˆ˜ ์„ ์–ธ์‹์˜ this๋Š” ์ด๋ฒคํŠธ๋ฅผ ๋ฐ”์ธ๋”ฉํ•œ ์š”์†Œ, event.currentTarget์™€ ๊ฐ™๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <body>
    <button>click me</button>
    <script>
      const btn = document.querySelector("button")
      const handleClick = e => {
        console.log(e.currentTarget) // <button>click me</button>
        console.log(this) // <button>click me</button>
        console.log(this === e.currentTarget) // true
      }
      btn.onclick = handleClick

      btn.addEventListener("click", handleClick)
    </script>
  </body>
</html>

ํ•˜์ง€๋งŒ ํ•จ์ˆ˜ ์„ ์–ธ์‹์ด ์•„๋‹Œ ํ™”์‚ดํ‘œ ํ•จ์ˆ˜์˜ this๋Š” ํ™”์‚ดํ‘œ ํ•จ์ˆ˜ ์ž์ฒด์ ์œผ๋กœ this๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์œ„์Šค์ฝ”ํ”„์˜ this๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <body>
    <button>click me</button>
    <script>
      const btn = document.querySelector("button")
      const handleClick = e => {
        console.log(e.currentTarget) // <button>click me</button>
        console.log(this) // Window
        console.log(this === e.currentTarget) // false
      }
      btn.onclick = handleClick
      btn.addEventListener("click", handleClick)
    </script>
  </body>
</html>

์ด๋Ÿฌํ•œ ์ฐจ์ด๋Š” ํด๋ž˜์Šค์˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ this๋ฅผ ๋ฐ”์ธ๋”ฉ์„ ํ•  ๋•Œ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ์ด๋‹ค.

<!DOCTYPE html>
<html lang="en">
  <body>
    <button class="btn">0</button>
    <script>
      class App {
        constructor() {
          this.$btn = document.querySelector(".btn")
          this.count = 0
          // this.$btn.onclick = this.increase.bind(this);
          this.$btn.onclick = this.increase
        }
        increase() {
          console.log(this) // <button class="btn">0</button>
          this.$btn.textContent = ++this.count // Uncaught TypeError: Cannot set properties of undefined (setting 'textContent')
        }
      }
      new App()
    </script>
  </body>
</html>

์œ„ ์ฝ”๋“œ์—์„œ increase๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋กœ ๋“ฑ๋ก๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— this๊ฐ€ class๋กœ ๋งŒ๋“ค์–ด์งˆ instance๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ”์ธ๋”ฉํ•œ DOM ์š”์†Œ this.$btn๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— this.$btn.textContent๋Š”this.btn.btn.btn.textContent`์™€ ๊ฐ™์•„์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

this๋ฅผ ์ธ์Šคํ„ด์Šค๋กœ ๋ฐ”์ธ๋”ฉ ์‹œ์ผœ์ฃผ๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•จ์ˆ˜์— ์ง์ ‘ bind๋กœ ๋ช…์‹œ์ ์œผ๋กœ ์ •ํ•ด์ฃผ๊ฑฐ๋‚˜ arrow function์„ ์ด์šฉํ•ด์„œ class field์˜ this๋ฅผ ๋ฉ”์†Œ๋“œ์— ์ฐธ์กฐํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

[bind๋กœ this ๋ฐ”์ธ๋”ฉ]

<!DOCTYPE html>
<html lang="en">
  <body>
    <button class="btn">0</button>
    <script>
      class App {
        constructor() {
          this.$btn = document.querySelector(".btn")
          this.count = 0
          this.$btn.onclick = this.increase.bind(this)
        }
        increase() {
          console.log(this) // App
          this.$btn.textContent = ++this.count
        }
      }
      new App()
    </script>
  </body>
</html>

[arrow function]

<!DOCTYPE html>
<html lang="en">
  <body>
    <button class="btn">0</button>
    <script>
      class App {
        constructor() {
          this.$btn = document.querySelector(".btn")
          this.count = 0
          this.$btn.onclick = this.increase
        }
        increase = () => (this.$btn.textContent = ++this.count)
      }
      new App()
    </script>
  </body>
</html>

๋งˆ์น˜๋ฉฐ

์ด๋ฒคํŠธ๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ์ด์ „์— ๋งˆ์ฃผํ–ˆ๋˜ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์˜ this ๋ฐ”์ธ๋”ฉ ๋ฌธ์ œ๋ฅผ ์ œ๋Œ€๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , ์ด๋ฒคํŠธ ์ „ํŒŒ์™€ ์œ„์ž„์— ๋Œ€ํ•ด์„œ๋„ ๋ฐ”๋ฅด๊ฒŒ ์•Œ ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„์ด์—ˆ๋‹ค.

[์ฐธ์กฐ]

@choi2021
๋งค์ผ์˜ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ฐœ๋ฐœ์ผ์ง€์ž…๋‹ˆ๋‹ค.