5. 불변 객체
1) 불변 객체
-참조형 데이터에서 데이터의 객체 자체를 변경할 때는 변경할 객체를 새로운 빈 공간의 주소에 할당하므로 기존의 데이터는 바뀌지 않음
-따라서 내부 변수를 변경할 때마다 새로운 객체를 만들어 재할당하는 것으로 규칙을 정하거나 자동으로 새로운 객체를 만드는 도구를 활용하면 불변성 확보 가능
2) 불변 객체가 필요한 경우
-아래의 경우 사용자의 이름이 바뀌는 것을 감지해야 하지만 이름이 바뀌는 순간 초기값의 이름도 바뀌므로 초기값과 바뀐 값의 이름을 비교해도 항상 같다고 나옴
// 가장 초기 이름과 성별 정보 입력
var user = {
name: 'old_name',
gender: 'male'
};
// 초기 정보와 새로운 이름을 인자로 받아 초기 정보에서 이름 값을 바꾸는 함수
var changeName = function(user, newName){
var newUser = user;
newUser.name = newName;
return newUser;
};
// 함수 실행(user 객체의 name을 'old_name'에서 'new_name'으로 변경)
var user2 = changeName(user, 'new_name');
// 초기 객체인 user와 이름을 바꾼 user2를 비교하여 다른 점이 있다면 아래 메세지 출력
// 함수를 통해 이름을 바꿔 user와 user2의 이름이 다르다면 아래의 메세지가 출력될 것
if(user !== user2){
console.log('유저 정보가 변경되었습니다.');
}
// 하지만 객체 내부의 변수를 하나 바꾸면 기존의 객체의 값까지 바꿔버리므로 user의 name도 new_name이 되어버려 메세지 출력X
console.log(user.name, user2.name); // new_name new_name
console.log(user === user2); // true
-따라서 다음과 같이 함수에서 객체 전부를 가져온 뒤 바꾸고 싶은 내부변수(name)만 바꾸고 return하도록 해야함
var changeName = function(user, newName){
return {
name: newName,
gender: user.gender
};
};
-또한 예시의 객체는 내부 변수가 두 개뿐이지만 여러 개라 매번 전부 써줄 수 없을 경우 for문을 통해 범용 함수 사용
var copyObject = function(target) {
var result = {};
for (var prop in target) { // 인자로 받은 target 객체 내의 모든 내부 변수 각각에 대해
result[prop] = target[prop]; // result에 똑같은 내부 변수 생성
}
return result;
};
-위 함수를 통해 user 객체를 user2로 복사하고 user2의 내부 변수를 바꾸면 user의 내부 변수는 그대로, user2의 내부 변수만 변경할 수 있음
var user = {
name: 'old_name',
gender: 'male'
}
var user2 = copyObject(user) // user 객체의 모든 내부 변수 복사
user2.name = 'new_name' // user2에 복사된 변수 중 name 변수 변경, 기존 초기 객체 user에는 변화X
if (user !== user2){
console.log('유저 정보가 변경되었습니다.');
}
console.log(user.name, user2.name); // old_name new_name
console.log(user === user2); // false
-매번 copyObject와 같은 형식의 함수를 작성하는 것 대신 immutable.js, baobab.js 등의 라이브러리 사용해도 됨
// immutable.js 사용 예시
// immutable 라이브러리 설치
const Immutable = require('immutable')
// user를 immutable 라이브러리의 Map함수를 통해 정의
const user = Immutable.Map({name: 'old_name', gender: 'male'});
// user2는 user 객체에서 name만 'new_name'으로 바꾼 객체로 정의
const user2 = user1.set('name', 'new_name')
console.info(user.get('name'); // old_name, user2를 바꿨어도 user는 바뀌지 않아 기존 값이 출력
console.info(user2.get('name'); // new name, user2는 바뀐 값으로 출력
console.info(user === user2); // false, user와 user2가 서로 달라짐
3) 얕은 복사와 깊은 복사
-얕은 복사는 바로 아래 단계의 값만 복사(중첩된 객체가 있을 시(객체 안의 객체), 내부의 객체의 내부 변수는 주소값만 복사되어 원본과 사본이 연결되어 있음)
-깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사
-예시
// user 객체 내에 urls 객체가 중첩되어 존재
var user = {
name: 'old_name',
urls: {
portfolio: 'http://github.com/abc',
blog: 'http://blog.com',
facebook: 'http://facebook.com/abc',
}
};
// 새로운 변수 user2에 위에서 사용한 copyObject 함수를 사용하여 얕은 복사 시행
var user2 = copyObject(user)
// name은 중첩되지 않고 객체 내에 바로 아래 단계의 변수이므로 바귄 객체에 대해 다른 주소가 할당되어 기존값은 바뀌지 않음
user2.name = 'new_name';
console.log(user.name === user2.name); // false
// urls 내부의 변수는 name변수보다 한 단계 아래의 변수로, 기존 값과 주소를 공유하게 되어 원본과 사본 둘 중 하나가 바뀌면 같이 바뀜
user2.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // true
user2.urls.blog = '';
console.log(users.urls.blog === user2.urls.blog); // true
-따라서 다음과 같이 중첩된 객체 urls에 대해서도 copyObject 함수를 적용하여 깊은 복사를 시행해주어야 함
var user2 = copyObject(user);
user2.urls = copyObject(user.urls);
user.urls.portfolio = 'http://portfolio.com';
console.log(user.urls.portfolio === user2.urls.portfolio); // false
user.urls.blog = '';
console.log(user.urls.blog === user2.urls.blog); // false
-범용적으로 사용할 수 있는 깊은 복사를 수행하는 함수
var copyObjectDeep = function(target){
var result = {};
if(typeof target === 'object' && target !== null){ // target이 null이 아닌 객체인지 확인
for(var prop in target){ // target이 객체라면 target 내부의 변수를 result에 각각 복사하여 모든 객체내의 내부변수를 복사하도록 명령
result[prop] = copyObjectDeep(target[prop]); // copyObjectDeep 함수를 재귀적으로 호출하여 객체 내부에 객체가 있다면 마지막 객체 내부까지 들어가서 모든 객체의 내부 변수를 탐색
}
}else{
result = target; // 객체 내부의 객체가 아닌 내부 변수로 존재하는 것은 그대로 result에 복사
}
return result;
};
-JSON을 활용한 깊은 복사: 객체를 JSON 형식의 문자열로 전환했다가 다시 JSON 객체로 바꾸기
var copyObjectViaJSON = function(target){
return JSON.parse(JSON.stringify(target));
};
※주의할 점: JSON으로 변경할 수 없는 변수(__proto__나 geteer/setter 등)은 무시됨
6. undefined와 null
1) undefined: 사용자가 어떤 값을 지정할 것이라고 예상되는 상황에 실제로 값을 지정하지 않을 때 Javascript가 자동적으로 부여
-값을 대입하지 않은 변수(데이터 영역의 메모리 주소를 지정하지 않은 식별자)에 접근할 때
var a;
a // undefined
-객체 내부에 존재하지 않는 변수에 접근할 때
var a={
b: 1,
c: 2
}
a.d //undefined
-return문이 없거나 호출되지 않는 함수 실행할 때
var a = function(b, c){
return b+c
};
var b = function(b, c){
b+c;
};
a(1, 1) // 2
b(1, 1) // undefined
-undefined를 명시적으로 부여한 경우: undefined는 비어있을을 의미하는 값으로 실재하며 빈 공간을 확보하고 인덱스가 이름으로 지정됨
(forEach 등으로 순회의 대상이 됨)
-undefined를 Javascript에서 자체적으로 부여한 경우: undefined는 키값(인덱스)가 존재하지 않음 (forEach 등으로 순회할 때 무시됨)
2) null: 비어있음을 명시적으로 나타냄
var n = null;
console.log(typeof n); // object, null은 object 형식
console.log(n == undefined); // true, null은 undefined와 동일하게 '비어있음'을 의미
console.log(n == null); // true
console.log(n === undefined); // false, null과 undefined는 의미는 같지만 형식이 다름
console.log(n === null); // true
-'비어있음'을 명시적으로 나타내고 싶다면 undefined보다는 null을 사용하는 것이 좋음
(undefined를 명시하는 것은 Javascript가 자체적으로 부여하는 undefined와 헷갈릴 수 있음)
'Front-end > Javascript' 카테고리의 다른 글
[코어 자바스크립트] 03. this(2) (0) | 2022.12.08 |
---|---|
[코어 자바스크립트] 03. this(1) (0) | 2022.12.05 |
화면 오픈소스 사이트, codepen (0) | 2022.12.05 |
[코어 자바스크립트] 02. 실행 컨텍스트 (0) | 2022.12.02 |
[코어 자바스크립트] 01. 데이터 타입(1) (0) | 2022.11.29 |