때는 2024년 여름 어느 날, ASP.NET 와 관련 UI 라이브러리를 써서 개발 중이던 개발자 1.

해당 UI 라이브러리를 사용해서 edit 가능한 테이블을 만들고 있었는데 문제 발생 🚨

안그래도 회사에서 산 버전은 오래되어서 레퍼런스가 잘 없는데, 고치자고 API 문서를 뒤져보자니 시간 낭비만 하는 것 같아서 그냥 HTML로 만들기로 했다. (ㅠㅠ)

 

* 구현한 기능 

1) table row 추가 및 시퀀스 자동 계산

2) table row 삭제

3) 합계 계산

4) number 컬럼 숫자 체크

5) table data 저장

6) confirm = "Y" 시 readonly

- 데이터 뿌리는 건 C# 으로 구현해서 따로 js 코드에 넣진 않음

 

<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <style>
      input[type="number"]::-webkit-outer-spin-button,
      input[type="number"]::-webkit-inner-spin-button {
        -webkit-appearance: none;
        margin: 0;
      }
      .non-clickable {
        pointer-events: none;
      }
    </style>

    <script defer>
      window.onload = () => {
        const arrInput = document.getElementsByTagName("input");
        const arrTextarea = document.getElementsByTagName("textarea");
        const confirm =
          document.getElementById("confirm").value === "Y" ? true : false;

        // readonly
        if (confirm) {
          [...arrInput].forEach((input, index) => {
            input.classList.add("non-clickable");
          });

          [...arrTextarea].forEach((textarea, index) => {
            textarea.classList.add("non-clickable");
          });
        }
      };

      function isValidNumber(args, event) {
        const value = event.target.value;
        let bInteger = /^-?\d+$/.test(value) && value <= 100 && value > 0;

        if (!bInteger) {
          event.target.value = "";
          event.target.focus();
        }
        return bInteger;
      }

      function sum(args, event) {
        const elements = document.getElementsByClassName("ratio");
        let sum = 0;

        if (!isValidNumber(args, event)) {
          return false;
        }

        for (let element of elements) {
          const val = parseInt(element.value);
          if (!isNaN(val)) {
            sum += val;
          }
        }
        document.getElementsByClassName("footerSum")[0].innerText = sum + "%";
      }

      function deleteRow(args, event) {
        const table = document
          .getElementById("table")
          .getElementsByTagName("tbody")[0];
        const cnt = table.rows.length;
        const row = args.closest("tr");

        event.preventDefault();

        if (cnt <= 1) {
          alert("삭제하실 수 없습니다.");
        } else {
          row.remove();
        }
      }

      function addRow(args, event) {
        const table = document
          .getElementById("table")
          .getElementsByTagName("tbody")[0];
        const newRow = table.insertRow();

        const confirm =
          document.getElementById("confirm").value === "Y" ? true : false;
        let cnt = !confirm ? 4 : 3;

        const arrSeq = [...document.getElementsByClassName("seq")].map(
          (row) => row.value
        );
        const rownum =
          Math.max.apply(Math, arrSeq.length === 0 ? ["0"] : arrSeq) + 1 || 1;
        let focusCell = null;

        for (let i = 0; i < cnt; i++) {
          const cell = newRow.insertCell(i);
          if (i === 0) {
            // UPJSEQ
            cell.innerHTML =
              '<input class="seq" type="number" value="' +
              rownum +
              '" onblur="isValidNumber(this, event);" />';
          } else if (i === 1) {
            // UAHEAD
            cell.innerHTML =
              '<textarea class="text" rows="3" maxlength="180"></textarea>';
            focusCell = cell;
          } else if (i === 2) {
            cell.innerHTML =
              '<input class="ratio" type="number" onblur="sum(this, event);" />';
          } else if (i === 3 && !confirm) {
            // delete button
            cell.innerHTML =
              '<button onclick="deleteRow(this, event);">삭제</button>';
          }
        }
        focusCell?.children[0]?.focus();

        return false;
      }

      async function saveTable(args, event) {
        const seq = document.getElementsByClassName("seq");
        const text = document.getElementsByClassName("text");
        const ratio = document.getElementsByClassName("ratio");
        let arrData = [];
        let bError = false;
        let msg = "";

        event.preventDefault();

        if (seq.length < 1) {
          bError = true;
          msg = "처리할 데이터가 없습니다.";
        }

        if (bError) {
          alert(msg);
          return false;
        }

        for (let i = 0; i < seq.length; i++) {
          arrData.push({
            seq: seq[i].value,
            text: text[i].value,
            ratio: ratio[i].value,
          });
        }

        let data = JSON.stringify(arrData);
        console.log(data);

        try {
          const response = await fetch("../etc/submit.js", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: data,
          });

          if (!response.ok) {
            throw new Error("Network error : "); // 500 error
          }

          const jsonResult = await response.json();
          const result = JSON.parse(jsonResult.d); // asp

          if (result.error) {
            alert(result.message);
          } else {
            alert("saved.");
          }
        } catch (error) {
          console.error("Fetch error : ", error);
        }
      }
    </script>

    <button id="save" onclick="saveTable(this, event);">SAVE</button>
    <button id="add" onclick="return addRow(this, event);">ADD</button>
    <input type="hidden" id="confirm" />

    <table border="1" id="table">
      <thead>
        <tr>
          <th style="width: 20%">SEQ</th>
          <th style="width: 40%">TEXT</th>
          <th style="width: 20%">RATIO</th>
          <th style="width: 20%">DEL</th>
        </tr>
      </thead>
      <tbody></tbody>
      <tfoot>
        <th colspan="2">SUM</th>
        <td class="footerSum"></td>
        <td></td>
      </tfoot>
    </table>
  </body>
