31기 IN SOPT 서버파트 1차 세미나


대부분 면접 준비하면서 공부했던 개념들이라 방대한 양에 비해 복잡하거나 어렵게 다가오지는 않았다. JS, TS, Node.js 모두 제대로 다뤄봤다고 말하긴 어렵지만 지겹도록 코드를 봤던 경험과, 최근 Node, Nest 벼락치기 하면서 손에 익은 것들이 꽤나 도움이 됐던 것 같다. 그러나 이 모든 걸 글로 제대로 정리를 해본 적은 없기에 (JS 관련 포스팅이 몇 개 있긴 하나 굉장히 조각조각 정보들이 흩어져 있다) 이번 기회에 정리를 해보려 한다

저작권상 문제가 될 수 있으니 최대한 파트장님 자료에서는 목차만 참고하고 나의 지식을 끌어모아 적는 것으로 !

JavaScript

javascript 기초인 듯 심화인 듯 한 내용들이 정리된 카테고리가 있으므로 일단 공유

var, let, const

var, let, const 는 모두 javascript의 변수 선언 방식이다. 왜 세가지나 있냐 라고 한다면 기존에 있던 var 선언방식이 문제가 많아 ES6 버전 이후, let과 const가 등장했다

간단하게 각각의 특징을 정리해보자면 아래 표와 같다

변수 선언 방식 특징
var 1. 재선언(동일한 이름으로 변수 중복 생성) 가능
2. 재할당 (기존에 선언된 변수에 새로운 값을 할당) 가능
3. 함수 스코프
4. 호이스팅 O
let 1. 재선언 불가능
2. 재할당 가능
3. 블록 스코프
4. 호이스팅 X
const 1. 재선언 불가능
2. 재할당 가능
3. 블록 스코프
4. 호이스팅 X

재선언

재선언이 문제가 되는 이유는 변수명이 고유할 수록 코드가 안전해지기 때문이다. 사실 짧은 코드를 작성할 때에는 크게 문제가 되지 않는데 예를 들어

var a = 1;
console.log(a); // 1

var a = 2;
console.log(a); // 2

와 같은 코드를 실행 했을 때 1과 2가 순서대로 출력될 것이라는 것은 예측 가능하다. 그러나, 만약 이러한 상황을 쉽게 예측할 수 없는 복잡하고 긴 코드를 작성한다고 가정했을 때, 자신도 모르게 기존에 사용하던 변수의 이름을 재사용하여 새로운 변수를 선언하는 일은 쉽게 일어날 수 있다. 뭔가 이상한 값이 반환되는데 그 원인을 찾지 못하는 꽤 심각한 상황을 불러올 수도 ..

재할당

재할당은 사실 재선언만큼 큰 오류를 불러오진 않는다. let과 const의 차이는 변수는 let으로, 상수는 const로 선언한다고 이해하면 쉽다. 말 그대로, 스코프 내에서 값이 계속 변화하는 상황에서는 let을, 값이 변하는 상황이 발생하지 않을 예정이거나, 혹은 절대 변하면 안 되는 상황일 때 const를 사용한다.

scope

스코프 : JS에서, 변수에 접근 가능한 범위

var은 함수 스코프, let과 const는 블록 스코프를 지니는데, 함수 스코프는 함수 내에서 접근 가능한 것, 블록 스코프는 중괄호에 묶인 범위 내에서만 접근 가능한 것을 뜻한다. 이를 코드로 살펴보면

function temp() {
    if(){
        //변수 선언
    }
    // 변수 호출
}

위와 같은 상황에서 변수가 var로 선언되어 있다면, 변수의 선언과 호출이 속한 블록(중괄호)는 다르지만 temp라는 같은 함수 내에 있기 때문에 정상적으로 호출이 가능하다.

그러나 해당 변수가 블록 스코프를 지닌 let 또는 const로 선언되었을 경우, 변수가 선언되어 있는 if 문의 블록(중괄호) 밖에서 변수에 접근을 시도하고 있기에, 에러가 발생한다.

호이스팅

호이스팅은 언급만 하고 넘어가셨는데 아주 간단하게만 설명을 해보자면 (깊게 파고들면 이것도 복잡하다) 변수 또는 (함수)에 대한 선언이 상단으로 이동하여 변수, 또는 함수에 대한 메모리 공간이 미리 할당되는 것인데, 이렇게만 들으면 무슨 말인가 싶을 수도 있으니 예시를 들어보자

console.log(a); // undefined
var a = 1

위 코드를 살펴보면 뭔가 이상하게 생겼다. a라는 변수를 만들지도 않고 출력을 하려 시도를 하는데, let과 const로 선언된 변수는 이러한 상황에서 Reference errror가 발생하나, var로 생성된 변수는 에러가 발생하지 않으며 undefined가 출력된다. 그럴 수 있도록 해주는 것이 호이스팅인 것 ..

