자바스크립트, class를 문법적 설탕(syntactic sugar)이라 부르는 이유

function으로 구현하는 class의 동작 방식

‘문법적 설탕’은 통용되는 단어이지만 어감이 이상한 건 어쩔 수 없다. 의역하여 ‘편리한 구문 용법’ 정도가 더 이해하기 쉬울 것 같다.

위키에서 syntactic sugar의 정의는 다음과 같다.

In computer science, syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express.
컴퓨터 과학 분야에서 ‘편리한 구문 용법’이란 읽기 쉽고 표현하기 쉽도록 구성된 프로그래밍 언어 내의 구문이다.

즉, 달콤한 초콜릿처럼 손이 자주 가는 구문이라고 볼 수 있을 것 같다.

자바스크립트는 처음부터 모든 기능을 갖고 태어나지 않았으므로 ES 버전으로 대표되는 추가 기능은 기존의 문법으로 구현된 것이 많다.

class 또한 기존의 OOP에서 차용한 개념을 자바스크립트에서 구현한 것으로 구현 방식은 자바스크립트의 문법을 벗어나지 않는다.

따라서 사용자가 선언하고 사용하는 방식은 다르지만 다음에서 확인할 두 코드의 작동 방식은 동일하다.

function으로 선언된 부분이 기본 동작 방식이며 class가 선언되면 이 function과 완전히 동일한 방식으로 구현된다.

같은 기능을 구현할 때 function과 class 중 어떤 것에 더 선뜻 손이 가는지를 묻는다면 설탕의 의미를 생각해볼 수 있다.

function 사용

function createGame( user, point ) {
  this.user = user;
  this.point = point;
}

createGame.prototype.addPoint = function() { this.point++; };

const myGame = new createGame("Yoon", 0);

myGame.addPoint();

class 사용

class CreateGame {
  constructor (user, point) {
    this.user = user;
    this.point = point;
  }
  addPoint() { this.point++; }
}

const myGame = new CreateGame("Yoon", 0);
myGame.addPoint();

삽입 정렬(insertion sort), 자바스크립트로 작성하기

앞에서부터 순서대로 정렬하여 자신의 위치에 삽입하는 방법

삽입 정렬은 배열의 앞에서부터 시작하여 자신의 앞에 있는 모든 요소와 크기 비교를 통해 자신에게 맞는 위치에 요소를 삽입한다.

다음 그림과 같은 방식이다.

3을 [6]의 적절한 위치에 삽입하고 다음으로는 5를 [3, 6]의 적절한 위치에 삽입한다. 다음으로는 1을 [3, 5, 6]의 적절한 위치에 삽입하고 이 방식으로 삽입 작업이 반복된다.

코드는 다음과 같다.

const insertionSort = (arr) => {

  for(let i=1;i<arr.length;i++){
    const checkValue = arr[i];
    let left = i-1;

    while(left>=0&&arr[left]>checkValue){
      arr[left+1] = arr[left];
      left--;
    }
    arr[left+1] = checkValue;
  }
  return arr;
}

console.log(insertionSort([6,3,5,1,9,4,8,2,7]));

다른 메모리 공간이 필요하지 않고 안정 정렬(stable sort)이며 시간 복잡도는 O(N)이다. 단점으로 최악의 시간 복잡도는 O(N^2)가 될 수 있으며 배열이 커질수록 비효율적이다.

퀵 정렬(quick sort), 자바스크립트로 구현하기

문제를 세분화하는 분할 정복(divide and conquer)과 퀵 정렬

퀵 정렬은 하나의 중심(pivot) 값을 기준으로 큰 값과 작은 값을 분류하고 이 작업을 반복하여 정렬하는 방법이다.

그림으로 보면 다음과 같다.

첫 요소인 6을 기준으로 잡아서 이보다 작은 수를 left, 큰 수를 right로 분류하고 이 작업을 세분화해서 반복 진행하면 최종적으로 정렬된 배열을 반환할 수 있다.

const quickSort = (arr) => {
  if(arr.length<=1){
    return arr;
  }

  const pivot = arr[0];
  const left = [];
  const right = [];

  for(let i=1; i<arr.length; i++){
    if(arr[i]<pivot){
      left.push(arr[i]);
    }else{
      right.push(arr[i]);
    }
  }
  return [...quick(left), pivot, ...quick(right)]
}