</html>

 

SEQ TEXT RATIO DEL
SUM

 

 

 

 

  // 사용자 고차함수
  function add(a) {
    return function (b) {
      return a + b;
    };
  }
  const result = add(1);
  const result2 = result(2);

 

HOC / HOF / Custom hook

- 고차 컴포넌트 (HOC): 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수. 컴포넌트의 재사용성과 로직 공유에 유용. 접두사 with 로 시작 권장, 부수 효과 최소화

- 고차 함수 (HOF): 함수를 인자로 받거나 함수를 반환하는 함수. 함수형 프로그래밍 패턴에서 사용됨

- 사용자 정의 (Custom Hook): React 훅을 사용하여 컴포넌트 로직을 캡슐화하고 재사용할 있게 해주는 함수

 

일급 객체

- 무명의 리터럴로 생성

- 변수나 자료구조(객체, 배열)에 저장

- 함수의 매개변수로 저장

- 함수의 반환값으로 사용


 
Outer/Inner Environment

- Outer Environment: 자바스크립트의 실행 컨텍스트에서 함수 외부에 정의된 변수와 함수들을 포함합니다. 함수가 호출될 때, 이 외부 환경은 함수의 상위 스코프를 제공합니다.

- Inner Environment: 함수 내부에서 정의된 변수와 함수들로, 함수 실행 중에만 유효합니다. 이 환경은 외부 환경의 변수에 접근할 수 있으며, 클로저를 통해 지속적으로 접근할 수 있습니다.

 

 클로저 (Closure)

- 클로저: 내부 함수가 외부 함수의 변수를 참조할 수 있게 해주는 기능입니다. 이는 내부 함수가 외부 함수의 실행이 끝난 후에도 변수에 접근할 수 있게 합니다.

- 클로저는 데이터 은닉과 상태 유지를 가능하게 하며, 함수형 프로그래밍과 비동기 작업에서 유용하게 사용됩니다. 클로저를 사용하면 비동기 코드에서도 상태를 유지할 수 있습니다.

 

 var

- var: 자바스크립트에서 변수 선언 시 사용되며, 함수 스코프를 가집니다. 함수 내부에서 선언된 var 변수는 함수 전체에서 유효하고, 함수 외부에서는 접근할 수 없습니다.

- 호이스팅: var 변수는 선언이 코드의 최상단으로 이동하지만 초기화는 원래 위치에서 이루어집니다. 따라서 변수는 선언 전에도 undefined로 접근 가능하며, 초기화된 이후에는 해당 값을 가집니다.

- 외부 참조 시 ReferenceError: 함수 외부에서 var로 선언된 변수에 접근하려고 하면 ReferenceError가 발생합니다. 이는 var 변수는 함수 스코프 내에서만 유효하기 때문입니다.

 

let

- let: 자바스크립트에서 블록 스코프를 가지는 변수 선언 방법입니다. 변수는 선언된 블록 내에서만 유효하며, 블록 외부에서는 접근할 수 없습니다. 이는 코드의 안정성과 가독성을 높이는 데 도움이 됩니다.

- TDZ (Temporal Dead Zone): let으로 선언된 변수는 초기화 전에는 접근할 수 없으며, 이 구역에서 변수에 접근하면 ReferenceError가 발생합니다. TDZ는 변수의 선언과 초기화 순서를 보장합니다.

- 블록 스코프: let은 블록 스코프를 지원하여, if 문, for 문 등 블록 내에서 선언된 변수는 그 블록 내에서만 유효합니다. 이로 인해 변수의 범위를 명확히 할 수 있습니다.

 

 const

- const: 자바스크립트에서 블록 스코프를 가지며, 상수 값을 선언할 때 사용됩니다. 선언과 동시에 값을 할당해야 하며, 이후 값의 재할당이 불가능합니다.

- 불변성: const는 변수의 재할당을 방지하지만, 객체나 배열의 내용은 변경할 수 있습니다. 이는 상수의 참조를 유지하며, 객체의 속성이나 배열의 요소를 변경할 수 있습니다.

- 블록 스코프: const는 블록 스코프를 가지므로, 선언된 블록 {} 내에서만 유효합니다. 이는 변수의 범위를 제한하여 코드의 안정성을 높이고, 블록 내에서의 의도치 않은 변경을 방지합니다.

 

실행 컨텍스트 (Execution Context)

- 실행 컨텍스트: 자바스크립트 코드가 실행되는 환경을 의미합니다. 실행 컨텍스트는 변수와 함수 선언, 실행 중의 스코프 등을 관리하며, 함수 호출 시 새로운 실행 컨텍스트가 생성됩니다.

- 컴포넌트: 실행 컨텍스트는 변수 객체(변수와 함수 선언), 스코프 체인(외부 환경에 대한 참조), this 값 등으로 구성됩니다. 실행 컨텍스트는 코드의 실행 시점에 생성되며, 함수 호출이 끝나면 제거됩니다.

