Programming/Java Script

var , let , const 키워드 다 부셔버리기 ( + 깊은 복사, 얕은 복사 with javascript data type)

감귤밭호지차 2023. 11. 1. 22:46

var, let , const 에 대해서 아주 사소한 것까지 다 부셔버리기 위해 정리글을 작성합니다. 왜냐면 면접에서 아주 탈탈 털렸기 때문이죠. 핫.핫.핫...  덩달아 가장 기초적인 JS의 털린 부분도 다 깔끔띠하게 정리해봅니다. 

 

[1]  var, let, const  키워드 정리 

자바스크립트는 함수 레벨 스코프를 준수한다.  (대부분의 프로그래밍 언어는 블록 레벨 스코프를 준수). 함수 레벨 스코프는 함수 내에서 선언된 변수는 함수 내에서만 유효하며 함수 외부에서는 참조할 수 없다.

# 함수 내부 선언 변수 - 지역 변수 , 함수 외부 선언 변수 - 모두 전역 변수

 

 

       var                                                    

전역 범위 , 함수 범위에서 선언될 수 있는 키워드로 "재선언" 되거나 " 업데이트" 될 수 있습니다. 재선언 될 수 있기 때문에 예상치 못한 출려을 가져올 수 있다는 점이 단점입니다. 최상위로 호이스팅되며 undefined 로 초기화 됩니다. 

 

여기서 제가 받은 질문 =>  var 키워드는 전역 범위에서 선언되서 최상위로 호이스팅 된다고 하셨는데 그 전역 범위는 항상 window 객체인가요? 전역에서 접근하려면 무조건 var 키워드를 사용해야 하나요? 

 

holly...holly...holly.. No.....

 

> WAIT, 전역 객체란..? 

모든 객체의 유일한 최상위 객체를 의미하며 일반적으로 Browser-side 에서는 window 객체, Server-side(node.js)에서는 global 객체를 의미합니다. var 키워드로 선언된 변수를 전역 변수로 사용하면 전역 객체의 프로퍼티가 됩니다. 

 

var global = "전역변수";

console.log(window.global); // "전역변수" 출력.

 

 

let 전역 변수는 전역 객체의 프로퍼티가 아닙니다. let 전역 변수는 보이지 않는 개념적인 블록 내에 존재하기 때문입니다. 

let global = "전역변수";

console.log(window.global); // undefined;

 

 

 

var 키워드의 경우 변수가 선언된 위치에 따라 유효범위가 달라집니다. 선언된 위치의 스코프 내에서 사용될 수 있습니다. 

블록 레벨 스코프를 따르지 않는 var 키워드.

 

PS. var 로 선언한 전역 변수 이름과 함수 내에서 선언된 지역 변수의 이름이 같아 충돌하는 범위에서는 지역변수가 더 짱짱맨으로 이겨서 전역변수 대신 지역변수가 사용됩니다. 

 

 

초기값 없이 변수 선언 가능. - 선언 단계와 초기화 단계가 한꺼번에 이루어지기 때문이다. 

(스코프에 변수를 등록(선언단계) 하고 메모리에 변수를 위한 공간을 확보한 후, undefined 로 초기화(초기화 단계)한다. 따라서 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않고 undefined 를 반환 - 이후 변수 할당문에 도달하면 비로소 값이 할당 )

유효 범위 : globally/locally/blocked

 

//재선언 : 이미 선언한 변수명을 다시 선언하는 것
var name = "emma";
var name = "Lora";

//재할당 [업데이트] : 이미 값이 할당된 변수에 새로운 값을 할당하는 것 
var name = "emma";
name = "Lora";

 

 

 

       let                                                    

ES6은 블록 레벨 스코프를 따르는 변수를 선언하기 위해 let 키워드를 제공합니다. 

블록 범위에서 선언이 되는 키워드로 중괄호 안에서 선언된 let 은 중괄호 밖에서 호출 될 시 undefined 를 반환합니다. 

"재선언"은 불가하며 "업데이트"는 가능합니다. 다만 동일한 변수가 다른 범위에서 다르게 선언할 수 있기 때문에 var의 예상치 못한 재선언으로 인한 단점을 보완할 수 있습니다. 최상위로 호이스팅 되지만 초기화는 되지 않습니다. 

 

초기값 없이 변수 선언 가능. - 선언 단계와 초기화 단계가 분리되어 진행.

(스코프에 변수를 등록(선언단계) 하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어진다. 초기화 이전에 변수에 접근하려고 하면 참조 에러가 발생하는데 이는 변수가 아직 초기화 되지 않았기 때문이다. 즉, 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문이다. 따라서 스코프의 시작 점부터 초기화 시작 지점까지는 변수를 참조할 수 없기 이 구간을 일시적 사각지대:TDZ 라고 한다. )

유효 범위 : globally/blocked

 

 

 

       const                                                    