말 그대로 var a = 1 이라는 변수의 선언이 console.log(a) 라는 호출 코드의 상단으로 이동한다. 그러나, 이 때 선언과 값의 초기화를 별개로 보아야 한다.

// var a = 1;

var a; // 선언
a = 1; // 초기화

해당 코드를 선언과 초기화로 분리하면 이렇게 되는데, 이 중 선언만 끌어올려지는 것. var은 변수의 선언과 초기화가 동시에 진행되기에, 호이스팅이 발생할 시 undefined 로 초기화가 진행된다(값은 없는 상태인데 메모리 공간이 존재함). 그렇기에 에러가 발생하지 않고 undefined 가 출력되는 것이다.

그러나 let 과 const는 선언과 초기화가 분리되어 진행되기에, 선언이 호이스팅 되면 초기화 되지 않은 변수가 전달되고, 이는 reference error을 발생시킨다.

function

코드를 먼저 살펴보면 아래와 같다

// 함수 선언식
function hello(name){
    console.log(`안녕 ${name}`);
}

//함수 표현식  (화살표 함수)
const sum = (a, b) => {
    result = a + b;
    console.log(result);
}

const sum_ = (a,b) => a+b; // 위랑 같은 함수

JS에서 함수 표현식은 선언식과 표현식으로 나뉘는데 이 때 위에 호이스팅에서 또는 (함수) 라고 적어둔 부분에 대한 설명을 할 수 있다. 함수 선언식으로 선언한 함수에서는 호이스팅이 발생하며, 함수 표현식으로 작성한 함수에서는 호이스팅이 발생하지 않는다. 따라서 선언식의 경우, 아래와 같은 코드가 에러 없이 돌아가게 된다 (위험)

// 함수 선언식
hello();
function hello(){
    console.log("안녕");
}

TypeScript

JS에 타입 문법을 추가한 상위 집합으로, 코드 작성 시 타입을 체크하는 언어 (컴파일 언어, 정적 타입)

사실 JS와 타입 제외하고 크게 다른 부분은 없어서 (라기엔 꽤 많지만 귀찮으니) 모르고 있었던 하나만 정리하고 과제 코드 설명으로 대체하려 한다

Object vs object

도전 과제

member, dinner interface

interface는 여러 가지 타입을 가지고 있는 property 값들로 이루어진 새로운 타입을 정의하는 것이다. 다만 이 내에서 완전한 메소드를 정의할 수는 없고, 추상화하여 정의한다.

아래는 member와 dinner에 대한 interface 정의 코드이다.

// member interface
interface Member{
    name: string;
    group: string;
}

// dinner interface
interface Dinner{
    member: Member[];
    menu: string[];
    shuffle: (data: any[]) => any[];
    organize: (data: Member[]) => void;
}

- Member

Member 내에는 이름을 나타내는 name과 yb/ob 를 나타내는 group 프로퍼티가 존재하며, 둘 다 string 타입을 지닌다.

- Dinner

Dinner의 경우 랜덤으로 짝을 짓고, 메뉴를 결정하는 역할을 하는 객체인데, 이에 대한 interface에는 Member 형식을 담은 array 인 member와 메뉴를 담은 배열인 menu, array의 순서를 랜덤으로 섞어주는 shuffle 함수, 이를 활용하여 결과를 도출하는 organize 함수가 존재한다.

member 과 menu는 각각 Member, string 타입을 요소로 지니므로 type[] 를 통해 어떤 타입을 지닌 요소들을 나열할 것인 지 정의하였다.

shuffle과 organize는 함수이기에, 어떤 타입을 매개변수로 받고, 어떤 타입의 결과를 리턴하는 지 정의한다. shuffle의 경우 Member와 string 타입을 모두 받아야하므로 특정 타입을 지정하지 않고 any[] 로 지정하였으며, organize의 경우 dinner 객체 안의 member(Member[] 타입)을 매개변수로 받으며 리턴값은 존재하지 않기에 void로 설정하였다.

dinner 객체

// dinner 객체
const dinner: Dinner = {
    member : [ 
      { name: "이름", group: "ob" },{ name: '이름', group: 'ob' },
      { name: '이름', group: 'ob' },{ name: '이름', group: 'ob' },
      { name: '이름', group: 'yb' },{ name: '이름', group: 'yb' },
      { name: '이름', group: 'yb' },{ name: '이름', group: 'yb' }
    ],
    menu : [
      '햄버거', '스테이크', '피자', '치킨', '떡볶이', '김치볶음밥', '파스타', '', '우동', '김밥'
    ],
    shuffle(array) {
      array.sort(() => Math.random() - 0.5);
      return array;
    },
    organize(array) {
      this.shuffle(array);
      this.shuffle(this.menu);
      const dinnerMember = array.map((mem)=>[mem.name+`(${mem.group})`]);
      console.log(`${dinnerMember[0]}님은 ${dinnerMember[1]}님과 함께 ${this.menu[0]}을(를) 드시면 됩니다 !`);
    },
  };
  
