본문 바로가기

개발인생다반사/TIL(Today i learned)

[Achievement Goals] - JS/Node 핵심개념과 주요문법

원시 자료형과 참조 자료형  Achievement Goals

1. 원시 자료형(primitive type)과 참조 자료형(reference type)의 구분이 왜 필요한지 이해할 수 있다.

원시 자료형은 선언된 변수명에 값이 1개만 존재한다. 그런데 자료의 개수가 여러 개인 경우가 있을 수 있다. 객체나 배열이 바로 그러한 종류에 속한다. 이렇게 여러 개의 자료를 단순하게 원시 자료형 처럼 1개의 변수에 1개의 자료를 매칭해서 저장한다고 가정하면 엄청나게 많은 스택이 필요하게 된다. 이를 해결하고자 하는 것이 바로 참조 자료형이다.

 

참조 자료형은 스택 형태의 메모리에 변수명이 선언된다. 다만 원시 자료형처럼 스택이 가지고 있는 값이 데이터 그 자체의 값이 아니라 데이터를 보관하고 있는 주소를 값으로 가지고 있다. 그리고 그 주소가 가리키는 곳에는 객체나 배열과 같은 다수의 데이터를 보관하고 있는 힙이라는 메모리이다. 

 

원시 자료형과 참조 자료형을 구분하는 이유는 바로 저장해서 관리해야 하는 자료의 단위가 한개이거나 여러개일 때가 존재하기 때문이고 한개일 때와 여러개일 때에 따라 변수를 선언하고 데이터를 저장하고 이용하는 방법이 컴퓨터 메모리 내부적으로 다르기 때문이다.

 

2. 원시 자료형과 참조 자료형의 차이를 이해하고, 각자 맞는 상황에서 사용할 수 있다.

원시 자료형은 변수명에 1개의 값만 저장되기 때문에 각 데이터간 원시 자료형 데이터를 복사할 경우 기존의 데이터에는 영향이 가지 않게 된다. 그러나 참조 자료형은 데이터의 주소를 가지고 있기 때문에 복사한 데이터에서 원소를 변경하게 되면 기존의 데이터에도 동일하게 수정되는 영향을 미치게 된다.

 

원시 자료형 데이터는 String, Number, Boolean, null, Undefined, BigInt, Symbol 총 7개를 말한다. 그 중에서 bigInt, symbol을 제외하고 나머지 5개가 주로 많이 쓰인다.  원시 자료형 데이터에서 변수명에는 한개의 데이터만 저장된다. 원시자료형은 변수의 값 자체를 변경하는 것은 불가능하나 변수에 다른 데이터는 할당할 수 있다.

 

참고로 null 값은 원시자료형으로 취급되지만 엄밀하게 따지면 참조하고 있는 주소가 없다는 의미이기 때문에 객체이다. null은 객체[array, object] 를 담을 변수를 초기화할 때 사용한다.  동등연산자(==)로 null과 undefined을 비교할 때 true를 리턴한다. 일치연산자(===)로 비교시에는 자료형이 다르기 때문에 false를 리턴한다.

 

참조 자료형 데이터는 객체, 배열, 함수가 있다. 앞서 살펴본 원시 자료형 데이터는 데이터의 용량이 타입에 따라 정해져 있다. 그런데 상황에 따라 데이터를 동적으로 수정하면서 데이터의 용량이 줄거나 늘어날 수도 있게 되었고 그러한 이유 때문에 참조 자료형 데이터를 보관하는 특별한 저장 공간을 만들어 사용하기로 하였다. 

 

3. 원시 자료형이 할당될 때에는 변수에 값(value) 자체가 담기고, 참조 자료형이 할당될 때는 보관함의 주소(reference)가 담긴다는 개념을 코드로 설명할 수 있다.

let num = 8;

변수 num에 Number 타입의 값이 8을 할당하였다. 컴퓨터 메모리 스택에 num이라는 이름 태그가 붙고 그 공간 안에 8이 저장되었다는 의미이다. 