let 과 동일하게 선언된 블록 범위 내에서만 접근이 가능하고 주로 일정한 " 상수 값을 유지하는 변수 " 를 선언하는데 사용되는 키워드입니다. const키워드는"재선언"과  "업데이트" 모두 불가능하며 다만 객체를 선언하는데 사용했다면 객체 자체를 업데이트 하는 것은 불가능 하지만 객체의 속성은 업데이트 할 수 있다는 특징이 있습니다. 배열 또한 배열 내부의 값을 업데이트 하는 것은 가능합니다. 최상위로 호이스팅 되지만 초기화 되지는 않습니다. 

 

const obj = {name: "emma", age: 28};

obj = {pet: 'cat'}; // error
obj.name = "Lora"; //console.log(obj) -> {name: "Lora", age:28}

 

const 키워드가 재할당(업데이트)가 불가하다는 의미는 const 변수의 타입이 객체인 경우, 객체에 대한 참조를 변경하지 못한다는 것을 의미합니다. 하지만 이때  객체의 프로퍼티는 보호되지 않기때문에 재할당은 불가능하지만 할당된 객체의 내용은 변경할 수 있다는 의미입니다. 

# 객체의 내용이 변경되더라도 객체 타입 변수에 할당된 주소값은 변경되지 않기 때문에 객체 타입 변수 선언에는 const 를 권장.

 

 

초기값 없이 변수 선언 불가. - 선언할 때 초기값을 함께 설정해야 합니다. 

유효 범위 : globally/blocked

 

 

정리

- 변수 선언에는 기본적으로 const 사용 / let은 재할당이 필요한 경우에 권장(대신, 변수의 스코프는 최대한 좁게)

- 변경이 발생하지 않는 원시값(재할당이 필요없는 상수) , 객체에는 const 키워드 권장.

- const 사용은 의도하지 않은 재할당을 방지해 주기 때문에 안전.

 

 

* 참고 블로그 : javascript - const/let/var 차이점
* 참고 블로그 : 
* 참고 사이트  : let, const | Poiemweb

 

 

 


 

 

[2] 깊은 복사,  얕은 복사  +  원시 타입과 참조 타입 

 

깊은 복사 얕은 복사를 설명하기 앞서 우선 JS의 원시 타입객체 타입에 대해 정리해보겠습니다.  

 

 

       원시 타입 : Primitive Type                                                    

 

1. 원시 타입의 값(원시 값)은 변경 불가능 한 값입니다. [ immutable value ]

2. 원시 값을 변수에 할당하면 변수에는 실제 값이 저장됩니다. -> 변수는 확보된 메모리 공간

3. 원시 값을 가진 변수를 다른 변수에 할당하면, 원본의 원시 값이 복사되어 전달 됩니다. = 값에 의한 전달 (pass by value)

 

 

 

변수는 어떤 하나의 값을 저장하기 위한 메모리 공간이자 그 이름이라고 하였습니다. 아래의 공간 처럼 count 라는 메모리 공간0 이라는 변수에 저장된 데이터 값이 저장됩니다. [1]의 원시 값은 변경 불가능한 값이다라는 의미는 0 이라는 데이터 값(원시 값) 자체를 변경할 수 없다는 의미입니다.

 

 

 

재할당은 이전의 원시 값을 바꾸는 것이 아니라 새로운 메모리 공간을 확보하고 이곳에 재할당할 값인 3을 저장합니다. 변수는 이 새롭게 재할당한 원시 값을 가리킴으로써 변수가 참조하던 메모리 공간의 주소가 바뀌는 것 입니다. 

 

 

 

 

 

 

아래의 그림 처럼 동일한 주소의 메모리 공간에서 데이터 원시 값을 변경할 수 있게 되면 예기치 못하게 변수 값이 변경 될 수 있기 때문에 상태 변경을 추적하기 어려워집니다. 

 

 

 

 

[3]의 값에 의한 전달(공유에 의한 전달.)이라는 복사는 변수의 원시 값이 복사(?)되어 전달됩니다. 새로운 메모리 공간(복사한 새 변수) 에 원시 값 자체가 복사되고 당연히 새로운 메모리 공간이기 때문에 원본의 메모리 공간의 주소값과 새로운 메모리 공간의 주소값은 다릅니다. 

 

실제 자바스크립트에서는 원시 값이 복사된다기 보다는 메모리 주소가 전달 되고 전달된 메모리 주소를 통해서 원본의 값을 참조할 수 있다고 하는데... 무슨 소리야..? 

 

 

 

 

 

 

 

 

 

       참조 타입 : object / reference Type                                                    

 

 

1. 객체(참조) 타입의 값 (객체)는 변경 가능한 값입니다. [ muutable value ]

2. 객체를 변수에 할당하면 변수에는 참조 값이 저장 됩니다. -> 변수는 확보된 메모리 공간

3. 객체를 가리키는 변수를 다른 변수에 할당하면 원본의 참조 값이 복사되어 전달 됩니다. = 참조에 의한 전달 (pass by reference)

 

 

 