- 콜 스택: 실행 컨텍스트는 콜 스택에 쌓여서 관리됩니다. 함수 호출 시 새로운 컨텍스트가 스택에 추가되고, 함수 실행이 완료되면 스택에서 제거됩니다. 이 스택 구조는 함수 호출과 실행 흐름을 관리합니다.

 

 힙 (Heap)

- 힙: 자바스크립트에서 메모리 관리의 영역으로, 동적으로 할당된 메모리를 저장하는 곳입니다. 객체, 배열, 함수 등과 같은 동적 데이터가 힙에 저장됩니다.

- 메모리 할당: 힙은 메모리 블록을 동적으로 할당하고 관리합니다. 이 영역은 크기가 고정되지 않으며, 필요한 만큼의 메모리를 자유롭게 할당받을 수 있습니다.

- 가비지 컬렉션: 힙 메모리는 가비지 컬렉터에 의해 관리됩니다. 사용되지 않는 메모리는 자동으로 해제되며, 메모리 누수를 방지합니다.

 

 화살표 함수 vs 일반 함수

- 화살표 함수: 간결한 문법을 제공하며, const add = (a, b) => a + b;와 같이 사용합니다. 화살표 함수는 상위 스코프의 this 값을 그대로 사용하며, arguments 객체를 지원하지 않습니다.

- 일반 함수: function 키워드를 사용하여 선언하며, function add(a, b) { return a + b; }와 같이 작성합니다. 일반 함수는 호출 시점에 this 값을 동적으로 결정하며, arguments 객체를 지원합니다.

- 호이스팅과 생성자: 일반 함수는 호이스팅되어 함수 정의 이전에도 호출할 있지만, 화살표 함수는 변수처럼 처리되어 정의된 후에만 호출 가능합니다. 일반 함수는 생성자로 사용될 있지만, 화살표 함수는 생성자로 사용할 없습니다.

 

 

Prototype

- https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes

- 자바스크립트는 객체 지향적 언어이자 프로토타입 언어

- 객체 간 관계 정의

- 모든 객체는 다른 객체를 기반으로 생성, 이때 기반으로 생성된 객체 -> 프로토타입

- 프로토타입은 객체의 기본적 구조와 메서드를 정의하는데 사용

- 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체(prototype object) 가짐

- 프로토타입 체이닝 : 해당 객체에 직접 정의된 메서드나 속성을 찾고, 상위 프로토타입 체인을 따라 올라가면서 찾음

- 프로토타입 체이닝을 통해 상위 객체의 메서드를 사용할 수 있음

- prototype method :  arrayInstance.filter() 처럼, 내가 직접 선언한 array 인스턴스에 체이닝해서 사용, 해당 객체의 인스턴스에 대해 호출되서 사용 -> Array.prototype 에 정의

- static method : Array.isArray() 처럼, Array 객체에 직접 체이닝. Array 객체의 생성자에 정의된 메서드, 객체의 인스턴스와 독립적으로 사용 가능


Array(배열)

- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array

- 원시값이 아니라 객체

- 자바스크립트 배열은 사이즈 조정 가능, 다양한 데이터 형식을 혼합해 저장

- 숫자(정수) 인덱스를 사용해서 접근

- length 속성으로 배열의 사이즈를 늘리고 줄일 수 있음

- Sparse arrays : 빈 슬롯(empty slots)를 포함한 배열

- 배열을 중첩하여 다차원 배열로 생성 가능

- 프로퍼티 추가 가능  ex) array.property = "hi";

- 정적 메서드 : Array.from(), Array.fromAsync(), Array.isArray(), Array.of()

- 인스턴스 메서드 : Array.prototype.map(), Array.prototype.reduce() 등 공식 문서 참고

// 다차원 배열
const a = new Array(4);
for (let i = 0; i < 4; i++) {
  a[i] = new Array(4);
  for (let j = 0; j < 4; j++) {
    a[i][j] = `[${i}, ${j}]`;
  }
}

 

배열 복사 연산

- 얕은 복사 : spread 연산자, Array.from(), Array.prototype.slice(), Array.prototype.concat()

-> 값을 복사 하는게 아니라, 원본 객체와 같은 참조(메모리 내 같은 값을 가리킴)를 공유하는 복사본

- 깊은 복사 : JSON.stringify() -> Json.parse(), structuredClone()

const fruits = ["Strawberry", "Mango"];
const fruitsAlias = fruits;
// 'fruits' and 'fruitsAlias' are the same object, strictly equivalent.
fruits === fruitsAlias; // true
// Any changes to the 'fruits' array change 'fruitsAlias' too.
fruits.unshift("Apple", "Banana");
console.log(fruits);
// ['Apple', 'Banana', 'Strawberry', 'Mango']
console.log(fruitsAlias);
// ['Apple', 'Banana', 'Strawberry', 'Mango']

 

Array-like Objects(유사 배열)

- 배열처럼 숫자 인덱스 사용

- length 속성

- 배열의 메서드(push, pop, forEach, ..)를 직접 사용할 수 없음

- Array 의 인스턴스가 아님 

- ex) NodeList

const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
console.log(arrayLike.length); // 3
// arrayLike.push('d'); // TypeError: arrayLike.push is not a function

 