console.log(quickSort([6,3,5,1,9,4,8,2,7]));

퀵 정렬은 최악의 경우를 제외하면 O(NlogN)의 시간 복잡도를 갖는 속도가 장점이지만 단점은 최악의 경우(이미 정렬된 배열) 시간 복잡도가 O(N^2)로 대폭 증가하고 불안정 정렬(unstable sort, 동일한 값에 대해서는 순서가 바뀔 수 있음)이라는 점이다.

옵셔널 체이닝(optional chaining)의 유용함(javascript)

ES2020, typescript3.7, Babel 7.8.0, CRA 3.3.0 Node.js 14 지원

옵셔널 체이닝의 연산자는 ?. 입니다.

최신 버전(ES2020)에서 사용 가능한 기능으로 객체 체인 속성의 존재 여부를 확인합니다.

애플워치 구매를 고민하는 친구에게 하던 말처럼 ‘있으면 모르겠지만 없으면 너무 불편한’ (결론은 익숙해지면 아주 유용한) 기능의 연산자입니다.

자바스크립트로 객체 내부의 속성에 접근할 때, 존재하지 않는 객체(undefined)의 내부 속성에 접근하면 에러가 발생합니다.

const myObject = {
   myOne:{
      name:'one'
   }
}

console.log(myObject.myTwo); // undefined
console.log(myObject.myTwo.name); // error

위와 같이 존재하는 객체의 하부 속성이 존재하지 않으면 undefined를 반환하지만 존재하지 않는 객체의 하부 속성에 접근하면 에러가 발생합니다.

객체와 속성이 존재하지 않는 상황을 가정하여 코드를 보수적으로 짜는 것은 습관화되어 있지 않으면 놓치기 쉽습니다.


1. 옵셔널 체이닝 사용

앞의 에러를 방지하기 위해서는 다음과 같이 먼저 객체의 존재 여부를 확인 후 존재하는 경우에만 속성에 접근하는 방법을 사용할텐데요.

const myObject = {
   myOne:{
      name:'one'
   }
}

console.log(myObject.myTwo&&myObject.myTwo.name); // undefined

&&를 사용해 존재 여부를 먼저 체크합니다.

A && B를 예로 들어,

A = true면 return B,
A = false면 return A

와 같습니다.

false의 조건은 다음과 같습니다.

  • undefined
  • null
  • NaN
  • 0
  • empty string
  • “”

위 코드는 존재 시 name 속성을 반환하지만 속성이 존재하지 않더라도 undefined가 반환되므로 에러가 발생하지 않습니다.

이 체크 부분의 코드를 간략하게 만들어 코드의 가독성을 높일 수 있는 것이 옵셔널 체이닝입니다.

const myObject = {
   myOne:{
      name:'one'
   }
}
console.log(myObject.myTwo?.name); // undefined

앞의 코드와 완전히 동일한 결과가 발생합니다.

굉장히 단순하고 직관적인 것이 매력적입니다.

물론 속성 이외에 메서드나 함수 호출에도 사용할 수 있습니다.

const myObject = {
   show(){
     console.log('show');
   }
}
console.log(myObject.show?.()); // show

console.log(myObject.exist?.()); // undefined
console.log(myObject.exist()); // error

호출한 메서드가 존재하지 않으면 옵셔널 체이닝은 undefined를 반환하지만 사용하지 않으면 에러가 발생합니다.

이와 같이 옵셔널 체이닝을 사용하면 null, undefined로 발생하는 에러를 많이 때려 잡을 수 있습니다.

그러나 과유불급을 잊지 말아야 합니다.

여기 저기 남발하면 에러 발생은 현저하게 줄어들겠지만 정작 문제가 발생해야 하는 부분에서도 문제가 발생하지 않아

디버깅이 어려워지는 문제가 발생하므로 꼭 필요한 부분에만 사용하는 것이 좋습니다.

map, reduce, filter 서로 대체가 가능할까 (javascript)

자바스크립트 배열을 처리하는 메서드 map, reduce, filter

자바스크립트에서 배열의 요소에 접근하는 대표 메서드인 map, reduce, filter는 개발자라면 필수적으로 그리고 좀 더 상세히 알고 가면 좋을 것 같습니다.

