근래에 면접을 준비하고 참여하며 느끼는 것은… 면접이 굉장히 괴로운 일이긴 해도 내가 무엇을 모르는지 확인할 수 있는 좋은 기회라는 것이다.
대체로 내가 약한 부분은 api 통신에 관련된 부분이나 자바스크립트 동작 원리 그리고 CS지식이다. CORS(교차 출처 리소스 공유)관련 질문은 면접을 볼 때마다 나왔는데, 하나의 api를 가지고 작업한 경험이 전부라서 제대로 대답을 할 수 없었다.
경험해보지 못한 것은 어쩔 수 없다고 해도, 기초적인 질문에 대답을 못한 것도 꽤 있다.
첫번째로 봤던 면접에서 다음과 같은 질문을 받았다.
원시형과 참조형의 차이는 무엇일까요? const로 선언한 변수에 할당한 값은 변경할 수 없다고 했는데, 배열인 경우 배열 안의 값을 변경할 수 있습니다. 이건 왜 그럴까요?
const a = 1;
const arr = [1,2,3,4,5];
arr.pop();
a = 2; // 타입에러
console.log(arr); // [1, 2, 3, 4]그러게, 왜 이러지?
첫번째 질문에 대해서 -원시형은 String, Number, Boolean 등과 같이 값이 하나인 형태를 의미하고, 참조형은 Object나 Array 같이 데이터의 묶음인 형태를 의미한다- 라고 대답할 수 있겠지만, 이 설명으로는 두번째 질문에 답을 할 수 없다. 왜 원시형이고, 참조형인지를 이해해야한다.
변수
그 둘을 이해하기에 앞서 변수가 무엇인지 정리가 필요할 것 같다.
https://developer.mozilla.org/ko/docs/Learn/JavaScript/First_steps/Variables
변수는 값 자체가 아니라** 어떤 값이든 저장할 수 있는 컨테이너 = 메모리 공간임을 알아야 한다. 변수는 변수의 이름과 메모리 공간을 연결해서 해당 값을 사용할 수 있도록 해준다.
primitive
원시(primitive) 값은 객체가 아니고, 메서드를 가지지 않는 불변하는 값 그 자체이다.
함수나 변수에 의해서 변경되는 것 같이 보이지만, 실제로 함수나 변수에 할당되는 원시 값과 원시 값 그 자체를 혼동해서는 안된다.
const a = 1;
a.toString(); // "1"
console.log(a); // 1
const testFunc = (num) => {
num * 10;
console.log(num * 10);
};
testFunc(a); // 10, 그러나 a는 그대로 1이다.그렇다면 할당되는 원시 값은 왜 변경되는 것일까?
이는 자바스크립트가 함수 호출을 위해 식별자를 찾는 과정에서 원시 값을 복사하여 사용하기 때문이다. 이러한 복사본은 해당 함수나 블록 스코에서만 사용되고, 결론적으로 원시 값에 영향을 주지 않는다.
따라서 원시형 값을 변수에 할당하는 것은 변수에 변하지 않는 실제 값 자체가 담는 것을 의미한다.
…하지만 const는 그렇다 쳐도, let이나 var로 선언된 값은 아래와 같이 변경이 되지 않는가? 이것을 불변하다고 할 수 있나? 라는 의문이 자연스럽게 생긴다.
let b = 1;
b = 2;
console.log(b); // 1이 아니라 2인데? 변경되는게 아닌가???하지만 이러한 현상(?)에 대해 알아보면, 새로운 메모리 공간에 재할당 된 값(위의 예제로는 2)을 저장한 뒤, 그 값 사용하는 것이므로 결국 최초의 원시 값(위의 예제로는 1)이 변경된 것은 아니다-는 것을 알게 된다.
(메모리와 재할당 관련된 부분은 나중에 관련 자료를 읽으며 더 알아봐야겠다.)
아래의 코드를 살펴보면 1이라는 원시 값은 없어지거나 변경된 것이 아니라는 것을 잘 알 수 있다.
let b = 1;
let c = b;
b = 2;
console.log(b); // b는 2가 담긴 새로운 메모리 공간을 가리키고 있다.
console.log(c); // 하지만 c는 여전히 1이 담겨있는 b의 메모리 공간을 가리킨다.Reference
그런데 참조형 값을 변수에 할당할 경우, 값 자체가 아니라 참조할 데이터 주소의 값이 변수에 들어가기 때문에 값 자체는 가변적이게 된다.
따라서 const를 사용해도 주소 안에 담겨있는 값은 변경이 가능하다. (대신 주소의 값은 변경이 불가능 하다.)
아래에 원시형일 때와 비슷한 상황을 만들어서 값을 출력해 보았다.
const b = [1,2,3,5];
const c = b;
// b = [1,2,3,5,7]; // 이건 주소를 변경하는 것이기 때문에 불가능하다.
b.pop(); // [1, 2, 3] 이건 값을 변경하는 것이라 가능함.
console.log(b); // [1, 2, 3]
console.log(c); // [1, 2, 3]
// 주소 안에 있는 값이 변경 되었기 때문에 c도 함께 변경된다.b라는 변수에 [1,2,3,5] 라는 배열의 값이 들어가는 주소를 할당한다. 변수 c는 [1,2,3,5] 라는 배열의 값이 들어있는 주소를 공유하고 있다.
이러한 상황에서 pop()을 사용해 배열의 마지막 값을 삭제했을 때, 변화한 값 [1, 2, 3]이 담겨있는 주소를 공유하는 변수 b, c는 같은 값을 출력하게 된다.
변수를 let으로 선언했다면 다음과 같이 출력 된다. 원시형 자료를 할당했을 때와 유사하게 변수 b와 c가 다른 주소를 바라보고 있다.
let b = [1,2,3,5];
let c = b;
b = ["A","B","C"]; // let으로 선언했기 때문에 주소 변경이 가능
b.pop();
console.log(b); // ["A", "B"]
console.log(c); // [1, 2, 3, 5]정리!!
글의 처음으로 돌아가서 면접 질문에 대한 답변을 해본다면
const로 선언한 변수에 배열이나 객체와 같은 참조형 자료를 할당할 경우, 변수에는 값 자체가 아닌 값이 들어있는 주소가 담기기 때문에 값의 변경이 가능하다-고 말할 수 있겠다.