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와 헷갈릴 수 있음)

+ Recent posts