⚡... 전개 연산자 [Spread Operator]

ECMAScript6( 2015 )에서 새롭게 추가 되었으며
특정 객체 또는 배열의 값 다른 객체,
배열로 복제하거나 옮길 때 사용❗

🔸배열 조합

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// concat 메소드로 arr1에 arr2랑 arr3 합친 새로운 배열 반환
const arrWrap = arr1.concat(arr2, arr3);

// [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(arrWrap);

👉 concat : 두 개 이상의 배열이나 문자열을 합쳐서
새로운 배열( 또는 문자열 )을 반환하는 메소드

...전개 연산자 사용 예제

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
const arrWrap = [...arr1, ...arr2, ...arr3];

// [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(arrWrap);

🔸객체 조합

const obj1 = {
  a: 'A',
  b: 'B'
};

const obj2 = {
  c: 'C',
  d: 'D'
};

const objWrap = {obj1, obj2};
console.log(objWrap);

👇

{
  obj1: {
    a: 'A',
    b: 'B'
  },
  obj2: {
    c: 'C',
    d: 'D'
  }
}  // 출력된걸 보면 객체 자체가 들어감

...전개 연산자 사용

const obj1 = {
  a: 'A',
  b: 'B'
};

const obj2 = {
  c: 'C',
  d: 'D'
};

const objWrap = {...obj1, ...obj2};
console.log(objWrap);

👇

{
  a: 'A',
  b: 'B',
  c: 'C',
  d: 'D'
}

🧚‍♀️...전개 연산자를 쓰면 "기존 배열을 보존"

const arr1 = [1, 2, 3];
const arr2 = arr1.reverse();

console.log(arr1); // [3, 2, 1]
console.log(arr2); // [3, 2, 1]

💁‍♀️ 원본 배열까지 역순으로 변경됨✔

const arr1 = [1, 2, 3];
const arr2 = [...arr1].reverse();

console.log(arr1); // [1, 2, 3]
console.lot(arr2); // [3, 2, 1]

◽ 원본 배열 유지➡불변성 유지🔥


얕은 비교 🤜💥🤛 깊은 비교

🔹 얕은 비교 Shallow Compare❓

숫자 문자열 등 '원시 자료형'은 '값'을 비교함

배열, 객체 등 '참조 자료형'
👉 값 혹은 속성 비교하지 않고 '참조 되는 위치'를 비교

◽ 원시 자료형(number, string, boolean 등) : 값 자체가 콜스택에 저장

◽ 참조 자료형(object, array, function 등) : 값은 힙(Heap)에 저장되고,
그 주소(reference)가 콜스택에 저장

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };

console.log(obj1 === obj2); // false

✅ 객체는 참조 자료형이라 === 같은 연산자는 객체의 "주소(참조)"만 비교
그래서 obj1obj2는 생긴 건 같아도 다른 주소를 갖기에 false 나옴
👉 이걸 얕은(shallow) 비교 라고 함

⚡...전개 연산자로 얕은 복사

⚡Object.assign( )으로 얕은 복사

✅ 객체를 복사하거나 여러 객체를 하나로 합칠 때 사용하는 메소드

Object.assign(복사될객체, 복사할객체);
const original = {
  a: 1,            // 👉 숫자 (원시값)
  b: { inner: 2 }  // 👉 객체 (참조값)
};

// 빈 객체에 original 내용 복사
const copy = object.assign({}, original);

// ✅ 원시값 바꾸면 원본 영향 없음
copy.a = 100;
// ❌ 참조된 객체는 공유됨
copy.b.inner = 999;

console.log(original.a);        // 1 (안 바뀜)
console.log(original.b.inner);  // 999 (원본도 바뀜!)

얕은 복사는 바깥값만 복사하고,
안쪽에 있는 객체는 같은 걸 공유하기에
안쪽 값을 바꾸면 원본도 바뀜 🙆‍♀️

Object.assign도 전개 연산자와 똑같이 당연히 결과 보면 false
💁‍♀️ 값은 같지만 false 나오는건 "주소 값이 다르게 때문"

얕은 복사할 때 전개 연산자와 Object.assign( ) 사용할 수 있음
소스 코드를 보면 전개 연산자 코드가 더 깔끔( 주로 이걸 많이 사용 )✨

dArray[4].push(8)을 했는데 문제는
이전의 넣어주기 전에도 다 8이 들어가버림😬
이후엔 당연히 넣어줬으니 들어가는게 맞음⭕

◽ push 메소드➡원본을 변경해버림✔