Working with array-like objects

- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#working_with_array-like_objects

- Some JavaScript objects, such as the NodeList returned by document.getElementsByTagName() or the arguments object made available within the body of a function, look and behave like arrays on the surface but do not share all of their methods. The arguments object provides a length attribute but does not implement array methods like forEach().

Array methods cannot be called directly on array-like objects.

Array 처럼 행동하지만, Array의 메서드를 공유하지 않는 자바스크립트 Objects

예시) NodeList, 함수의 arguments

length 속성 제공 O, forEach 메서드 제공 X

 

NodeList

- https://developer.mozilla.org/en-US/docs/Web/API/NodeList

- 노드 컬렉션 objects 

- Node.childNodes 속성이나 document.querySelectrotAll() 메서드를 호출하면 NodeList 반환

- Node.childeNodes : NodeList(Live collection), DOM의 변경사항을 실시간으로 컬렉션에 반영 

- document.querySelectorAll() : NodeList(Static collection), DOM을 변경해도 컬렉션에 영향 X

- 메서드

1) item() : 인덱스 반환

2) forEach() : NodeList의 element를 함수에 전달

3) entries(), keys(), values() : iterator 반환, 컬렉션에 포함된 모든 키/값 순회

- cf. 순서를 보장하지 않는 for .. in 문은 사용 금지, 대신 순서를 보장하는 for .. or 문 사용

var list = document.querySelectorAll("input[type=checkbox]");
Array.prototype.forEach.call(list, function (item) {
  item.checked = true;
});

 

유사 배열 객체 -> 배열 변환

- Array.from(arrayLike, mapFn?, thisArg?) : 순회 가능 객체나 유사 배열 객체를 배열로 변환 # Array.from 

- spread 연산자 사용

const arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
const arr = Array.from(arrayLike);
console.log(arr); // ['a', 'b', 'c']

const nodeList = document.querySelectorAll('div');
const arr = [...nodeList];
console.log(arr); // 배열로 변환된 NodeList

 

 

getElementById

- https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById

- Document.getElementById(id)
- 파라미터로 받은 id로 DOM을 검색(대소문자 유의)
- Element Object 반환 (Document inherits) ex. HTMLElement, SVGElement, MATHMLElement

- 찾고자 하는 id가 없으면 null 리턴

- Id가 없는 element에 접근하기 위해서는 selector를 이용하는 Document.querySelector() 사용 권장

- 다른 element를 찾아보는 querySelector() / querySelectorAll() 랑 다르게, 반드시 id는 전체 Document에서 unique 해야함

-> Document에 없는 element는 찾을 수 없고, Node.insertBefore()이나 비슷한 메서드를 이용해 DOM에 만들어야 id로 검색 가능

 

<html lang="en">
  <head>
    <title>getElementById example</title>
  </head>
  <body>
    <p id="para">Some text here</p>
    <button onclick="changeColor('blue');">blue</button>
    <button onclick="changeColor('red');">red</button>
    
    <script>
        function changeColor(newColor) {
          const elem = document.getElementById("para");
          elem.style.color = newColor;
        }
    </script>
  </body>
</html>

 

 

 getElementsByName

- https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByName

- getElementsByName(name)

- NodeList collections 반환(노드 컬렉션)  cf. querySelectorAll() 도 마찬가지로 NodeList 반환 # 참고 링크

-> A Live NodeList 반환 : 같은 name element가 추가되거나 삭제되면 자동적으로 새로운 elements 업데이트

<!doctype html>
<html lang="en">
  <head>
    <title>Example: using document.getElementsByName</title>
  </head>
  <body>
    <input type="hidden" name="up" />
    <input type="hidden" name="down" />
    <script>
        const up_names = document.getElementsByName("up");
        console.log(up_names[0].tagName); // displays "INPUT"
    </script>
  </body>
</html>

 

 

querySelector

- https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector

- Document.querySelector(selectors)

- 파라미터로 selectors

-> 유효한 CSS selector string이 아닐 경우 예외 처리(SyntaxError)

- Document 내에서 selectors를 만족하는 첫번째 Element 반환

- 만족하는 selectors가 없는 경우 null 반환

- 만약, 첫번째 Element가 아니라 Element 리스트가 필요한 경우 querySelectorAll() 메서드 사용

 

// Class
const el1 = document.querySelector(".myclass");

// Complex selectors 
// 클래스가 user-panel.main인 div 안에 있는, input name이 login인 첫번째 element 반환
const el2 = document.querySelector("div.user-panel.main input[name='login']");

// Negation
const el3 = document.querySelector(
  "div.user-panel:not(.main) input[name='login']",
);

// ID
const el4 = document.querySelector("#myID");

 


비교

  getElementById getElementsByName querySelector querySelectorAll
parameter id (unique) name 선택자 선택자
return Element NodeList 첫 번째 Element NodeList

 

 

실제 사용 및 코드 리뷰

사실 하기처럼 아이디 유효성 체크하는 코드 작성하다가 classList 쓰려고 querySelector 사용한건데, 제대로 사용하지 못한 것 같다.

어차피 id로 찾을 거 였으면 좀 더 직관적으로 getElementById 를 사용하는게 나을뻔 했다. 

(늘, 시간에 쫓겨서 코드짜다가 뒤늦게 밀려드는 후회..🥺)