각각의 특징에 대해 이야기하면 대부분은 아마도

  • map -> 모든 요소에 한 번씩 접근하기
  • reduce -> 모든 요소를 하나로 합치기
  • filter -> 모든 요소에서 원하는 것만 골라내기

정도의 개념을 갖고 있을 것 같습니다.

굳이 위 메서드가 아니라 forEach 문 등 다른 방식을 사용해도 같은 로직의 구현이 가능한데요.

그렇다면 위 메서드끼리도 서로 변경하여 같은 결과를 반환하도록 사용이 가능할까요?

각 메서드의 기능을 살펴보면서 알아보도록 하겠습니다.


1. map

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

인덱스는 현재값의 인덱스, 배열은 map이 순회하는 배열 array를 의미합니다.

이제 map을 사용하는 가장 기본적인 코드를 확인해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value) => { return value });

console.log(newArray);  // [10, 20, 30, 40, 50]
console.log(myArray===newArray); //false

map은 해당 배열 자체를 바꾸는 것이 아니므로 리턴값을 사용하기 위해서는 다른 변수에 넣어 주어야 합니다.

파라미터에서 index는 현재 접근한 요소의 index이며 array는 순회하는 배열 전체를 전달합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.map((value, index, array) => {
   if(index%2){
     return value-array[1];   
   }else{
     return value-array[0];
   }                              
});

console.log(newArray);

다음 코드는 자주 접하는 실수 중 하나로 코드의 동작 여부를 생각해 볼 필요가 있습니다.

const myData = {10, 20, 30, 40, 50};

const newData = myData.map((value) => {return value+1});

console.log(newData);

위 코드는 에러가 발생합니다.

바로 map의 특성 때문인데요. map은 배열에서 동작하는 메서드이며 객체 object에서는 동작하지 않습니다.

하지만 다음과 같이 배열 내부의 object는 접근 가능합니다.

const myData = [{name:'chamchi', age:3},{name:'ggongchi', age:2},{name:'myulchi', age:1}];

const newData = myData.map((value) => value.name );

console.log(newData); //['chamchi', 'ggongchi', 'myulchi']

그렇다면 map을 사용해 reduce나 filter 메서드와 같은 로직을 구현할 수 있을까요?

다음 코드를 통해 확인할 수 있을 것 같습니다.

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

const newArray = myArray.map((value) => {
   if(value%2){
     return value;   
   }                            
});

console.log(newArray); //[1, undefined, 3, undefined, 5]

홀수의 요소만 반환하고자 위와 같이 작성하더라도 map은 모든 요소를 반환합니다.

짝수 부분은 리턴값이 정의되지 않아 undefined로 반환된 것을 확인할 수 있습니다.

따라서 map은 reduce, filter의 기능은 구현할 수 없을 것 같습니다.


2. reduce

먼저 메서드는 위와 같으며 *가 붙어있는 파라미터는 생략할 수 있습니다.

누적값은 배열을 순회하면서 작업의 처리 결과를 누적하는 값으로 순회가 종료되면 최종 리턴값이 됩니다.

reduce의 예시로 모든 요소를 더하는 예시가 많다보니 용도는 전체 요소 더하거나 빼서 하나로 만들기라고

알고 있을 수 있지만 누적값 설정을 통해 다양한 방식으로 활용이 가능합니다.

인덱스는 현재값의 인덱스, 배열은 reduce가 순회하는 array 배열을 의미합니다.

마지막에 있는 시작값은 누적값의 처음 기본값을 전달합니다.

생략하면 0이 기본값이 됩니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value });

console.log(newArray); // 150

시작값이 생략되었으므로 acc의 기본값은 0이 됩니다.

그럼 초기값을 설정한 샘플을 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { return acc + value },100);

console.log(newArray); // 250

이를 응용하여 초기값을 설정하면 다양한 결과를 만들 수 있습니다.

먼저 배열을 다른 배열로 복사하는 기능을 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value) => { 
     acc.push(value);
     return acc; 
 },[]);

console.log(newArray); // [10, 20, 30, 40, 50]

이것으로 map과 똑같은 로직의 구현이 가능합니다.

그렇다면 filter를 구현해 보겠습니다.