아무리 ...cArray 이런식으로 했더라도
원본까지 다 변경 하는 걸 볼 수 있음👀

⚡Array.from( )으로 얕은 복사

👉 유사 배열이나 iterable 객체를 새 배열로 복사

// 원본 배열 ( 중첩 배열 )
const original = [[1, 2], [3, 4]];
// Array.from으로 얕은 복사 ( 겉 배열만 복사됨 )
const copy = Array.from(original);

// 복사한 배열의 내부 배열 값 수정
copy[0][0] = 999;

// 원본도 바뀜! → 내부 배열은 같은 객체를 가리킴
console.log(original); // ✅ [[999, 2], [3, 4]]
console.log(copy);     // ✅ [[999, 2], [3, 4]]

JS에서 Array.from(original)처럼 얕은 복사를 하면…
"바깥 배열은 새로 만들지만, 안에 든 참조형(객체, 배열 등)은
기존 걸 그대로 복사해와서 같은 걸 가리킴!"

⚡slice( )로 얕은 복사

✂ 배열의 일부를 잘라서 새 배열 반환🔁

// 원본 배열 (중첩 배열)
const original = [[1, 2], [3, 4]];

// slice()로 얕은 복사 (겉 배열만 복사)
const copy = original.slice();

// 내부 배열 수정
copy[1][1] = 888;

// 원본도 바뀜 → 내부는 여전히 같은 객체!
console.log(original[1][1]); // 888

🧊얕은 동결( Object.freeze )

👉 겉(1단계)만 못 바꾸게 막는 것! 속(깊은 객체)은 여전히 변경 가능

Object.freeze 메소드➡객체를 동결함
❌동결된 객체는 더 이상 변경 불가❌

const aObject = {
  "a": "a",
  "b": "b",
  "cObject": { "a": 1, "b": 2 }
}

Object.freeze(aObject);
aOject.a = "c";
console.log('aObject', aObject);

c로 억지로 바꾸려는데 변경 되지 않음( 동결 시켰기 떄문 )

✅ 얕은 동결이기에 "a": "a", "b": "b" 이부분만 동결된 것

그럼 이부분을 바꾸려고 하면 어떻게 될까❓

이전의 것도 이후의 것도 다 변경된 걸 볼 수 있음

얕은 복사나 동결이나 둘 다 중첩된 부분에선
제대로 된 역할을 수행하지 못 함🙅‍♀️

💁‍♀️얕은 복사➡"depth 복사 불가능"❌

겉(1단계)만 복사되고,
안쪽 객체/배열은 같은 걸 공유하기에 내부 수정 시 원본도 바뀜

🤷‍♀️depth란❓

const obj = {
  a: 1,             // depth 1
  b: {
    c: 2,           // depth 2
    d: {
      e: 3          // depth 3
    }
  }
};

✅ 객체나 배열이 얼마나 깊게 중첩되었는지를 말함

✅ 왜 중요한가❓➡얕은 복사는 depth 1까지만 복사함
더 깊은 건 복사하지 않고, 주소만 복사(참조)

🔹 깊은 비교 Deep Equality❓

🔍객체 안의 모든 속성과 값을 재귀적으로
하나하나 비교해서 진짜 값이 같은지 확인하는 거👀

Object depth 깊지 않은 경우 : JSON.stringigy() 사용
Object depth 깊은 경우 : lodash 라이브러리의 isEqual() 사용

🔥방법 1 - JSON.stringify

const aObject = {
  "a": "a",
  "b": "b",
  "cObject": { "a": 1, "b": 2 }
}

// 깊은 복사
const newAObject = JSON.parse(JSON.stringify(aObject));
console.log('aObject', aObject);
console.log('aObject', newAOject);

aObject를 JSON.stringify 문자열로 바꾸고( 예: { a: 1 } → "{"a":1}" )
객체로 다시 변경해주기 위해 JSON.parse 사용한 것⭕

둘 다 똑같이 나오는 걸 볼 수 있음

const aObject = {
  "a": "a",
  "b": "b",
  "cObject": { "a": 1, "b": 2 }
}

Object.freeze(aObject);  // 동결

// 깊은 복사
const newAObject = JSON.parse(JSON.stringify(aObject));
console.log('aObject', aObject);
console.log('aObject', newAOject);
aObject.cObject.a = 3; // 1을 3으로 바꿔줌
console.log('aObject', aObject);
console.log('aObject', newAOject);

아까 얕은 동결했을 땐 안 됐는데
변경 하기 전 후를 출력해보면👇

변경 전은 안 바뀌고 후는 바뀐 걸 볼 수 있음

