Frudy
4k
2020-02-02 23:30:37 작성 2020-02-13 14:32:54 수정됨
5
742

Object.assign() vs 전개구문


실무에서 응용해보면서 느낀 이 둘의 차이점을 정리해볼게요.

QnA에 글이 올라와서요 ㅎㅎㅎ


혹시나 잘못된 부분이 있다면 지적부탁드립니다.


///


기능 : 2개 이상의 객체에 프로퍼티에 담긴 값을 덮어씀.


Object.assign을 보면,

src와 data객체에 저장된 객체가 서로 합쳐진다는 느낌을 받습니다.



전개구문 역시 합쳐진다는 느낌을 받습니다.

하지만, 이 둘은 명확하게 달라서 처음에 고생좀했어요.


차이점 : 참조 유지 vs 참조 다름




Object.assign의 return값은,

첫번째 매개변수의 참조값과 동일합니다.


핵심은, src변수에 저장된 참조값을 건드리지않고,

src변수에 저장된 객체 안에 값을 덮어씁니다.


하지만, 전개구문은 다릅니다.


합쳐진 객체는 합쳐지기 전 원본객체와 참조값이 다릅니다.


///


사실 Object.assign을 저렇게 사용하면, 참조값이 달라지지 않는것은 당연합니다.

Object.assign의 첫 번째 매개변수로 온 객체를 '직접 수정' 하기 떄문입니다.


그래서 저는, 참조가 유지된다 vs 참조가 다르다 << 이 관점에서 응용해왔습니다.


예시1)

const COMMON_QUERY = Object.freeze({
page: 1,
per: 10,
orderby: "created"
});


프론트단에서 서버에 리스트 데이터를 요청할 때 흔히 쓰는 값인대요,

보통 현재 페이지번호만 바꿔서 보내주는경우가 많아요.

그래서 저 공통 쿼리객체에서 page값을 교체한 다음 보내고 싶은데,

Object.freeze는 안에 값을 바꾸지못하도록 만들기 때문에 수정이 불가능합니다.


그러므로, COMMON_QUERY객체를 Object.assign으로 page = 2를 덮어쓸 수 없으니,

이렇게 했었죠.


예시2)

이번엔 참조를 반드시 유지해야하만 하는 케이스였는대요,


* 하나

* 둘

* 셋

* 넷

[참조바꾸기버튼]


<ul>
<li v-for="item in currentList">{{item.name}}</li>
</ul>
<button @click="referenceChange">참조바꾸기</button>
listWrap = {
list: [
{name: "하나"},
{name: ""},
{name: ""},
{name: ""}
],

meta: [
{total: 4}
]
};

currentList = this.listWrap.list;

referenceChange() {
this.listWrap = {
list: [
{name: "one"},
{name: "two"},
],

meta: [
{total: 2}
]
};
}


이렇게 리스트 뿌리는 페이지가 있다고 했을 때,

참조바꾸기버튼으로,


(서버에서 리스트를 받았다 가정하고) 데이터를 덮어쓰는게 아닌 새로운 참조로 바꿔버리면

화면에서 다시 렌더링안됩니다. (원래는 변수에 값바뀌면 화면도 자동으로 렌더링되는데...)


이런식으로, 은근히 참조를 유지해야하는경우 / 하면 안되는경우가 있어서,

프로그래밍할 때 한번씩 햇갈립니다.

