*해당 포스팅은 김기수의 '코딩 자율학습 HTML + CSS + 자바스크립트', 정재남의 '코어 자바스크립트' 도서를 참고했습니다.*
const
const는 constant인 '상수'의 어원으로 말 그대로 변하지 않는 상수를 의미한다. const도 let과 같이 ES6에서 추가됐는데, 기본 내용은 let과 비슷하다. let과 다른점이 있다면, 재할당이 안 된다. 그래서 상수 변수를 선언할 때 사용하는 키워드라고 부른다.
선언과 동시에 값이 할당되어야 하며, 메모리에 시작과 동시에 공간이 할당됨을 의미한다.
const num = 20;
num = 10;
Cannot assign to "num" because it is a constant
재할당이 되지 않는다.
또한, 선언을 먼저 하고 할당을 나중에 하는 것도 불가능하다. 선언과 동시에 할당이 필요하다.
const num;
num = 2;
The constant "num" must be initialized
const는 변경 가능하다?
분명 바로 위에서 const는 재할당이 불가능하다고 했다. 그런데 const는 변경 가능할수도 있다.
예시를 하나 들어보겠다.
const person = {
name: "Hong Gildong"
};
delete person.name;
console.log(person);
위와 같은 코드를 실행하면 "{}"과 같이 빈 객체가 출력된다. 하지만 앞서 const가 재할당이 불가능하다고 했다.
그러나 위 코드는 delete라는 키워드에 반응해 빈 객체가 출력됐다. 이는 객체 자료형의 특성인 참조 때문에 그렇다.
이전 포스팅 참조형과 기본형 데이터를 계속 이야기 했다. 기본형과 참조형은 관리하는 방식에 차이가 있다.
기본 자료형: 깊은 복사 (Deep copy)
기본 자료형은 변수에 데이터를 할당할 때 데이터 그 자체가 할당된다.
let num = 10;
let copyNum = num;
num = 20;
위 코드를 그림으로 나타냈다. 처음 num은 10을 할당 받았기에 @8100에는 10이 존재한다. num또한 이를 참조한다.
copyNum은 num을 복사했기 때문에 같은 @8100을 참조한다. 그런데 num이 20으로 바뀌면서 @8101에 20을 추가하면서 참조하는 곳이 바뀌었다. 그러나 copyNum은 여전히 @8100을 참조하기 때문에 바뀌지 않는다. 즉, num과 copyNum은 연동 된 것이 아니다.
이렇게 복사한 값을 재할당할 때 한쪽 데이터가 변경되어도 서로 영향을 미치지 않게 복사되는 것을 깊은 복사라고 한다.
기본 자료형: 얕은 복사 (shallow copy)
깊은 복사에서는 기본 자료형을 알아봤다. 하지만 우리는 기본 자료형만 쓰진 않는다.
const person ={
name : "Hong Gildong"
};
person = {
name: "Lee"
}
위와 같은 상황이 가능할까? 되지 않는다. 알루미늄이 아니라 유리로 만들었대도 냄비는 냄비인 것처럼.
하지만, 알루미늄이 아니라 유리로 냄비 자체는 만들 수 있다.
따라서 변수에 할당된 객체에 속성을 추가하거나 값을 변경하는 건 가능하다.
const person = {
name: "Hong Gildong"
};
person.name = "Hong"
변수 person의 입장에선 데이터를 재할당하는 것이 아니기 때문이다. 그림으로 나타내보겠다.
이름 person의 객체가 @8100을 참조한다. 값이 객체므로 @8100에서 객체 변수영역에 @2000~부터 임의의 영역을 할당한다. 객체 변수 영역에 프로퍼티인 name을 값 @8101에 저장한다. 그리고 이 주소를 참고 할 것이다.
그러나 name은 Hong으로 바뀌었고, @8101은 가비지 컬렉팅 되고 @2000의 값은 @8102를 참조한다.
잘 보면, person 즉, @100은 바뀌지 않았다. person의 속성인 name은 참조하는 곳이 달라졌을 뿐이다. 하지만 데이터 영역과 객체 변수 영역 모두 변경되었다. 이처럼 데이터를 복사했을 때 한쪽 데이터가 변경되면 다른 쪽 데이터도 변경되어 서로 영향을 받는 것을 얕은 복사라고 한다.
얕은 복사와 깊은 복사의 추가 예시
불변 객체?
var user = {
name : "korea",
gender: "male"
};
var changeName = function(user, newName){
var newUser = user;
newUser.name = newName;
return newUser;
}
var user2 = changeName(user, "Jung");
if(user1 == user2){
console.log("유저 정보가 변경되었습니다.");
}
console.log(user.name, user2.name); //Jung Jung
console.log(user === user2); //true
주석처리 된 결과를 보면 알겠지만, 분명 user2의 name만 변경했다. 그런데 어째서 user1과 user2의 name같은 걸까?
@100과 @101이 같은 곳을 참조하기 때문이다. 이미 changeName을 한 시점부터, 새로운 객체 user2(newUser)는 효율을 위해 같은 객체인 @8100을 참조했을 것이다. 같은 곳을 참조하기 때문에 두 객체가 바뀌는 것이다.
이런 현상을 막기 위해선 다른 객체를 만들어야 한다.
var user = {
name : "korea",
gender: "male"
};
var changeName = function(user, newName){
return {
name: newName,
gender: user.gender
}
}
var user2 = changeName(user, "Jung");
if(user1 == user2){
console.log("유저 정보가 변경되었습니다."); //유저 정보가 변경되었습니다.
}
console.log(user.name, user2.name); //korea Jung
console.log(user === user2); //false
changeName 함수의 return이 바뀌었다. 이전의 방식은 user를 복사했는데, 새로운 객체를 반환하고 있다. 마찬가지로 그림으로 표현해보겠다.
user는 연한 주황, user2는 연한 파랑색으로 표현했다. 알 수 있듯이, 새로운 객체가 생겨 user2가 user의 정보에 의존하지 않고 있다. 그렇게 때문에 서로 변경이 한꺼번에 되지 않았다. 다만 @2003의 경우 색이 섞여있는데, gender의 경우는 user2가 user의 정보를 참조하기 때문이다.
하지만 문제점이 있다. 객체를 return하면서 변경할 필요가 없는 gender를 하드코딩했다는 점이다.
지금은 속성이 2가지 뿐이지만, 나중에 속성이 늘어나면 모든 속성을 타이핑할 순 없을 것이다. 즉 객체를 복사할 수 있는 함수가 있다면 좋을 것이다.
객체 안에 객체, 깊은 복사가 필요하다
앞서 우리는 얕은 복사는 바로 아래 단계의 값만 복사하는 방법이라고 알았다. 기본 데이터는 깊은 복사도 한 단계만 이뤄지지만, 만약 객체 안에 객체가 있는 데이터는 어떻게 할까? 아래 예시를 보자.
//기존 정보를 복사해서 새로운 객체를 반환하는 함수(얕은 복사)
var copyObject = function(target){
var result = {};
for (var prop in target){
result[prop] = target[prop];
}
return result;
}
var user = {
name : "korea",
etc: {
hobboy: "soccer",
favorite: "reading",
movie: "parasite"
}
};
var user2 = copyObject(user); //user을 복사하고 있다.
user2.name = "japan";
console.log(user.name === user2.name); //false
user.etc.hobby = "basketball";
console.log(user.etc.hobby === user2.etc.hobby); //true
user2.etc.movie = '';
console.log(user.etc.movie === user2.etc.movie); //true
user2에 user를 복사하고, user2의 name과 etc의 속성들을 재할당해본다. name은 성공적으로 잘 변경됐다. 하지만 etc의 프로퍼티를 변경하려 하니 바뀌지 않은 것을 볼 수 있다. 분명 우리는 객체를 따로 return하는 함수도 만들었는데, 왜일까?
어떤 객체를 복사할 때 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체와 프로퍼티 중 그 값이 기본형 데이터일 경우에는 그대로 복사하면 되지만, 참조형(객체) 데이터는 다시 그 내부의 프로퍼티들을 복사해야 한다. 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야지만 비로소 깊은 복사가 이뤄진다.
위의 예시를 그림으로 살펴보겠다.
user 객체에는 name이라는 원시형과 etc라는 객체형 데이터가 포함되어있다.
먼저, 메모리에 빈공간인 @100을 확보해 주소의 이름을 user로 지정한다. 이 값을 데이터 영역 @8100에 저장한다.
@8100에 가보니, 객체로 이루어진 데이터기 때문에 @5000 이후로 빈 공간을 확보해 객체 데이터를 저장한다.
이후 프로퍼티의 값들은 데이터 영역으로 저장되고, @etc 역시 @3002~로 저장된다.
그렇다면, 이를 복사한 user2는 어떻게 복사되었을까?
우선, copyObject 함수에서 새로운 result 객체를 반환했으므로 변수 영역에 독립적으로(user의 경우 @100, user2의 경우 @300) 저장됐음을 알 수 있다.
name인 "korea" 또한 기존 user와는 다른 주소 @7101에 저장되었다. 그러나, etc는 좀 달랐다.
우리는 copyObject에서 etc 객체를 복사했다. 즉, 틀 전체를 복사해버린 것.
다시 말해 etc 객체는 user와 user2 모두 같은 곳을 참조하고 있다는 것이다. 그림으로 표현해보겠다.
이 상황에서 etc의 값이(편의상 값이라고 했으며 원래는 etc 객체의 프로퍼티의 값이 바뀌는 것) 바뀐다면, user2의 name과 user의 name이 같이 바뀐다. 그럼 이를 어떻게 해야할까?
객체 안의 프로퍼티도 같이 복사해야한다
var copyObjectDeep = function(target){
var result = {};
if (typeof target === 'object' && target !== null){
for (var prop in target){
result[prop] = copyObjectDeep(target[prop]);
}
} else {
result = target;
}
return result;
}
앞 예시에서 봤던 copyObject 함수를 보완한 함수다. 기본적으로 비슷하지만, else 조건이 추가되었고 함수또한 재귀로 호출된다.
간단하게 도식화 해보았다. 만약 객체라면 재귀로 호출해서 프로퍼티까지 복사해오는 것이고, 그렇지 않고 원시(기본)형 데이터라면 그대로 값을 할당하도록 했다. 이 함수를 사용해 객체를 복사한 다음에는 원본과 사본이 완전히 다른 객체를 참조하게 되어 어느 쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향이 생기지 않는다.
'Programming > JavaScript' 카테고리의 다른 글
[JS] 자바스크립트 실행 컨텍스트, 스코프 체인, environmentRecord/outerEnvironmentReference (0) | 2024.03.26 |
---|---|
[JS] 자바스크립트 조건문, 반복문 (0) | 2024.03.24 |
[JS] 자바스크립트 함수, 순수 함수/일급 함수,함수 표현식/함수 선언식/화살표 함수, 함수 호이스팅 (0) | 2024.03.24 |
[JS] 자바스크립트 변수 var/let, 호이스팅, 블록 스코프/함수 스코프, 불변값/가변값 (0) | 2024.03.23 |
[JS] 자바스크립트 변수의 정의, 선언/할당, 메모리 구조, 리터럴 (0) | 2024.03.23 |