깊은 복사➡객체 안의 중첩된 값들까지 새로 복사하는 거🤓
👉 그래서 원본을 바꿔도 복사본은 안 바뀜✔

const obj = {
  a: 1,
  b: { inner: 2 }
};

Object.freeze(obj);   // 얕은 동결

obj.a = 999;          // ❌ 실패 (막힘)
obj.b.inner = 999;    // ✅ 성공 (안쪽은 안 막힘!)

즉, 겉 객체는 수정 불가❌ 안쪽 객체는 여전히 수정 가능⭕

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };

// 얕은 비교
console.log(obj1 === obj2); // false

// 깊은 비교 (방법 1 - JSON.stringify)
console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true

⚠️ 단점 : JSON.stringify( )는 순서가 다르거나
undefined, 함수, 순환 참조가 있으면 실패함 😥

  • JSON : 데이터를 문자열로 표현하는 포맷 ( 예: { "a": 1 } )
  • stringify : 객체를 JSON 문자열로 바꿔주는 함수 ( JSON.stringify(obj) )

🔥방법 2 - Lodash의 isEqual 함수 사용

📚 JS 편하게 쓰라고 만든 유틸 함수 모음 라이브러리
( 예 - 깊은 비교, 배열 조작 등 )

npm install lodash
import _ from "lodash";

const obj1 = { a: 1, b: 2 };
const obj2 = { a: 1, b: 2 };

console.log(_.isEqual(obj1, obj2)); // true

🔥방법 3 - structuredClone

중첩 객체까지 포함해서 깊은 복사 해주는 브라우저 내장 함수

const mushrooms1 = {
  amanita: ["muscaria", "virosa"],
};

const mushrooms2 = structuredClone(mushrooms1);

mushrooms2.amanita.push("pantherina");
mushrooms1.amanita.pop();

console.log(mushrooms2.amanita);  // ["muscaria", "virosa", "pantherina"]
console.log(mushrooms1.amanita);  // ["muscaria"]

왜 쓰는지❓
👉 기존엔 깊은 복사하려면
JSON.parse(JSON.stringify(obj))처럼 불편했는데
함수나 undefined, Date, Map, Set 같은 건 복사❌
structuredClone()은 이걸 깔끔하게 해결해줌🧙‍♀️


🐱‍💻요약

원시 타입➡값 자체가 Call Stack에 저장되고
참조 타입➡값이 Heap에, 그 참조(주소)가 Call Stack에 저장😎

둘 다 { a: 1 }이지만 Heap엔 서로 다른 객체가 생기고
Call Stack엔 각각 다른 주소(참조값)가 저장됨
👉 === 비교하면 false가 나오는 것

그래서 이걸 깊은 비교로 억지로 해서
실제 값을 비교할 때 깊은 비교를 하게 함

얕은 복사할 땐 사용할 수 있는건 주로 '스프레드 오퍼레이터( ... )'
그리고 'Object.assign( )'도 사용할 수 있음🙆‍♀️

여기까진 어떤 걸 사용해서 얕은 복사 할 수 있는지 해본거임🧐

얕은 복사를 해도 depth가 있는 부분은
얕은 복사가 안된다 라는 걸 알려준 부분👌

🤔얕은 복사가 안된 부분을 바꿨을 때 어떤 일이 일어났지❓
👉 원본 배열이나 원본 객체 부분도 depth 부분은 같이 변경돼버림

aObject 동결시켰는데 첫번째 depth 변경하려니 변경 안 됐음🙅‍♀️

⭕얕은 복사나 얕은 동결이나 첫번째 depth에만 영향줌⭕

depth가 있는 부분까지 효과를 주고 싶다면 깊은 복사를 해주면 됨

😎JSON.parse(JSON.stringify 이용해서 깊은 복사 해주면 됨❗

JSON 말고 다른 걸로 했으면 좋겠다고 하면
...스프레드 오퍼레이터를 첫번째 depth에 써버리고
두번째 depth에도 써버리기➡얕은 복사하고 그담 depth에도
얕은 복사를 하면 결국엔 모든 depth가 복사 된 것⚡

depth가 많은데 어떻게 다 ...스프레드 오퍼레이터 사용하냐 싶으면
📚 lodash 라이브러리 사용하기 👇

lodash에 엄청나게 많은 메소드들이 있음

라이브러리 사용하고 싶지 않다면 전역 API인 structuredClone 이용하기👇

profile
안녕하세요! 퍼블리싱 & 프론트엔드 개발 공부 블로그 입니다!

0개의 댓글