// onblur event
function checkId(event) {
    const idVal = event.target.value;
    const id = document.querySelector("#" + event.target.id);
    const idLabel = document.querySelector("#idLabel");

    if (!idVal || isNaN(idVal)) {
    	idLabel.classList.add("alert-show");
        id.classList.add("is-invalid");
    } else {
    	idLabel.classList.remove("alert-show");
        id.classList.remove("is-invalid");
        }
    }
}

 

 


script - head

<!DOCTYPE html>
<html lang="en">
	<head>
    	<script src="script.js"></script>
    </head>
    <body></body>
</html>

- HTML 파싱 중 <script> 태그를 만나서 JS 파일 파싱 및 실행

- JS 파싱 + 실행 이 끝나야 나머지 body를 파싱하기 때문에 시간이 오래 걸림

- DOM 조작이 필요없는 경우 사용

- ES6 defer 속성과 유사

 

script - body

<!DOCTYPE html>
<html lang="en">
	<head></head>
    <body>
    	<div id="div1">안녕!</div>
    	<script src="script.js">
        	var divId = document.getElementById("div1");
        </script>
    </body>
</html>

- HTML 파싱이 끝나야 JS 파싱 및 실행

- DOM을 조작하기 위해서는 조작하고자 하는 내용 뒤에 <script> 태그 삽입

- ES6 async 속성과 유사

 

 script - async

<!DOCTYPE html>
<html lang="en">
	<head>
    	<script async src="script.js"></script>
    </head>
    <body></body>
</html>

- ES6부터 등장

- HTML과 JS를 병렬적으로 파싱

- JS 파싱이 끝나면 즉시 실행되기 때문에 실행되는 동안 HTML파싱 멈춤

- JS 실행이 끝나야 HTML 파싱

- 한 페이지에서 여러 js 파일을 로드하는 경우, 빠른 것 부터 불러와서 순서가 보장되지 않음

- <head>, <body> 태그 둘 다 삽입 가능

 

 script - defer

<!DOCTYPE html>
<html lang="en">
	<head>
    	<script defer src="script.js"></script>
    </head>
    <body></body>
</html>

- ES6부터 등장

- HTML과 JS를 병렬적으로 파싱

- HTML 파싱이 끝나야 JS 실행

- 순서대로 JS 파일 실행, 순서 보장

 

 

 

 

 

 

DOM

- createElement() : Node 생성

- Node : 객체, 트리 구조로 이루어짐 

 

 Text

- var textNode = document.createTextNode("안녕");

- TextContent : <script>, <style> 요소를 포함해서 모든 요소를 가져옴

- InnerText : 사람이 읽을 수 있는 요소만 처리, css에 종속적이라 reflow비용 발생

- InnerHtml : html 파싱 비용 발생 X, html을 그대로 가져옴
- https://developer.mozilla.org/ko/docs/Web/API/Node/textContent

 

객체에 key 존재 여부 확인

- "key" in obj : 해당 객체의 prototype chain을 거슬러 올라가서(상속된 곳 까지) key가 존재하면 true

- Object.prototype.hasOwnProperty(obj, "key") : 해당 객체가 key를 직접 가질 때에만 true

 

 

 

 

 

 

 

 

스레드

- 자바스크립트는 원래 개별/싱글 스레드 -> 메인 스레드 이벤트 루프가 싱글 스레드

- 브라우저(window)에 멀티 스레드를 탑재  

-> JS를 실행하는 환경이 멀티 스레드인 것  cf. Node.js도 마찬가지로 JS 구동 환경이나 라이브러리임

 

자바스크립트 엔진

- 메모리 힙 : 참조(배열, 객체, 함수) 데이터 저장, 메모리 할당, 가비지 컬렉터

- 콜 스택 : 원시 데이터 저장, 실행 컨텍스트, 싱글 스레드 

 

Web API

- Web API가 DOM을 조작해서 Stack구조를 편하게 사용할 수 있게 도와줌

JavaScript Engine
메모리 힙
콜스택
Event Loop
콜 스택과 큐를 이어주는 역할
콜 스택이나 큐가 비어있는지 확인 
Browser
Web API

마이크로 태스크 큐(priority 1)
태스크 큐
애니메이션 프레임 큐

- 이벤트 루프는 태스크 큐에서는 1개씩 실행시키는 것에 반해, 마이크로 태스크 큐는 큐가 전부 비워져야 이벤트 루프가 멈춤

 

애니메이션 프레임 큐

- 애니메이션 프레임 큐는 60 프레임마다 다시 화면을 그림

- requestAnimationFrame(callback) 함수로 1초에 60번 동작, 무한 호출되지 않고 1초에 60번만 호출됨

-> 이후 callback 함수를 호출하고, 다 실행한 뒤 layout(css som)을 그림

cf) requestAnimationFrame()은 JS 파일이랑 같이 페인팅, 원래는 JS를 만나면 멈추나 콜스택 원리로 가능

 

브라우저 렌더링 단계 - CSS관점

1) requestAnimationFrame()

2) Layout : CSSOM, css tree를 만드는 단계, html 계산 // Reflow : Layout 단계 다시 실행

3) Paint : CSSOM + DOM 합치는 단계, css 계산