2
1
  • 댓글 5

  • TigerJquery
    169
    2020-02-03 08:33:38 작성 2020-02-03 08:34:22 수정됨

    사견이긴 한데요 😅

    밑에서 뷰의 반응성 예제를 들어주셨는데,
    뷰는 내부적으로 Object.defineProperty 사용해서 반응성을 구현하는데,
    그래서 인스턴스 내의 data 객체 내에서 상태를 정의하거나, 뷰에서 제공하는 set 메서드로 별도로 설정해주지 않으면 반응성으로 동작하지 않잖아요.

    사실 데이터 객체 내에서 정의된 프로퍼티라면, 해당 프로퍼티의 값을 바꿔줘도 화면에서 데이터 변화를 감지 못하는 일이 없는데, 어떤 의미로 작성해주신 것인지 궁금합니다

    그리고 서버에서 받아오는 데이터를 수정할 필요가 있다면, 원본데이터를 수정하는 것 보다는 용도에 따라 별도로 deepCopy해서 사용하는 것이 맞지 않을까요?
    제가 적어주신 내용을 정확히 이해한 것인지는 모르겠지만, 문득 생각이 들어 의견 남겨봅니다.

    그리고 Object.assign과 전개연산자는 shallowCopy가 필요할때도 사용합니다 🙂
    위 예시는 경우에 따라 다르겠지만 원본 객체에 변화를 주기 때문에 사이드 이펙트가 생길 여지가 있어보여요!
    본문 예시대로라면 Object.assign도
    const obj1 = { a: 'a', 'b: 'b'}, const obj2 = { 1: 1, 2: 2 }
    const result = Object.assign({}, obj1, obj2) 이렇게 사용해야 될 것 같아요~

    spread operator는 객체의 setter를 트리거하지 못하기 때문에, 이 부분도 차이가 있구요.
    저도 아직 모자란 부분이 많지만, 알고있는 수준에서 의견을 남깁니다 :) 

    0
  • Frudy
    4k
    2020-02-03 12:47:33 작성 2020-02-03 12:59:55 수정됨

    1. 위 코드는 클래스로 컴포넌트를 정의했었기 때문에, 별도의 data객체에서 선언하지않아도 작동됩니다.


    하지만 클래스라는걸 미리 명시하지않았군요.


    2. setter를 트리거하지않는다는 차이점은

    제가 빼먹은게 맞습니다.


    3. 서버에서 받아오는 데이터를 수정하려는 것은 아니었습니다.

    위 코드에서는 listWrap에 원본데이터의 참조를 넘겨주고있어서,


    기존 listWrap의 list에 저장되는 참조값과

    새로받은 listWrap의 list에 저장되는 참조값이

    달라지기때문에 반응성이 망가지게 됩니다.


    그래서 저는 이 경우에, Object.assign으로 listWrap에 서버의 데이터를

    기존의 객체안에 덮어쓰는방법으로 해결했어요.


    부작용은, 타입스크립트의 타입체크로,

    얕은복사시 제가 원하지않는 값도 같이 덮어써지는 현상은 막을수있다고 생각했어요.


    그리고 예제가 좀 부적절했네요!

    이해하기어려워보여요.

    저녁에 다시 고치겠습니다.

    1
  • TigerJquery
    169
    2020-02-03 19:55:25

    타입스크립트를 사용해본 적이 없어서 몰랐네요 🙂
    공유 감사드립니다 :)

    0
  • miraee05
    137
    2020-02-13 00:59:46

    Object.assign은 명세가 원래 첫번째 argument의 값을 modified 한다고 써있습니다. 


    따라서 참조 유지로 이해하시는건 적절하지 않은 것 같습니다.

    const result = Object.assign(src, data);


    는 단순히

    Object.assign(src, data);

    const result = src;


    와 다르지 않습니다. 


    이 때문에 Object.assign을 쓸때는 첫번째 argument에 {} 리터럴 객체를 넣어서

    Object.assgn({}, src, data) 이렇게 사용하면 전개식과 똑같이 나오게 됩니다.

    저는 이 표현에 반대하는 편이지만 위에서 말씀하신것처럼 참조 유지가 되지 않는 것이지요.

    오히려 저는 이런 표현보다는 새객체에 할당했다는 표현과, 기존 객체를 수정했다는 표현이 맞는것 같습니다.

    (또는 얕은 복사의 개념가 비슷할 것 같습니다.)


    마찬가지로 전개구문은 리터럴 객체를 할당을 해놓고 할당 안의 키와 밸류를 해체할당해서 넣어주는 것이기 때문에 새 객체가 생성되는 것입니다.


    Eslint airbnb에서는 Object.assign 사용을 error로 처리하는데 그 이유는 아래와 같습니다. 

    링크 참조해주시면 감사하겠습니다.

    https://github.com/airbnb/javascript#objects--rest-spread


    따라서 참조 유지와 참조 다름의 개념이 아니라. 새 객체에 할당 했느냐, 아니면 기존 객체를 수정했느냐에 관점으로 봐야 할 것 같네요.


    참고로 ES6의 많은 Object, Array 관련 메서드들이 이름만 가지고는 deep copy와 shallow copy를 구분할 수 없으므로 이부분도 신중하게 고민하시고 메서드를 쓰는게 좋은것 같습니다.



    0
  • Frudy
    4k
    2020-02-13 12:06:02

    음.... 네 오해가 생길수있겠네요.

    저는 둘다 "수정"된 내용의 객체를 얻을 수 있는 방법


    이란걸 알고있는 상태에서,

    좀더 눈여겨봐야하는 차이인

    참조가 바뀌냐 안바뀌냐를 말씀드렸습니다.


    말씀대로 Object.assign은

    참조유지가 주 기능인 메소드가 아니죠. 맞아요.


    그래서 다시 정의해본다면,

    첫번째 매개변수로 넘긴 변수의 프로퍼티에 저장된 값을 덮어쓰는 메소드 이므로,


    당연히 첫번째 매개변수로 넘긴 변수에 저장되는 참조값 자체는 바뀌지않는다 라고

    정의내리는게 차라리 나을거같습니다. 


    잘 정리해서 다시 올리겠습니다.

    날카로운피드백 감사합니다.



    0
  • 로그인을 하시면 댓글을 등록할 수 있습니다.