getElementsByClassName ํ•จ์ˆ˜ ๊ตฌํ˜„ํ•˜๊ธฐ (+์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ์œ ์‚ฌ๋ฐฐ์—ด๊ณผ ๋ฐฐ์—ด, ๋ณ€ํ™˜ ๋ฐฉ๋ฒ•)

    ํ”„๋ก ํŠธ์—”๋“œ ๋ผ์ด๋ธŒ ์ฝ”๋”ฉ์ด๋‚˜ ํ™”์ดํŠธ๋ณด๋“œ ํ…Œ์ŠคํŠธ์—์„œ ์ž์ฃผ ๋‚˜์˜ค๋Š” ๊ณผ์ œ ์ค‘ ํ•˜๋‚˜๊ฐ€ DOM API๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ๊ทธ์ค‘ ๋Œ€ํ‘œ์ ์ธ ๊ฒƒ์ด document.getElementsByClassName์„ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๋ผ๋Š” ๊ณผ์ œ์ž…๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ์ ‘๊ทผํ•˜๋ฉด ์ข‹์„์ง€ ๋ฒ ์ด์Šค์ผ€์ด์Šค → ์—ฃ์ง€์ผ€์ด์Šค → ํšจ์œจ์„ฑ → ๊ฐ€๋…์„ฑ ์ˆœ์„œ๋กœ ํ’€์–ด๋ด…๋‹ˆ๋‹ค.

     

     

    ๋ฌธ์ œ ์ดํ•ด

    ๋ชฉํ‘œ๋Š” ์ฃผ์–ด์ง„ className์„ ๊ฐ€์ง„ ๋ชจ๋“  ์š”์†Œ๋ฅผ ์ฐพ์•„ ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
    ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” DOM ํŠธ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ ์กฐ๊ฑด์— ๋งž๋Š” ์š”์†Œ๋ฅผ ๋ชจ์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

    function getElementsByClassName(className) {
      const bodyChildrenEls = document.body.children;
      const result = [];
      const stack = [...bodyChildrenEls];
    
      while (stack.length) {
        const currentElement = stack.pop();
        const currentElementChildren = currentElement.children;
    
        for (let i = 0; i < currentElementChildren.length; i++) {
          const currentChildrenEl = currentElementChildren[i];
          stack.push(currentChildrenEl);
        }
    
        if (currentElement.classList.contains(className)) {
          result.push(currentElement);
        }
      }
    
      return result;
    }

     

     

    1. ๋ฒ ์ด์Šค์ผ€์ด์Šค

     

    • ์ตœ์ƒ์œ„ ์š”์†Œ์—๋งŒ ํด๋ž˜์Šค๊ฐ€ ๋ถ™์€ ๊ฒฝ์šฐ
    • DOM์ด ๋น„์–ด์žˆ์„ ๋•Œ (<body></body>)
    • ์ฐพ๋Š” ํด๋ž˜์Šค๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
    function getElementsByClassName(className) {
      const bodyChildrenEls = document.body.children;
      const result = [];
    
      for (let i = 0; i < bodyChildrenEls.length; i++) {
        const currentEl = bodyChildrenEls[i];
        const currentElClassName = currentEl.className;
    
        if (currentElClassName === className) {
          result.push(currentEl);
        }
      }
    
      return result;
    }

     

     

    2. ์•ณ์ง€ ์ผ€์ด์Šค

    • ํด๋ž˜์Šค๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ๋ถ™์€ ๊ฒฝ์šฐ
    • ์ค‘์ฒฉ๋œ ์š”์†Œ์ผ ๊ฒฝ์šฐ
    • ์ค‘์ฒฉ ๋Œ‘์Šค๊ฐ€ ๊นŠ์–ด์งˆ ๊ฒฝ์šฐ ... ๋“ฑ ๊ทธ ์™ธ์— ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ถ”๊ฐ€๋˜๋Š” ์กฐ๊ฑด๋“ค .. ? 

    2-1. ์ค‘์ฒฉ๋œ ์ž์‹๊นŒ์ง€ ์ˆœํšŒ (DFS / Stack)

    ๋ชจ๋“  ํ•˜์œ„ ๋…ธ๋“œ๊นŒ์ง€ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๋„๋ก stack์„ ๋„์ž…ํ•ฉ๋‹ˆ๋‹ค.

    function getElementsByClassName(className) {
      const bodyChildrenEls = document.body.children;
      const result = [];
      const stack = [...bodyChildrenEls];
    
      while (stack.length) {
        const currentElement = stack.pop();
        const children = currentElement.children;
    
        // ์ž์‹ ์š”์†Œ ์Šคํƒ์— ์ถ”๊ฐ€
        for (let i = 0; i < children.length; i++) {
          stack.push(children[i]);
        }
    
        if (currentElement.className === className) {
          result.push(currentElement);
        }
      }
    
      return result;
    }
     

    โœ… ๋™์ž‘: ์ค‘์ฒฉ ๊ตฌ์กฐ๊นŒ์ง€ ํƒ์ƒ‰ ๊ฐ€๋Šฅ
    โŒ ๋ฌธ์ œ: className์ด "a b"์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๊ฐœ์ผ ๊ฒฝ์šฐ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜์ง€ ์•Š์Œ

     

    2-2 ๋‹ค์ค‘ํด๋ž˜์Šค ๋Œ€์‘ ("a b")

    function getElementsByClassName(className) {
      const bodyChildrenEls = document.body.children;
      const result = [];
      const stack = [...bodyChildrenEls];
    
      while (stack.length) {
        const currentElement = stack.pop();
        const children = currentElement.children;
    
        for (let i = 0; i < children.length; i++) {
          stack.push(children[i]);
        }
    
        // classList.contains๋กœ ํŠน์ • ํด๋ž˜์Šค๋งŒ ๊ฒ€์‚ฌ
        if (currentElement.classList.contains(className)) {
          result.push(currentElement);
        }
      }
    
      return result;
    }

     

    โœ… ๋™์ž‘: <div class="a b"> ์—์„œ getElementsByClassName("a") ํ˜ธ์ถœ ์‹œ ์ •์ƒ์ ์œผ๋กœ ๋งค์นญ๋จ

     

    3. ํšจ์œจ์„ฑ (์‹œ๊ฐ„๋ณต์žก๋„ & ๊ณต๊ฐ„๋ณต์žก๋„)

    • ์‹œ๊ฐ„๋ณต์žก๋„: ๋ชจ๋“  ๋…ธ๋“œ๋ฅผ ํ•œ ๋ฒˆ์”ฉ ๋ฐฉ๋ฌธํ•˜๋ฏ€๋กœ O(N) (N์€ DOM ๋…ธ๋“œ ์ˆ˜)
    • ๊ณต๊ฐ„๋ณต์žก๋„: ์Šคํƒ๊ณผ ๊ฒฐ๊ณผ ๋ฐฐ์—ด์— ์ตœ๋Œ€ O(N)๊นŒ์ง€ ์ €์žฅ ๊ฐ€๋Šฅ


    4. ๊ฐ€๋…์„ฑ 

    ๋ฉด์ ‘ ํ›„๋ฐ˜์—๋Š” ์ฝ”๋“œ์˜ ๊น”๋”ํ•จ๋„ ํ‰๊ฐ€ ์š”์†Œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Œ 

    • stack → pendingNodes
    • result → matchedElements
    • currentElementChildren → children

     

     

     

    + ์œ ์‚ฌ๋ฐฐ์—ด ์ •๋ฆฌ 
    ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์“ฐ๋‹ค ๋ณด๋ฉด ๋ฐฐ์—ด์ฒ˜๋Ÿผ ๋ณด์ด๋Š”๋ฐ ๋ฐฐ์—ด์€ ์•„๋‹Œ ๊ฐ’๋“ค์„ ์ข…์ข… ๋งŒ๋‚ฉ๋‹ˆ๋‹ค. ํ”ํžˆ ์œ ์‚ฌ๋ฐฐ์—ด(array-like) ์ด๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ๊ฒƒ๋“ค์ž…๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์œ ์‚ฌ๋ฐฐ์—ด๊ณผ ๋ฐฐ์—ด์˜ ์ฐจ์ด๋ฅผ ์ •๋ฆฌํ•˜๊ณ , ์œ ์‚ฌ๋ฐฐ์—ด์„ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


    ์œ ์‚ฌ๋ฐฐ์—ด์ด๋ž€?

    ์œ ์‚ฌ๋ฐฐ์—ด์€ length ์†์„ฑ๊ณผ ์ธ๋ฑ์Šค ๋ฒˆํ˜ธ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ๊ฒ‰๋ชจ์Šต์€ ๋ฐฐ์—ด ๊ฐ™์ง€๋งŒ ์‹ค์ œ ๋ฐฐ์—ด ๋ฉ”์„œ๋“œ(map, forEach, filter ๋“ฑ)๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

     

    ๋Œ€ํ‘œ์ ์ธ ์˜ˆ์‹œ:

    • ํ•จ์ˆ˜ ์•ˆ์˜ arguments
    • document.querySelectorAll ๊ฐ™์€ DOM API ๊ฒฐ๊ณผ๊ฐ’
    • HTMLCollection
     
    function example() {
      console.log(arguments); // [Arguments] { '0': 1, '1': 2, '2': 3 }
    }
    
    example(1, 2, 3);
    const divs = document.querySelectorAll('div');
    console.log(divs); // NodeList(3) [div, div, div]

     


    ๋ฐฐ์—ด๊ณผ์˜ ์ฐจ์ด

    ๋ฐฐ์—ด์€ Array.prototype์„ ์ƒ์†๋ฐ›์•„ ๋‹ค์–‘ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด ์œ ์‚ฌ๋ฐฐ์—ด์€ ๋‹จ์ˆœ ๊ฐ์ฒด๋ผ ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค.

     
    const arr = [1, 2, 3];
    arr.forEach(x => console.log(x)); // ์ •์ƒ ๋™์ž‘
    
    const divs = document.querySelectorAll('div');
    divs.forEach(x => console.log(x)); 
    // TypeError: divs.forEach is not a function

    ์œ ์‚ฌ๋ฐฐ์—ด → ๋ฐฐ์—ด ๋ณ€ํ™˜ ๋ฐฉ๋ฒ•

    1. Array.from()

    ES6์—์„œ ๋„์ž…๋œ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

     
    const divs = document.querySelectorAll('div');
    const divArray = Array.from(divs);
    
    divArray.forEach(div => console.log(div));

    2. ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž [... ]

    ๋” ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋ฐฐ์—ด๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    const argsToArray = (...args) => {
      const arr = [...arguments];
      console.log(arr);
    };
    
    argsToArray(1, 2, 3); // [1, 2, 3]
     

    3. Array.prototype.slice.call()

    ES6 ์ด์ „์— ๋งŽ์ด ์“ฐ์ด๋˜ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

     
    function oldSchool() {
      const arr = Array.prototype.slice.call(arguments);
      console.log(arr);
    }
    
    oldSchool('a', 'b', 'c'); // ['a', 'b', 'c']

    ๊ทธ ์™ธ ์‹ค์ œ ํ™œ์šฉ ์˜ˆ์‹œ

    DOM ์กฐ์ž‘

    querySelectorAll๋กœ ์„ ํƒํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ํ•ด map์„ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

     
    const buttons = document.querySelectorAll('button');
    
    // ์œ ์‚ฌ๋ฐฐ์—ด ๊ทธ๋Œ€๋กœ๋Š” map ๋ถˆ๊ฐ€
    // buttons.map(...) -> ์—๋Ÿฌ ๋ฐœ์ƒ
    
    // ๋ณ€ํ™˜ ํ›„ ์‚ฌ์šฉ
    const texts = [...buttons].map(btn => btn.innerText);
    console.log(texts);

    ํ•จ์ˆ˜ ์ธ์ž ์ฒ˜๋ฆฌ

    arguments ๊ฐ์ฒด๋ฅผ ๋ฐฐ์—ด๋กœ ๋ฐ”๊พธ๋ฉด ํŽธ๋ฆฌํ•˜๊ฒŒ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    function sum() {
      const nums = Array.from(arguments);
      return nums.reduce((acc, cur) => acc + cur, 0);
    }
    
    console.log(sum(1, 2, 3, 4)); // 10

    ํƒ€์ž… ํŒ๋ณ„ํ•˜๊ธฐ

    ์œ ์‚ฌ๋ฐฐ์—ด์ธ์ง€ ๋ฐฐ์—ด์ธ์ง€ ํ—ท๊ฐˆ๋ฆด ๋•Œ๋Š” Array.isArray()๋ฅผ ์“ฐ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

    Array.isArray([1, 2, 3]);        // true
    Array.isArray(document.querySelectorAll('div')); // false
    ๋ฐ˜์‘ํ˜•

    ๋Œ“๊ธ€