4) Composite : 브라우저에 출력하는 단계, 레이어 간 합성 개념(이전에는 한 레이어에서 하는 방식)


display / hidden / ::before

- display: none; 속성 사용시 DOM에서 노드 생성 X

- hidden: true; 속성 사용시 노드 생성 O 

- ::before 속성 사용 시 노드 생성 X, 레이아웃은 만듦

 

tip

- css 속성 중 top과 같은 속성은 구조를 다시 짜서 reflow

- transition과 같은 속성을 사용하면

 

setTimeout 실행 단계

1) 콜 스택에서 setTimeout 실행

2) setTimeout 내부 콜백 함수는 Timer API(Web API)로 이동

3) 타임아웃되면 콜백 함수가 태스크 큐로 이동

4) 콜 스택이 비면 태스크 큐에 있는 콜백 함수를 콜 스택으로 가져와 실행

 

References

- 이벤트 루프 : https://inpa.tistory.com/entry/🔄-자바스크립트-이벤트-루프-구조-동작-원리

- 렌더링 : https://d2.naver.com/helloworld/5237120

 

 

프로세스(process)

- 프로그램을 실행하는 단위

- 하나의 프로그램 실행은 하나의 프로세스를 가지고, 그 프로세스 내부에서 모든 작업 처리

 

스레드(thread)

- 하나의 프로그램 내에서 동시에 여러 개의 복잡한 작업을 수행할 필요성 대두

- 프로세스보다 작은 실행 단위

- 하나의 프로세스 내에서 여러 개의 스레드를 만들 수 있음

- 스레드끼리 메모리 공유 -> 여러 작업 동시 수행

 

자바스크립트 : 싱글 스레드

- 최초의 자바스크립트는 브라우저에서 HTML을 그리는데 도움을 주는 보조적 역할

- 스레드는 하나의 프로세스에서 동시에 서로 같은 자원에 접근할 수 있는 문제(동시성 문제) 발생 ex) DOM 조작

-> 싱글 스레드에서 동기 방식으로 작동되도록 설계

- 한 번에 하나의 작업만 동기적(직렬 방식)으로 처리

-> 요청이 시작된 이후에는 무조건 응답을 받은 이후에 다른 작업 처리, 그동안 다른 모든 작업 대기

<-> 비동기(asynchronous) : 한 번에 여러 작업 실행

- Run-to-completion : 모든 코드는 동기식으로 한번에 하나씩 순차적으로 처리

 

이벤트 루프(Event loop)

- 자바스크립트 런타임 외부에서 자바스크립트 비동기 실행을 돕기 위해 만들어진 장치

- 호출 스택에 실행 중인 코드가 있는지, 태스크 큐에 대기 중인 함수가 있는지 반복해서 확인

- 호출 스택이 비워 있는지 여부를 확인하고, 수행해야 할 코드가 있다면 오래된 태스크부터 순차적으로 꺼내와서 자바스크립트 엔진을 이용해 실행

-> 동기식으로 수행되는 메인 스레드가 아닌 태스크 큐가 할당되는 별도의 스레드에서 비동기 함수 수행

function bar() {
	console.log('bar');
}

function baz() {
	console.log('baz');
}

function foo() {
	console.log('foo');
    setTimeout(bar(), 0);
    baz();
}

foo(); // foo -> baz -> bar

1) call stack : Foo()

2) call stack : console.log('foo')

3) console.log('foo') 실행

4) call stack : setTimeout(bar(), 0)

5) 타이머 이벤트 실행 후 -> task queue로 이동, call stack에서 제거

6) call stack : baz() 

7) call stack : console.log('baz')

8) console.log('baz') 실행

9) call stack에서 baz() 제거

10) call stack에서 foo() 제거

11) event loop : call stack empty 체크 후 task queue 확인

12) console.log('bar') task queue -> call stack 이동 

13) console.log('bar') 실행

14) call stack에서 bar() 제거


 호출 스택(call stack)

- 자바스크립트에서 수행해야 할 코드나 함수를 순차적으로 담아두는 스택

 

태스크 큐(task queue)

- 자료구조 set 형태

- 실행해야 할 태스크 : 비동기 콜백 함수, 이벤트 핸들러

- ex) setTimeout, setInterval, setImmediate

 

✓ 마이크로 태스크 큐

- 기존 태스크 큐와 다르게 다른 태스크 처리

- 마이크로 태스크 큐가 빌때까지 기존 태스크 큐의 실행은 뒤로 미뤄짐

- ex) process.nextTick, promises, queueMicroTask, MutaionObserver

- 각 마이크로 태스크 큐가 끝날 때 마다 한 번씩 렌더링(동기 코드와 유사)

- cf) 동기 코드는 동기 코드 실행 후 렌더링

 

✓ 구조 분해 할당(Destructing assignment)

- 배열 또는 객체의 값을 분해해 개별 변수에 즉시 할당

- w전개 연산자는 마지막에 사용

 

✓  배열 구조 분해 할당 

const array = [1, 2, 3, 4, 5];

const [first, second, third, ...arrayRest] = array;
// first 1, second 2, third 3, arrayRest [4, 5]

const first, , , , fifth] = array; 
// 2, 3, 4 는 표현식이 없으므로 변수 할당 생략
// first 1, fifth 5


const array2 = [1, 2];
const [a = 10, b = 10, c = 10] = array2;
// a 1, b 2, c 10