const myArray = [10, 20, 30, 40, 50];
const newArray = myArray.reduce((acc, value, index) => { 
     if(index%2){
       acc.push(value);
     }
     return acc; 
 },[]);

console.log(newArray); // [20, 40]

코드를 통해 확인해보니 reduce는 초기값 설정을 통해 map이나 filter와 같은 로직의 구현이 가능합니다.


3. filter

filter는 말 그대로 배열의 요소를 필터링하여 조건이 참이 되는 요소만 반환합니다.

const myArray = [10, 20, 30, 40, 50];

const newArray = myArray.filter((value) => { 
    return value%20 === 0
 });

console.log(newArray); // [20, 40]

filter는 필터링 기능만 갖고 있어 map이나 reduce의 로직을 만들긴 어려울 것 같습니다.



map과 filter는 용도가 조금 더 특정되어 있다보니 다른 메서드의 로직을 그대로 구현하기는 어렵지만 reduce는 초기값을 통해 다양한 로직의 구현이 가능해 다재다능한 메서드인 것 같습니다.

자바스크립트를 이용한 BFS, DFS 구현하기(javascript)

데이터는 선형 구조(배열, 연결리스트, 스택, 큐) 또는 비선형 구조(트리, 그래프)로 이루어져 있으며, 순차적으로 나열된 선형 구조에 비해 비선형 구조의 데이터는 탐색이 어렵습니다.

하지만 비선형 구조의 대표적인 탐색 방법인 BFS, DFS를 사용하면 깔끔하게 탐색이 가능합니다.

두 방법 모두 무차별 탐색(Brute Force Search, 모든 데이터를 하나씩 탐색) 방법을 사용합니다.


DFS는 깊이 우선 탐색 방법으로 트리 구조의 데이터에서 노드마다 가장 깊이까지 탐색한 뒤 다음 노드로 이동하는 방법입니다.

위와 같은 트리 구조의 데이터가 있을 때, DFS는 한번 선택한 길은 끝까지 가본 뒤 다음 길을 탐색하는 방식과 같습니다.

그림으로 나타내면 다음과 같습니다.

검색 속도는 BFS에 비해서 느리지만 조금 더 간단합니다.

경로의 특징이 필요한 문제를 풀 때 DFS를 사용합니다.

const graph = {
  A: ["B", "C"],
  B: ["A", "D"],
  C: ["A", "E"],
  D: ["B", "F"],
  E: ["C","G"],
  F: ["D","H","I"],
  G: ["E","J","K"],
  H: ["F","L"],
  I: ["F", "M"],
  J: ["G","N"],
  K: ["G","O"],
  L: ["H"],
  M: ["I","P"],
  N: ["J"],
  O: ["K"],
  P: ["M"]
};

const bfs = (graph, start) => {

    const checked = [];    // 탐색 완료 데이터
    const willCheck = [];  // 탐색 예정 데이터
    
    willCheck.push(start);
    
    while(willCheck.length!==0){
      const node = willCheck.pop();  // 스택(Last In First Out)
      if(!checked.includes(node)){
       	 checked.push(node);
         //reverse() 제거 시 그림의 4,3,2,1 순서로 탐색     
      	 willCheck.push(...graph[node].reverse());  
        
      }
   }
	return checked;
}

console.log(bfs(graph, "A"));
// ['A', 'B', 'D', 'F', 'H', 'L', 'I', 'M', 'P', 'C', 'E', 'G', 'J', 'N', 'K', 'O']

BFS는 너비 우선 탐색 방법으로 트리 구조 데이터에서 노드의 인접 데이터를 모두 탐색한 뒤 다음 데이터로 이동하는 방법입니다.

그림으로 나타내면 다음과 같습니다.

탐색 속도는 DFS보다 빠르며 최단 거리를 구하는 문제에서 사용할 수 있습니다.

const graph = {
  A: ["B", "C"],
  B: ["A", "D"],
  C: ["A", "E"],
  D: ["B", "F"],
  E: ["C","G"],
  F: ["D","H","I"],
  G: ["E","J","K"],
  H: ["F","L"],
  I: ["F", "M"],
  J: ["G","N"],
  K: ["G","O"],
  L: ["H"],
  M: ["I","P"],
  N: ["J"],
  O: ["K"],
  P: ["M"]
};