dinner.organize(dinner.member);

- member

Member 객체들이 모여있는 배열로, SOPT 서버파트 일부 인원들의 정보가 들어있다

- menu

다양한 메뉴를 담은 배열 ! 처음에 문제 이해를 못해서 파트장님께 추가로 선언해줘야하는 것인 지 여쭤봤다 ,, 머쓱 ㅎㅎ

- shuffle

배열을 매개변수로 받아 랜덤으로 섞은 후 반환하는 함수로, 파트장님이 코드를 제공해주셨다

- organize

랜덤으로 짝을 배정하고, 저녁 메뉴를 정해 출력해주는 함수이다. 제공해주신 코드에 최종 호출이 dinner.organize(dinner.member) 이므로, 메뉴의 경우 suffle 함수에 this.menu 로 전달해주었다.

this는 호출한 객체를 가리키는데, 화살표 함수의 경우, 자신이 아닌, 자신을 포함하는 외부 스코프에서 this를 계승받는다. 따라서 organize 안에서 this.menu, this.suffle 등을 작성하면, 이는 organize 안의 menu와 shuffle이 아닌, organize를 포함하는 ‘dinner 객체의 스코프’에서 menu와 shuffle을 찾는 것. 외부에서 dinner.menu, dinner.shuffle을 호출하는 것과 같다.

dinnerMember [index] 형태로 호출하여 출력하는 기존의 코드를 보고, dinnerMember을 array로 선언해야한다는 것을 알았다. 처음에 단 두 명만 배열에 넣어야 한다는 깜찍한 생각을 해서 이게 뭐지 싶었으나 곧 깨달았다 .. suffle된 array를 [name(group)] 형태로 전환하여 dinnerMember에 할당하는데, 이 때 모든 원소에 대해 작업을 진행할 것이므로 map을 사용한다. (꼭 저 형식으로 넣을 필요는 없으나, 그룹이 괄호로 출력되는 게 깔끔하길래 그렇게 했다)

메뉴도 함께 출력해야하니 기존에 멤버만 출력하던 출력문에 메뉴를 출력하는 부분을 추가해주면 짜잔 완성 !

feedback

위 도전과제 코드에 대한 피드백을 받아 이를 반영 및 공부해보고자 한다

  1. 타입을 유추할 수 있다면 any 타입은 지양할 것 -> 유니온 타입 또는 alias로 대체
  2. 인터페이스 폴더 분리하기

Union 타입이란 ?

유니온 타입은 or 연산자 (파이프라인) 을 활용하여 A 또는 B이다 를 나타내는 타입이다.

function temp(data : string | number){
  //
}

위와 같이 코드를 작성하면 temp 함수는 string 과 number 타입의 인수를 받을 수 있는 함수가 되는 것 ! 타입을 any를 지정하는 경우 Js로 작성하는 것과 다를 바 없이 작동하는데, 유니온 타입을 사용하면 ts의 장점을 살릴 수 있다.

이를 활용하면 interface 내의 shuffle 함수를 다음과 같이 추상화할 수 있다.

//shuffle: (data: any[]) => any[];
shuffle: (data: Member[] | string[]) => Member[] | string[];

주의할 점

그러나, union 타입의 경우 오류 발생을 최소화하기 위해, 두 타입에 공통적으로 들어있는 속성에만 접근할 수 있도록 한다.

interface A {
  a: string;
  b: number;
}
interface B {
  a: string;
  c: string;
}
function introduce(data: A | B) {
  data.a; 
  data.b; //  오류
  data.c; // 오류
}

위와 같이 코드를 작성했을 때, 아래와 같이 b와 c 속성이 존재하지 않는다는 오류가 발생하는 것을 알 수 있다. 이번 과제의 경우 둘 다 array 타입이기에 오류 없이 사용할 수 있으나, 후에 사용할 때 주의를 기울여야할 것 같다.

Alias 타입이란 ?

Alias 는 interface와 새로운 타입을 정의한다는 점에서 비슷하나, 원시값, 유니온, 튜플 등도 타입과 같이 더 다양한 타입을 지정할 수 있다.

더 자세한 비교 문서를 보고 싶다면

아래 코드에서 type member_string ~ 에 해당하는 부분이 type alias 이다 ! 코드가 제법 타입스크립트다워졌다(?)

type member_string = Member[] | string[]; // 변수명 이상한..데 이건 내 뇌의 한계

interface Dinner{
    member: Member[];
    menu: string[];
    shuffle: (data: member_string) => member_string;
    organize: (data: Member[]) => void;
}