Bin's Blog

불변 객체 본문

JavaScript

불변 객체

hotIce 2023. 5. 3. 11:23
728x90

1. 불변 객체란

  • 이전에 참조형 데이터에 대해서 살펴봤을 때, 참조형 데이터의 가변은 데이터 자체가 아닌 내부 프로퍼티를 변경할 때만 성립한다. 데이터 자체를 변경하려고 하면 기본형 데이터와 마찬가지로 기존 데이터는 변하지 않는다. 
  • 불변성을 확보할 필요가 있을 경우에 불변 객체를 취급하고, 그렇지 않은 경우에 기존 방식대로 사용하는 식으로 상황에 따라 대처한다.
  • 불변 객체가 필요한 경우는 값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우가 종종 발생한다. 
  • 객체의 가변성에 따른 문제점
let user = {
    name: "Jaenam",
    gender: "male"
};

let changeName = (user, newName) => {
    let newUser = user;
    newUser.name = newName;
    return newUser;
};

let user2 = changeName(user, "Lee");

if (user !== user2) {

   console.log("유저 정보가 변경되었다");

}

// Lee, Lee
console.log(user.name, user2.name);

// true
console.log(user === user2);
  • 위에 코드를 보면 user2.name만 바꿨는데 user.name도 바뀐 것을 볼 수 있다.

 

  • 해결 방법
let user = {
    name: "Jaenam",
    gender: "male"
};

let changeName = (user, newName) => {
    return {
       name : newName,
       gender: user.gender
    };
};

let user2 = changeName(user, "Lee");

// 유저 정보가 변경되었다.
if (user !== user2) {

   console.log("유저 정보가 변경되었다");

}

// Jaenam , Lee
console.log(user.name, user2.name);

// false
console.log(user === user2);
  • changeName 함수가 새로운 객체를 반환하도록 수정해서 이제 user와 user2는 서로 다른 객체이다. 
  • 그러나 ChangeName 함수는 새로운 객체를 만들면서 gender를 변경할 필요가 없는데 하드코딩으로 입력했다. 이런 식으로 하게 되면 대상 객체에 정보가 많을수록, 변경해야 할 정보가 많을수록 사용자가 입력하는 수고가 늘어난다. 
  • 그래서 프로퍼티 개수와 상관 없이 모든 프로퍼티를 복사하는 함수를 만드는 것이 좋을 것 같다. 

 

  • 얕은 복사(기존 정보를 복사해 새로운 객체를 반환하는 함수)
let copyObject = (target) => {
    let result = {};
    for (let prop in target) {
        result[prop] = target[prop];
    }
    
    return result;

};

let user = {
    name: "Jaenam",
    gender: "male"
};


let user2 = copyObject(user);
user2.name = "Jung";

// 유저 정보가 변경되었다.
if (user !== user2) {

   console.log("유저 정보가 변경되었다");

}

// Jaenam , Lee
console.log(user.name, user2.name);

// false
console.log(user === user2);

1-1. 얕은 복사와 깊은 복사

  • 얕은 복사는 바로 아래 단계의 값만 복사하는 방법이고, 깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법이다.
  • 위에 얕은 복사 코드에서 봤듯이 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주솟값만 복사한다는 뜻이다.
  • 그러면 얕은 복사의 경우 해당 프로퍼티에 대해서 원본과 사본이 모두 동일한 참조형 데이터의 주소를 가리키게 되고 사본을 바꾸면 원본도 바뀌고 원본도 바뀌면 사본도 바뀐다. 
  • 예시
let copyObject = (target) => {
    let result = {};
    for (let prop in target) {
        result[prop] = target[prop];
    }
    
    return result;

};

let user = {
    name: "Jaenam",
    urls: {
       portfolio: "http://github.com/abc",
       blog: "http://blog.com",
       facebook: "http://facebook.com/abc"    
    }

};

let user2 = copyObject(user);

let user2 = copyObject(user);
user2.name = "Lee";

// false
console.log(user.name === user2.name);

user.urls.portfolio = "http://portfolio.com";
// true
console.log(user.urls.portfolio === user2.urls.portfolio);


user2.urls.blog = "";
// true
console.log(user.urls.blog === user2.urls.blog)
  • 위에 봤듯이 user의 정보를 바꿨더니 user2의 정보도 따라서 바뀌고 user2의 정보를 바꿨더니 user의 정보도 바뀌었다. 
  • user객체에 직접 속한 프로퍼티에 대해서는 복사해서 완전히 새로운 데이터가 만들어진 반면에 한 단계 더 들어간 urls의 내부 프로퍼티들은 기존 데이터를 그대로 참조한다.
  • 이런 현상을 방지하려면 user.urls 프로퍼티에 대해서도 불변 객체로 만들 필요가 있다.

 

  • 중첩된 객체에 대한 깊은 복사
let user2 = copyObject(user);
user2.urls = copyObject(user.urls);

user.urls.portfolio = "http://portfolio.com";
// false
console.log(user.urls.portfolio === user2.urls.portfolio);

user2.urls.blog = "";
// false
console.log(user.urls.blog === user2.urls.blog);

 

  • 객체의 깊은 복사를 수행하는 범용 함수
let copyObjectDeep = (target) => {
    let result = {};
    if (typeof target === "object" && target !== null) {
       for (let prop in target) {
           result[prop] = copyOjbectDeep(target[prop]);
       
       }
    } else {
       result = target;
    }
    return result;


};
  • 위에서 재귀적으로 copyObjectDeep 함수를 호출해서 속성값을 복사하고, 복사된 값을 result[prop]에 저장된다. 이 과정에서 객체의 중첩된 속성까지 깊이 복사가 이루어진다. 

 

  • JSON을 활용한 간단한 깊은 복사
let copyOjbectViaJSON = (target) => {
    return JSON.parse(JSON.stringify(target));

};

let obj = {
    a: 1,
    b: {
        c: null,
        d: [1, 2],
        func1: function () { console.log(1);}
       },
       func2: function () { console.log(4); }
    };
}

let obj2 = copyObjectViaJSON(obj);

obj2. a = 3;
obj2.b.c = 4;
objb.d[1] = 3;


// { a : 1, b: { c:null, d: [1, 3], func1: f()}, func2: f()}
console.log(obj);

// { a : 3, b: { c: 4, d: [1, 2]}
console.log(obj2);
  • 위의 방식은 객체를 JSON 문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꾸는 것이다. 그러나 이 메서드는 함수, 심볼, 순환 참조 등을 변환하지 않는다. 따라서 변환과정에서 함수와 같은 정보는 손실돼서 위와 같이 obj2를 출력하면 func이 사라진다. 
  • JSON으로 변경할 수 없는 프로퍼티들은 모두 무시한다. httpRequest로 받은 데이터를 저장한 객체를 복사할 때 등 순수한 정보만 다룰 때 활용하기 좋은 방법이다.
728x90

'JavaScript' 카테고리의 다른 글

undefined와 null  (0) 2023.05.05
Map() 객체  (0) 2023.05.04
JavaScript Array.map() 함수  (0) 2023.05.02
불변값과 가변값  (0) 2023.05.01
데이터 타입이란?  (0) 2023.04.25