const bfs = (graph, start) => {

    const checked = [];
    const willCheck = [];
    
    willCheck.push(start);
    
    while(willCheck.length!==0){
      const node = willCheck.shift(); // 큐(First In First Out)
      if(!checked.includes(node)){
       	 checked.push(node);
      	 willCheck.push(...graph[node]);       
      }
   }
	return checked;
}

console.log(bfs(graph, "A"));
// ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P']

textContent, innerText, innerHTML 간단 비교(javascript)

요소의 텍스트를 다루는 textContent, innerText, innerHTML의 차이점과 유의 사항

요소에 접근해 데이터를 설정하거나 가져올 때 사용하는 프로퍼티인 textContent, innerText, innerHTML에 대해 알아보겠습니다.

<div id=1 onClick=(check(event))>click
    <div id=2>child1</div>
    <div id=3>child2
      <div id=4>grandchild</div>
    </div>
</div>

onClick 이벤트를 통해 div요소의 텍스트에 접근할 때 각각의 프로퍼티를 사용한 결과는 다음과 같습니다.


1. 실행 결과

const check = (event) => {
    console.log(event.target.textContent); 
}
const check = (event) => {
    console.log(event.target.innerText); 
}
const check = (event) => {
    console.log(event.target.innerHTML); 
}

textContent

text/plain으로 파싱하여 모든 요소를 반환합니다. <script>, <style> 요소를 포함해 CSS를 사용해 숨겨진 요소도 함께 반환하며 요소의 원시 텍스트를 사용하므로 성능이 좋습니다.

innerText

text/plain으로 파싱하여 렌더링 후의 요소를 반환합니다. <script>, <style> 요소는 반환하지 않으며 숨겨진 요소도 반환하지 않습니다. 자식 노드를 모두 제거하고 하나의 텍스트로 반환되며 성능은 보통입니다.

innerHTML

text/html로 파싱하여 요소의 html, xml 전체를 반환합니다. html을 다루므로 보안 이슈 중 하나인 XSS(Cross Site Scripting)에 취약합니다. HTML5에서는 innerHTML에 삽입된 <script> 태그는 실행되지 않도록 변경되었지만 <img>등 다른 태그를 통해 접근하면 여전히 취약점이 남습니다. 따라서 innerHTML은 별도로 문제 방지를 위한 설정이 없다면 사용을 권장하지 않습니다.



구현도 중요하지만 상황에 맞고 적절한 방식을 통해 구현한 시스템에 문제가 생기지 않도록 사전에 방지하는 것이 더 중요한 것 같습니다. 적절한 방식을 선택하는 것은 결국 각 기능의 이해를 통해서야 비로소 가능한 부분이므로 필요할 때 정리를 해두면 도움이 될 것 같습니다.