let arr = [1, 2, 3];

arr는 참조 자료형 타입 배열이다. 컴퓨터 메모리 스택에 arr 라는 이름표가 붙고 그 공간 안에는 자료가 보관되어 있는 힙의 주소가 저장된다. 힙에는 배열 자료가 저장되어 있다. 

 

배열은 push, pop 등으로 배열의 요소가 추가되거나 삭제될 수 있다. 그에 따라 힙의 저장공간 용량도 늘어났다가 줄어들었다가 한다. 즉 참조 자료형 데이터는 데이터의 크기가 변할 수 있다는 의미이다.

 

 스코프 Achievement Goals

1. 스코프의 의미와 적용 범위를 이해할 수 있다.

스코프는 변수 접근 규칙에 따른 유효 범위를 의미한다.  바깥쪽 스코프에 선언된 변수는 안쪽 스코프에서 접근할 수 있지만 안쪽 스코프에서 선언된 변수는 바깥쪽 스코프에서 접근할 수 없다. 이는 스코프가 중첩되어서 사용될 수 있다는 것을 의미한다. 가장 바깥쪽 스코프는 전역 스코프라고 지칭하며 반대는 지역 스코프라고 한다. 그리고 전역변수 보다 지역변수가 더 높은 우선 순위를 가진다.

let name = '김코딩';

function showName() {
  let name = '박해커'; // 지역 변수
  console.log(name); // 두번째 출력
}

console.log(name); // 첫번째 출력
showName();
console.log(name); // 세번째 출력

위의 코드에서 showName() 함수 호출할 경우 let name = '박해커'에 의해 '박해커'가 출력된다.  전역변수 let name = '김코딩' 이 있지만 지역변수가 전역변수보다 우선 순위가 높으므로 지역변수 name인 '박해커'가 출력된다. 이처럼 동일 변수이름에서 지역변수에 의해 전역변수가 가려지는 현상을 쉐도잉(variable shadowing)이라고 한다.

 

2. 스코프의 주요 규칙을 이해할 수 있다

스코프에는 2가지 종류가 있다. 하나는 block scope라고 하는데 중괄호 {  } 기준으로 범위가 구분된다.  또 다른 종류는 function scope로 function 키워드가 등장하는 함수 선언식 및 함수 표현식이 유효 범위이다. 다만 화살표 함수는 block scope로 취급된다. 

 

키워드 var는 블록 스코프(화살표 함수 블록 스코프는 무시하지 않는다) 를 무시하고 함수 스코프만 적용받는다. 보통 코드를 작성할 때 블록은 들여쓰기가 적용되고, 그 구분이 시각적으로 분명하다.  따라서 많은 사람들은 블록 스코프를 기준으로 코드를 작성하고, 생각하기 마련이다. 그러나 var는 이 규칙을 무시하므로, 코드를 작성하는 사람이 블록 스코프/함수 스코프에 대한 이해가 없으면 코드가 다소 혼란스러울 수 있다. 따라서, var 보다는 let 으로 변수 선언을 하는 것을 권장한다.

 

var 보다 let이 안전한 let은 변수의 재선언이 불가하기 때문이다. 만일 동일한 변수를 키워드 let과 함께 재선언한다면 식별자가 이미 선언되었다는 에러 메세지를 보게 될 것이다. let과 달리 변수에 재할당을 할 필요가 없다면 키워드 const를 사용한다. 

 

브라우저에는 전역객체 window가 있다. 크롬 개발자 도구 콘솔에서 window라고 입력하게 되면 윈도우창을 의미하는 객체가 조회가 된다. 또한 window 객체는 전역영역을 담고 있다. 그래서 var로 선언된 전역 변수 및 전역 함수는 window 객체에 속하게 된다.

var name = '김코딩';
console.log(window.name); // 김코딩