객체는 원시 값 처럼 확보해야 할 메모리 공간의 크기를 사전에 정해 둘 수 없는 복합적인 자료구조입니다. 변수 person 에 객체 { name: 'emma' } 를 할당하면 이 객체 자체를 메모리 공간에 저장하는 것이 아니라 객체가 생성된 0x0669F913 와 같은 메모리 공간의 주소를 저장합니다. 즉, 참조 값은 객체가 저장된 메모리 공간의 주소를 의미합니다. 

 

 

 

 

 

 

객체는 변경 가능한 값이기 때문에 재할당 없이 프로퍼티를 동적으로 추가하거나, 갱신하거나 삭제할 수 있기 때문에 " 변경가능한 값" 이라고 정의됩니다. 아래의 코드 처럼 변수에 저장된 객체의 주소를 타고 들어가서 메모리에 저장된 객체를 직접 수정할 수 있습니다. 

 

 

var person = { name: 'emma' };

person.name = 'olivia';   
// console.log(person); -> { name: 'olivia' }

person.address = 'Seoul';  
// console.log(person); -> { name: 'olivia', address: 'Seoul' }

 

 

객체는 이러한 특징으로 인해 원시 값과는 다르게 여러 개의 식별자가 하나의 객체를 공유할 수있습니다. 이와 관해서는 아래의 <<깊은 복사 & 얕은 복사 >> 에서 자세히 설명하겠습니다. 

 

 

 

[3]에서 설명하듯이 객체를 가리키는 변수를 다른 변수에 할당(복사) 하게되면 원본의 참조 값(주소 값)이 복사되어 전달되기 때문에 " 참조에 의한 전달 " 이라고 불리웁니다. 아래의 그림 처럼 여러 개의 식별자 ( person , copy ) 가 하나의 객체를 공유할 수 있는 형태가 됩니다. 

 

 

원본 person을 복사본 copy에 할당하면서 각각의 메모리 공간이 생성됩니다. 이 안에는 객체가 저장된 메모리 공간의 주소값이 저장되어있습니다. (얕은 복사) 즉, 원본과 복사본 모두 동일한 객체를 가리킨다는 의미입니다. 

 

 

 

 

 

 

좀더 정확히 표현하자면 아래의 그림 처럼 되어있다고 생각하시면 됩니다. 같은 객체를 공유하기 때문에 원본 (person) 이나 복사본(copy)에서 객체를 변경하면 서로에게 영향을 주고받게 됩니다. 어차피 {name : 'emma'} 라는 객체를 가리키고 있기 때문에 복사본(copy)에서 프로퍼티를 변경하는 이벤트가 일어나면 원본에서도 동일한 객체를 공유하고 있기 때문에 변경된 객체를 반환하게 됩니다. 

 

 

 

 

 

 

 

 

그렇다면, 제가 면접에서 질문 받았던 얕은 복사와 깊은 복사에서의 값의 수정에 따른 차이점을 어떻게 설명할 수 있을까요? 

 

대중적인 관점에서 깊은 복사는 원시 값과 관련해서 값 자체를 다른 변수에 할당하는 것을 의미하고, 얕은 복사는 객체를 가리키는 참조(주소 값)을 다른 변수에 할당하는 것을 의미합니다. 그렇다면 원시 타입에서 값의 수정 = 재 할당이 일어나면 새로운 메모리 공간(주소)가 생성되면서 변경된 원시 값이 저장되고 변수는 이 새로운 주소를 가리키게 됩니다. 반면에 참조(객체) 타입은 객체가 변경될 수 있기 때문에 새로운 메모리 공간의 생성 없이 메모리에 저장된 객체를 직접 수정할 수 있고 이 메모리 공간을 가리키는 변수 자체에는 별도의 변화가 일어나지 않습니다. 

 

 

 


 

 

 

 

       깊은 복사   &   얕은 복사                                                  

 

객체를 프로퍼티 값으로 가지는 " 객체 " 의 경우 얕은 복사는 한 단계까지만 복사하는 경우를 의미하고, 깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하는 것을 의미합니다. 얕은 복사와 깊은 복사로 생성된 객체는 원본과는 다른 객체입니다. ( = 원본과 복사본은 참조 값이 다른 별개의 객체인 것입니다.) 하지만 얕은 복사는 객체에 중첩되어 있는 객체의 경우 참조 값을 복사합니다.

깊은 복사는 객체에 중첩되어 있는 객체까지 모두 복사하기 때문에 원시 값처럼 완전한 복사본을 만든다는 차이가 있습니다. 

 

원시값과 관련해서 원시 값을 할당한 변수를 다른 변수에 할당하는 것을 깊은 복사, 객체를 할당한 변수를 다른 변수에 할당하는 것을 얕은 복사라고 부르는 경우도 있습니다. 이것이 가장 일반적으로 알고 있는 깊은 복사와 얕은 복사입니다. (150p)

 

 

 

 

 

 

 

* 참고 도서 : 모던 자바스크립트 Deep Dive