const [a = 1, b = 1, c = 1, d = 1, e = 1] = [undefined, null, 0 , ''];
// undefined일 때만 기본값으로 사용
// a 1, b null, c 0, d '', e 1

 

✓  객체 구조분해 할당

const object = {
	a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: 5
};

const { a, b, c ..objectRest } = object;
// a 1, b 2, c 3, objectRest = { d: 4, e: 5 ]

const object2 = {
	a: 1,
    b: 2
}

const { a: first, b: second } = object2;
// first 1, second 2

const { a = 10, b = 10, c = 10 } = object2;
// 기본값 부여
// a 1, b 2, c 10

const key = 'a';
const { [key]: a } = object2;
// a 1

 

✓ 전개 구문

- 전개 연산자 ... 사용

- 기존의 객체나 배열에 영향을 미치지 않고 값 복사

 

 

✓ 객체 초기자

- 객체를 선언할 때 객체에 넣고자 하는 키와 값을 가지고, 변수가 이미 존재한다면 해당 값을 간결하게 넣어주는 방식

const a = 1;
const b = 2;

const obj = { a, b }; // { a: 1, b: 2 }

 

Array 프로토타입 메서드

- map : 인수로 전달받은 배열과 똑같은 길이의 새로운 배열을 반환하는 메서드, 각 아이템을 순회하면서 콜백으로 연산한 결과로 구성된 새로운 배열 생성

- filter : 콜백 함수를 인수로 받아 이 함수의 truthy 조건을 만족하는 경우에만 해당 원소를 반환하는 메서드, filter 결과에 따라 원본 배열 길이 이하의 새로운 배열 반환

- reduce : 콜백 함수와 초기값을 인수로 받아 초기값에 따라 배열, 객체 또는 그 외의 무언가를 반환하는 메서드, reducer 콜백 함수를 실행하고 초기값에 누적해 결과 반환

- forEach : 콜백 함수를 받아 배열을 순회하며 단순히 콜백 함수를 실행하기만 하는 메서드, 반환값 undefined

const arr = [1, 2, 3, 4, 5];

arr.map((item) => item * 2); // [2, 4, 6, 8, 10]

arr.filter((item) => item % 2 === 0); // 2, 4

arr.reduce((result, item) => { return result + item}, 0); // 15

arr.forEach((item) => console.log(item)); // 1, 2, 3

 

✓ 삼항조건연산자

- 조건문 ? 참일 때 값 : 거짓일 때 값

 

 

 

 

 

Environment Record ⭐️

- Environment record

- Outer environment record

- Inner environment record

- https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0

+ 실행 컨텍스트 -> 콜스택 -> 이벤트

 

 

✓ 스코프

- var : 함수 레벨 스코프

- let / const : 블록 레벨 스코프

 

✓ 클로저

- 지역 변수가 함수 종료 이후에도 살아있는 것

 

함수

- 자바스크립트에서 함수는 객체

- 자기 스코프에서 없는 값은 외부를 참조함

- 함수는 리턴값이 없으면 undefined

function hello() {
	let a = 1;
    function inner() {
    	console.log(++a);
    }
    inner();
}

const outer2 = outer(); // 2
inner environment record : { }
outer environment record : { a: 1, inner: f}
global environment record : { outer:f , outer2 undefined }
실행 컨텍스트

- 스코프 체이닝을 통해 inner -> outer -> global envrionment record 참조

- 여기서 const outer3 = outer() 를 생성하면 2가 계속 출력

- 3, 4로 증가시키면서 출력하고 싶으면 inner(); -> return inner(); 로 코드 변경

- outer2가 undefined가 아닌, inner() 펑션을 가지게 됨

- 그래서 스코프 체이닝을 통해 function outer / 변수 a가 죽어버리는게 아니고, 전역의 범위에서 변수 a를 가지고 있게 되므로 2, 3, 4로 증가되어 출력 

 

Array 프로토타입

- 배열도 객체 중 하나

[1, 2, 3].forEach(() => { ... });

- forEach가 Array의 프로토타입이기 때문에 사용 가능

- Array의 상위 Object hasOwnProperty() 사용 가능

- cf) Object의 constructor에 정의된 keys() 같은 메서드는 Object만 사용 가능

 

✓ 원시값 프로토타입

10.toString();

- 원시값인 10이 어떻게 Number 객체의 프로토타입을 사용할 수 있는지

-> JavaScript 엔진이 숫자 리터럴을 일시적으로 Number 객체로 변환하여 toString()메서드 호출 : 박싱(Boxing)

-> 다시 "10"이라는 결과값을 문자열 원시값으로 반환

 

클래스 작동 방식