템플릿 리터럴(template literal)과 쉬운 응용(feat.`)

`로 시작해서 `로 끝나는 템플릿 리터럴

템플릿 리터럴(template literal)은 ES6부터 도입된 기능으로 문자열 다루기에 특화된 기능인데요.

` (backtick, grave accent)

템플릿 리터럴은 ` 기호로 시작해 ` 기호로 끝나며, 문자열과 변수를 함께 전달하거나 태그를 편리하게 사용할 수 있습니다.

또한 다음과 같은 방식도 사용도 가능한데요.

const printData = (text) => {
    console.log(text[0]); // 'Hello everyone!'
}

printData`Hello everyone!`;

함수에 템플릿 리터럴을 전달할 수도 있습니다.

이를 태그드 템플릿(tagged template)이라고 하는데요.

호출된 함수는 템플릿 리터널 내 문자열 조각이나 표현식 등을 매개변수로 받습니다.

styled-components에서 사용되는 방식도 태그드 템플릿입니다.


1. 템플릿 리터럴(template literal)

템플릿 리터럴은 시작과 끝에 반드시 `(backtick)을 붙여야 하며, ‘(홑따옴표), “(쌍따옴표)와 반드시 구분해야 합니다.

const name = 'clint Eastwood';
const age = '92';
const city = 'San Francisco';
const occupation = 'director';

// 표현식
console.log('His name is '+name+ '\n' + 'and '+age+'.');

// 템플릿 리터럴
console.log(`He was born in ${city}
and he is a ${occupation}`);

템플릿 리터럴에서 ${ }를 사용하면 블록 내 변수 또는 함수에 접근이 가능하며, \n 등 이스케이프를 사용하지 않고도 입력한대로 출력할 수 있습니다.

템플릿 리터럴은 쉽게 응용이 가능하니 태그드 템플릿에 대해 알아보겠습니다.

2. 태그드 템플릿(tagged template)

const TextBox = styled.div`
  display: inline-block;
  color: gray;
  margin: 0 auto;
`;

const StyledInput = styled.input`
    font-size: 1rem;
    border: none;
    border-bottom: 1px solid gray;
    outline: none;
    width: 100%;
`;

위와 같이 styled-components에서 태그드 템플릿이 밥 먹듯이 사용됩니다.

템플릿 리터럴로 함수를 호출하면 어떻게 사용할까요?

함수를 호출하면 매개변수로 문자열과 변수가 전달됩니다.

const name = 'clint';
const age = '92';
const city = 'LA';

const showString = (text, ...args) => {
    console.log(text);
    console.log(args);
    console.log(text[2]);
    console.log(args[2]);
}

showString`He is ${name} and ${age}. From ${city}`;

전달된 매개변수는 텍스트와 ${ }에 넣은 변수이며, 각각 확인할 수 있습니다.

텍스트와 변수에 접근하려면 인덱스를 사용하면 됩니다.

텍스트의 경우 ${ }이 들어간 부분에서 끊어주므로 ‘He is’, ‘ and ‘, ‘. From ‘, ”이렇게 네 부분으로 나뉘어지며, 순서대로 0, 1, 2, 3의 인덱스를 갖습니다.

태그드 템플릿을 사용하는 이유는 문자열을 원하는 형식으로 재가공하여 사용할 수 있는 장점이 있기 때문입니다.



styled-components를 무턱대고 사용하는 것보다 구조를 알고 사용하면 더 도움이 될 것 같아 태그드 리터럴에 대해 알아보았습니다. 템플릿 리터럴은 이제 너무 유용하고 편리한 기능이므로 꼭 알아두어야 할 기능 중 하나인 것 같습니다.

target으로 부모 요소, 자식 요소 접근하기(자바스크립트,target)

event와 target으로 부모 요소와 자식 요소에 접근하는 방법

자바스크립트는 보통 이벤트와 target을 통해 요소에 접근합니다.

그리고 <div>와 같은 요소를 중첩해서 사용하다 보면 이벤트 발생 시 부모 요소 또는 자식 요소에 접근이 필요한 경우가 있습니다.

예를 들면 다음과 같이 id=2인 div 요소의 이벤트가 발생했을 때 부모 div 요소 id=1에 접근하고 싶은 경우가 있습니다.

const check = (event) => {
    console.log(event.target.id);
}

<body>
  <div id=1>    
        <div id=2 onClick=(check(event))>click
            <div id=3></div>
            <div id=4></div>
        </div>
  </div>
</body>

그렇다면 어떻게 접근을 해야 할까요?

1. 이벤트를 통해 부모 요소 속성 접근하기

현재 이벤트가 발생한 요소를 감싸고 있는 부모 요소에 접근하기 위해서는 target과 parentElement를 사용하여 접근할 수 있습니다.

부모 요소의 id 속성에 접근하는 코드는 다음과 같습니다.

const check = (event) => {
    console.log(event.target.parentElement.id);    // 1
}

<body>
  <div id=1>    
        <div id=2 onClick=(check(event))>click
            <div id=3></div>
            <div id=4></div>
        </div>
  </div>
</body>

실행해보면 콘솔에 1을 프린트합니다.

만약 id가 아닌 다른 속성에 접근하고 싶은 경우에는 id 대신 해당 속성명을 그대로 사용하면 됩니다.

구조를 확인하고 싶은 경우에는 console.log(event.target.parentElement)을 입력하여 확인할 수 있습니다.

console.log(event.target.parentElement)결과

현재 요소를 기점으로 접근을 진행하므로 target을 사용하며, 이벤트가 기점이 되는 경우 currentTarget를 사용합니다.

관련 포스트 : target, currentTarget 차이가 뭘까?


2. 이벤트를 통해 자식 요소 속성 접근하기

위 코드에서 보면 <div id=2>의 자식 요소는 <div id=3>과 <div id=4>입니다.

자식 요소는 target과 children을 통해 접근하는데요.

부모 요소는 하나밖에 없지만 자식 요소는 여럿 존재할 수 있습니다.

따라서 자식 요소는 인덱스를 통해 접근해야 하며, 리액트 코드를 통해 자식 요소의 구조를 살펴보겠습니다.

const accessChildren = () => {

const check = (e) => {
    console.log(e.target.children)
}

    return (
        <div id={1}>    
            <div id={2} onClick={check}>click
                <div id={3}>I'm a first child</div>
                <div id={4}></div>
            </div>
        </div>
    )
}

export default accessChildren;

콘솔에 찍히는 children의 구조는 다음과 같이 두 개의 object를 갖습니다.

따라서 첫 번째 자식 요소의 id에 접근하기 위해서는 다음과 같이 사용하면 됩니다.

const check = (e) => {
    console.log(typeof(e.target.children[0].id)) // 3
}

그렇다면 첫 번째 자식 요소의 텍스트인 I’m a first child는 어떻게 접근할까요?

간단하게 위 children 구조를 열어서 살펴보면 답이 나옵니다.

textContent를 사용해서 접근할 수 있습니다. (innerHTML은 XSS에 취약)

const check = (e) => {
    console.log(e.target.children[0].textContent) //I'm a first child
}


특정 요소가 속한 속성 전체(예를 들면 e.target)를 콘솔에 출력하면 다양한 하위 속성을 확인할 수 있으므로 원하는 속성을 찾거나 에러를 해결할 때 유용하게 사용할 수 있습니다.

피보나치 수열, 자바스크립트 재귀로 함수 만들기(feat.메모이제이션)

메모이제이션을 사용한 피보나치 수열의 재귀 함수
피보나치(Fibonacci) 수는 이탈리아 수학자인 레오나르도 보나치(Leonardo Bonacci)에 의해 연구되어 알려진 것으로, 토끼가 새끼를 낳아 매달 증가하는 토끼의 수에 관해 계산하는 것으로 유명합니다.

0부터 시작하는 수를 순서대로 나열하면,

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181…….와 같이 수가 증가하는데요.

바로 앞(n-1)의 수와 그 바로 앞(n-2)의 수를 더하면 현재의 수(n)가 됩니다.

다음 그림은 네모칸 채우기로 한 면의 길이가 피보나치 수와 같이 증가를 보이는 것을 알 수 있습니다.

출처:https://en.wikipedia.org/wiki/Fibonacci_number

사각형이 1/4씩 회전하며 그리는 나선형은 황금 비율(1.618)에 수렴하여 과학적이며 예술적인 비율을 만들어 낸다고 합니다.

https://en.wikipedia.org/wiki/Fibonacci_number

그럼 이 피보나치 수열을 재귀를 사용해 자바스크립트 함수로 만들어 보겠습니다.

const fibonacci = (n) => {

  if ( n <= 1 ) {
    return n;
  }
  
  return fibonacci(n-1) + fibonacci(n-2);
}

위 함수는 피보나치 수열을 재귀적으로 수행하지만 계산 양이 늘어날 때마다 시간 복잡도는 가파르게 증가합니다.

이는 계산의 중복 문제 때문인데요.

예를 들어 fibonacci(10)을 실행하면 fibonacci(9), fibonacci(8), fibonacci(7)……. 순서로 실행을 하게 되는데요.

fibonacci(9)는 다시 fibonacci(8), fibonacci(7), fibonacci(6)…. 을 실행하게 됩니다.

결국 이미 한 계산을 하고 또 하고 또 하고… 합니다.

하지만 이미 수행한 계산 결과를 배열로 저장해두고 거기서 값을 꺼내쓰면 계산의 중복은 발생하지 않으므로 효율적이고 아름다운 함수가 됩니다.

이를 메모이제이션(Memoization)이라고 하는데요.

메모이제이션을 사용하는 자바스크립트 코드는 다음과 같습니다.

const fibo = (num, memo) => {

      memo = memo || {}

      if(memo[num]){
          return memo[num]
      }

      if(num<=1){
          return num;
      }

      return fibo(num-1, memo) + fibo(num-2, memo);

}

간단하면서도 효율적으로 피보나치를 계산하는 자바스크립트 함수를 사용할 수 있습니다.