Codestates SEB FE 42기/정리노트

S1 unit10 | 원시 자료형과 참조 자료형

2realzoo 2022. 11. 13. 13:50

📌 원시 자료형 (primitive data type)

하나의 저장공간에 하나의 값을 저장

: Number, String, Undifined, Bigint, Boolean, Null

1980’s의 자료는 csv처럼 배열과 비슷한 형태로 보이도록 콤마로 문자열을 나누어 사용했다.

하지만 그렇게 하면 넣을 수 있는 요소에 한계가 있다.

let word = 'hi';
let myWord = word;
myWord = 'Hello';
console.log(word); // 'hi' 

값을 복사해서 넣었기 때문에 word의 내용은 바뀌지 않았다.

📌 참조 자료형 (reference data type)

: 동적인 저장공간의 주소를 변수에 달아두어 여러 개의 값을 저장

: Array, Object, Function

동적인 저장공간인 heap에 데이터를 따로 보관

let obj = { cat : 10 };
let myObj = obj;
myObj.cat = 20;
console.log(obj.cat); // 20 

obj와 myObj는 같은 주소를 갖기 때문에 같은 저장공간을 가진다.

따라서 myObj에서 바꾼 내용은 obj에서도 바뀌어져 보인다.

🌱 퀴즈

console.log('codestates' === 'codestates'); // true
console.log(4 === 4); // true
console.log([1, 2, 3] === [1, 2, 3]); // false
console.log({ cat: '쪼맹이' } === { cat: '쪼맹이' }); // false

참조 자료형의 ===(strict equality) 는 주소값이 같은지 확인한다.

두 참조형의 주소값은 다르기 때문에 false.

let myArray = [2, 3, 4, 5];
let ourArray = myArray;
ourArray[2] = 25;
ourArray = undefined;
console.log(myArray); // [2, 3, 25, 5]

ourArray에 myArray와 같은 주소를 할당했다가 그 주소의 배열의 2번째 요소를 변경했다.

그리고 원시 자료형인 undefined를 할당했기 때문에 myArray에 접근할 수 없다.

하지만 myArray에는 주소가 그대로 할당되어 있기 때문에 [2, 3, 25, 5] 이다.

 

📌 깊은 복사 얕은 복사

자바스크립트에서의 복사란 원본을 바꾸지 않으면서 동일한 복사본을 반환하는 것이다.

다만, 앞에서 배웠다시피 원시 자료형의 복사는 쉬운데 반해, 참조 자료형은 요소를 복사하는 것과 reference를 복사하는 것 두가지 형태가 있다. 보통 reference를 복사하는 것은 '복사' 라고 말하지는 않는다.

그냥 reference를 새로운 변수에 할당한 것 뿐이다.

그래서 새로운 변수에 할당된 참조 자료형을 편집하면 원래 변수의 참조 자료형도 똑같이 편집된 모습을 볼 수 있다.

한 마디로, 하나의 저장공간에 있는 참조 자료형의 주소를 두개의 변수에서 가지고 있는 셈이다.

 

그러면 참조 자료형에서의 복사는 어떻게 할까?

보통 이러한 메서드와 연산자를 이용해 복사한다.

 

  • array.slice()
  • ...spread 연산자
  • Object.assign()

그러나 이러한 방법으로는 배열 안의 배열처럼 참조 자료형 내부의 참조 자료형까지 복사할 수는 없다.

 

보다시피 내부의 배열은 주소만 붙여넣어진 걸 알 수 있다.

나머지 방법도 마찬가지다. 

 

그렇다면 깊은 복사는 어떻게 해야할까?

사실 자바스크립트에서의 깊은 복사는 거의 없는 것이나 마찬가지다.

json.stringify와 json.parse를 사용한 방법은 앞에서의 이터러블 문제점을 해결하고 깊은 복사를 수행하지만,

다른 문제로는 Date나 BigInt같은 데이터에는 먹히지 않는다는 단점이 있다.

그 이유는 jso으로 표현되는 데이터가 아래 그림으로 한정되어 있기 때문이다.

 

 

우리는 또 다른 방법을 찾을 수 있다.

바로 Lodash 와 Ramda를 이용하는 것이다.

이 둘은 퍼포먼스보다 정확성을 중시하여 직접,,, 하나하나 다 경우의 수를 두고 체크한다.

 

사실 자바스크립트는 내부적으로 깊은 복사를 하도록 모델링 되어있지 않기 때문에 깊은 복사가 어렵다.

그래서 우리는 깊은 복사가 정말로 필요한지 생각해보고 얕은 복사를 하는 것이 좋다.

 

🔗깊은 복사와 얕은 복사에 대한 심도있는 이야기

 

 

📌 호이스팅

catName("호랑이");

function catName(name) {
  console.log("제 고양이의 이름은 " + name + "입니다");
}

/*
결과: "제 고양이의 이름은 호랑이입니다"
*/

위의 MDN 예제를 보면, 함수 호출이 먼저 일어나고 그 뒤에 함수 선언이 일어났다.

자바스크립트는 위에서부터 코드를 읽어내려가며 동작을 수행하기 때문에 오류가 나야한다고 생각할 수 있다.

하지만 예제에서는 함수의 호출이 제대로 일어났다.

그 이유는 호이스팅에 있다.

 

호이스팅이란, 자바스크립트가 함수의 선언, 변수의 선언을 먼저 최상위에 선언하고 처음부터 코드를 실행하는 것이다.

따라서 이 예제의 함수는 이미 맨 위에서 선언되어 있다고 할 수 있다.

 

다른 예제를 보면

console.log(num); // 호이스팅한 var 선언으로 인해 undefined 출력
var num; // 선언
num = 6; // 초기화

var로 선언한 num는 최상단에서 선언되며 바로 undefined 할당이 일어난다.

그래서 console.log(num)은 undefined를 내놓게 된다.

 

console.log(num); // ReferenceError
num = 6; // 초기화

하지만 선언이 없다면 호이스팅도 일어나지 않는다.

이 예제에서 num라는 변수는 호이스팅 되지 않았으므로 ReferenceError가 뜨게 된다.

 

console.log(num); // 호이스팅한 let 선언과 TDZ로 인해 ReferenceError 출력
let num; // 선언
num = 6; // 초기화

또 주의할 점은 let과 const이다.

이 둘은 호이스팅이 일어나지만 그 즉시 undefined가 할당되진 않는다.

선언과 초기화 사이에 TDZ(Temperal Dead Zone, 시간상 사각지대)가 생기기 때문이다.

그래서 이 둘을 TDZ에서 참조하면 ReferenceError를 볼 수 있다.

 

처음에 호이스팅을 접했을 땐

'오... 좋다 함수를 뒤에서 선언해도 앞에서 쓸 수 있다니...!' 라고 생각했지만 사실 호이스팅은 좋은 것이 아니었다.

호이스팅으로 인해서 여러 혼란이 생기기 때문이다.

예를 들면 코드를 읽는 사람은 앞에 뜬금없는 함수가 작동된다면 코드를 이해하기 어렵다.

그러므로 호이스팅에 대해서는 주의가 필요하다.