function user(_name, _age) {
	let._logged = true;
    
    return {
    	get name() { return _name },
        set name(v) { _name = v },
        getStatus() { return _logged ? 'login' : 'logout'
    }
}

const person = user('mimi', 20);
perosn.name(); // mimi

- 만약 class의 static 메서드 처럼 사용하고 싶다면 -> function 자체에 Object.getPrototypeOf(인스턴스)

- 자바스크립트에도 ES6개념부터 클래스라는 개념이 있으나, 진짜 클래스가 아니고 프로토타입 개념

 

 


클래스
- ES6이후 등장한 개념
- 리액트 16.8 버젼 : 클래스형 컴포넌트(생명주기 함수) -> 함수형 컴포넌트
- 특정한 객체를 만들기 위한 일종의 템플릿

class Car {
	constructor(name) {
    	this.name = name
    }
    
    honk() {
    	console.log(`${this.name}이 경적을 울립니다.`);
    }
    
    static hello() {
    	console.log('저는 자동차입니다.')
    }
    
    set age(value) {
    	this.carAge = value
    }
    
    get age() {
    	return this.carAge
    }   
}

const myCar = new Car('자동차');

myCar.honk(); 
Car.hello();
myCar.hello; // Uncaught TypeError: myCar.hello is not a function

myCar.age = 32;
console.log(myCar.age, myCar.name); // 32 자동차

 
 
constructor
- 생성자
- 하나만 존재 가능, 생략도 가능
 
프로퍼티
- 클래스의 속성값
- JS에서 기본적으로 모든 프로퍼티 값은 public
- ES2019부터 #를 붙여서 private 선언 가능
 
✓ getter / setter
- 클래스에서 무언가 값을 가져올 때 사용
- 예약어 get / set 사용
 
✓ 인스턴스 메서드
- 클래스 내부에 선언한 메서드
- 자바스크립트의 prototype에 선언 : Object.getPrototypeOf(인스턴스) === 클래스.prototype cf)인스턴스.__proto__ 
-> 프로토타입의 메서드, 프로토타입에 선언되었기 때문에 프로토타입 체이닝을 통해 새로 생성한 인스턴스 메서드에서 부모 클래스의 메서드에 접근 가능(위 예제 honk()의 경우)
 
✓  정적 메서드 
- 예약어 static 사용
- 클래스의 인스턴스가 아닌 이름으로 호출
- 정적 메서드 내부의 this : 클래스로 생성된 인스턴스가 아닌, 클래스 자신을 가리킴 
- 인스턴스를 생성하지 않아도 사용 가능
- 재사용성
- ex) 애플리케이션 전역에서 사용하는 유틸 함수
 
✓ 상속
- 에약어 extends 사용
- 기존 클래스를 상속받아서 자식 클래스에서 이 상속받은 클래스를 기반으로 확장하는 개념
 
✓ 클래스와 함수의 관계
- ES6이전에는 자바스크립트 프로토타입을 활용해 클래스의 작동 방식을 동일하게 구현
- 객체지향 언어를 사용하던 다른 프로그래머가 좀 더 자바스크립트에 접근하기 쉽게 만들어주는 Syntactic sugar 역할
- 리액트에서 클래스형 컴포넌트 생성 시 React.Component, React.Purecomponent 상속

// 바벨로 변환
'use strict'

// 클래스가 함수처럼 호출되는 것을 방지
function _classCallCheck(instance, Constructor) {
	...
}

// 프로퍼티 할당
function _defineProperties(target, props) {
	...
}

// 프로토타입 메서드와 정적 메서드 선언
function _createClass(Constructor, protoProps, staticProps) {
	...
}

 
 
클로저
- 리액트 함수 컴포넌트는 대부분 클로저 방식에 의존 cf) 리액트 클래스 컴포넌트 : 클래스, 프로토타입, this
- 함수와 함수가 선언된 어휘적 환경(Lexical Scope)의 조합
- Lexical Scope : 변수가 코드 내부에서 어디서 선언되었는지
- 호출되는 방식에 따라 동적으로 결정되는 this와 다르게 코드가 작성된 순간에 정적으로 결정
- 클로저는 이러한 어휘적 환경을 조합하여 코딩하는 기법
 
✓ 스코프(Scope)
- 변수의 유효 범위 
 
✓  전역 스코프(Global scope)
- 전역 레벨에 선언
- ex) 브라우저 window, Node.js Global

var global = 'global scope';

console.log(global === window.global) // true

 
✓  함수 스코프
- 자바스크립트는 기본적으로 함수 레벨 스코프를 따름
- { } 블록이 스코프 범위를 결정하지 않음

if (true) {
	var global = 'global scope';
}

console.log(global); // global scope
console.log(global === window.global); // true

function hello() {
	var local = 'local variable';
    console.log(local); // local variable
}

hello();
console.log(local); // Uncaught ReferenceError: local is not defined

 
✓ 클로저의 활용
- 자바스크립트는 함수 레벨 스코프를 가지고 있으므로, 스코프는 동적으로 결정

function outerFunction() {
	var x = 'hello';
    function innerFunction() {
    	console.log(x);
    }
    return innerFunction;
}

const innerFunction = outerFunction();
innerFunction() // hello

- outerFunction이 innerFunction을 반환하였고, innerFunction에는 x라는 변수가 존재하지 않지만, 해당 함수가 선언된 어휘적 환경(outerFunction)에는 x가 존재하며 접근할 수 있으므로 정상적으로 'hello'출력 가능
- 전역 스코프는 어디에서든 접근할 수 있으므로 보안에 취약 -> 클로저 활용
- 리액트에서 클로저 : useState
- 클로저에서 var 보다 let, const 사용 권장
- 클로저는 생성될 때 마다 그 선언적 환경을 기억해야 하므로 비용 발생 -> 스크립트를 실행하는 시점부터 클로저를 메모리에 올려두고 시작
 

+ Recent posts