전역 변수를 너무 많이 선언하는 것은 좋지 않다. 전역 변수는 어디서든 접근이 가능하기 때문에 너무 많이 선언하고 사용하게 되면 부수적인 문제(side effect)가 발생할 위험이 높아진다. 또한 var로 전역변수를 선언하게 되면 window 기능을 덮어씌어서 내장기능을 사용할 수 없게 만든다. 

 

선언키워드 var, let, const를 사용하지 않고 변수를 선언하면 해당 변수는 var 키워드로 선언한 것으로 취급된다. 그래서 애초에 선언없는 변수 할당 오류를 방지하기 위해 strict mode를 사용할 수 있다.

'use strict'

function showAge() {
    age = 90;
    console.log(age);
}

// 에러발생

 

클로저 Achievement Goals

1. 클로저 함수의 정의와 특징에 대해서 이해할 수 있다.

클로저는 내부함수가 외부함수의 맥락에 접근할 수 있는 것을 말한다. 클로저 함수는 자신을 포함하고 있는 외부 함수가 종료된 이후에도 외부함수의 인자, 지역변수를 사용할 수 있는 함수를 말한다. 

const adder = x => y => x + y;
adder(5)(7); // 12


adder(5); // y => 5 + y;

위의 코드에서 할 수 있듯이 adder(5)는 함수 y => 5 + y 를 리턴하고 있다. 클로저 함수는 함수를 리턴하는 함수이다. 위의 코드는 아래와 같이 쓸 수 있다.

function adder(x) {
  return function(y) {
    return x + y;
    }
}

리턴하는 함수에 의해 스코프가 구분된다. 스코프의 특징 안쪽 스코프의 변수는 바깥쪽 스코프의 변수에 접근할 수 있다는 것에 의해 내부 함수의 변수 x는 외부함수 x에 접근해서 가져올 수 있다. 즉 클로저 함수의 두번째 특징은 외부함수이 변수에 접근할 수 있는 내부함수이다.

 

2. 클로저를 이용해 유용하게 쓰이는 몇 가지 코딩 패턴을 이해할 수 있다.

일반적으로 함수는 실행이 끝나고 나면 함수 내부의 변수를 사용할 수 없다. 클로저 함수는 외부 함수 실행이 끝나더라도 외부 함수내의 변수가 메모리 상에 저장된다.  이러한 클로저의 특징으로 몇가지 코딩 패턴을 실무에서 사용할 수 있다.

const tagMaker = tag => content => '<${tag}>${content}<${tag}>

const divMaker = tagMaker('div');
divMaker('hello') // <div>hello<div>

tagMaker('div') = content => <div>content<div> 인것처럼 함수가 리턴된다. div 태그를 스코프 안에 두고 content에 값을 넣어서 계속해서 사용하는 구조이다. 클로저 함수는 이처럼 특정 데이터를 스코프 안에 가두어 둔채로 계속 사용할 수 있다.

 

클로저 Achievement Goals

1. Spread/Rest 문법, 구조 분해 할당을 사용할 수 있다.

Spread 문법은 주로 배열을 풀어서 인자로 전달하거나 배열을 풀어서 각각의 요소로 넣을 때 사용된다.

function sum(x, y, z) {
  return x + y + z;
}

const numbers = [1, 2, 3];

sum(...numbers) // 6

 

Rest문법은 매개변수(파라미터)를 배열의 형태로 받아서 사용할 수 있다. 파라미터의 개수가 가변적일 때 유용하다.

function sum(...theArgs) {
  return theArgs.reduce((previous, current) => {
    return previous + current;
  });
}

sum(1,2,3) // 질문: 어떤 값을 리턴하나요?
sum(1,2,3,4) // 질문: 어떤 값을 리턴하나요?

구조 분해 할당은 Spread문법을 이용해 값을 해체한 후, 개별 값을 변수에 새로 할당하는 것을 말한다. (spread syntax는 iterable 한 모든 것의 (대표적으로 문자열, 배열) 요소를 "펼쳐"주는 문법을 의미한다.)