의도: 참조에 의한 접근과 값에 의한 접근을 구분할 수 있는지 확인하는 질문
팁: 객체를 참조가 아닌 값으로서 복사하는 방법도 소개하면 좋다.
나의 답안
객체, 배열, 함수 등의 객체 데이터 타입은 참조에 의해 복사됩니다.
이는 변수에 객체의 값이 직접 저장되는 것이 아니라, 객체가 저장된 메모리 주소(참조)가 저장되는 것입니다.
객체를 다른 변수에 할당하거나 함수에 전달할 때, 메모리 주소(참조)가 복사되므로 두 변수는 동일한 객체를 가리키게 됩니다.
복사된 변수는 같은 메모리 주소를 참조하므로, 한 쪽의 변경이 다른 쪽에도 영향을 미칠 수 있습니다.
따라서 불변성을 유지하거나 깊은 복사를 사용하여 원본 데이터를 보호하는 것이 중요합니다.
깊은 복사는 객체의 모든 레벨을 복사하여, 원본 객체와 복사된 객체가 완전히 독립적이게 만드는 방법입니다.
가장 단순한 깊은 복사 방법은 JSON 방법입니다.
JSON.stringify()메서드를 이용하여 객체를 문자열로 변환한 후, 이를JSON.parse()메서드를 이용하여 다시 객체로 변환하여 깊은 복사를 수행합니다.
하지만 이 방법은 함수,undefined,Symbol, 순환 참조를 포함한 객체는 제대로 복사되지 않는다는 단점이 있습니다.
깊은 복사의 두 번째 방법으로는 재귀 함수를 사용하는 것입니다.
이는 깊은 복사를 직접 구현하는 방법으로, 재귀적으로 객체의 모든 속성을 복사하는 것입니다.
하지만 이 방법은 구현이 복잡하다는 단점이 있습니다.
Lodash 라이브러리를 사용하면
_.cloneDeep메서드를 사용하여 깊은 복사를 쉽게 수행할 수 있습니다.
추가적으로, 최신 JavaScript에서는structuredClone()메서드를 사용하여서도 깊은 복사를 쉽게 수행할 수 있습니다.
앞서 설명한 이 두 가지 방법이 가장 간단한 깊은 복사 방법이라서 저는 이 방법을 선호합니다.
주어진 답안 (모범 답안)
여기서 참조에 의한 복사가 일어나는 이유는 값 자체를 복사해주는 게 아니라 값이 들어 있는 메모리 주소를 복사해서 주기 때문입니다.
그렇기에 객체를 복사하게 되면 서로 같은 메모리 주소를 가리키게 되어 한쪽이 값을 바꾸면 다른 쪽이 보여주는 값도 바뀌게 되는 것입니다.
이러한 특징 때문에 여러 오류가 생기곤 합니다.
그래서 깊은 복사를 통해 객체 또한 값으로서 복사하도록 하는 방법들이 있는데 대표적으로는
JSON.stringify()를 통해 객체를 문자열로 바꾸어서 복사하는 방법이 있고, 비교적 최근에 나온structuredClone()을 사용하는 방법이 있습니다.
개인적으로는 후자가 더 짧고 쉽게 때문에 더 애용하는 편입니다.
자바스크립트에서 참조에 의한 접근과 값에 의한 접근은 데이터가 변수에 저장되고 다른 변수로 복사되거나 함수로 전달될 때의 동작 방식을 설명한다.
이 두 개념은 데이터 타입(원시 타입 vs. 객체 타입)에 따라 결정된다.
자바스크립트에서 객체를 복사하는 것은 프로그래밍에서 자주 필요하지만, 올바르게 수행하려면 객체의 특성과 복사 방식의 차이를 이해해야 한다.
객체 복사는 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)로 나눌 수 있다.
원시 타입: string, number, boolean, undefined, null, symbol
작동 방식
1) 값을 복사하여 새 변수에 전달한다.
2) 두 변수는 서로 독립적이다.
예제
let a = 10; // 원시 값 저장
let b = a; // 값 복사
b = 20; // b의 값 변경
함수 호출 시 동작
function changeValue(num) {
num = 20;
console.log("Inside function:", num); // 20
}
let x = 10;
changeValue(x);
console.log("Outside function:", x); // 10 (원본은 영향 없음)
객체 타입: Object, Array, Function
작동 방식
1) 객체의 참조값(메모리 주소)을 복사하여 새 변수에 전달한다.
2) 두 변수가 동일한 객체를 참조한다.
예제
let obj1 = { name: "Alice" }; // 객체 생성
let obj2 = obj1; // 참조 복사
obj2.name = "Bob"; // obj2를 통해 수정
console.log(obj1.name); // "Bob" (같은 객체를 참조)
console.log(obj2.name); // "Bob"
함수 호출 시 동작
function modifyObject(obj) {
obj.name = "Bob";
}
let person = { name: "Alice" };
modifyObject(person);
console.log(person.name); // "Bob" (원본 객체가 변경됨)
| 값에 의한 접근 | 참조에 의한 접근 | |
|---|---|---|
| 적용되는 데이터 타입 | 원시 타입 | 객체 타입 |
| 복사 방식 | 값 자체를 복사 | 메모리 주소를 복사 |
| 독립성 | 복사된 값은 독립적 | 복사된 참조는 동일 객체를 가리킴 |
| 원본 데이터에 미치는 영향 | 없음 | 모든 참조가 영향을 받음 |
얕은 복사는 객체의 1단계 속성만 복사한다.
즉, 객체 내부의 참조 타입(다른 객체나 배열 등) 속성은 복사되지 않고 원본 객체의 참조를 공유한다.
얕은 복사 방법
1) Object.assign() 사용
const original = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, original);
shallowCopy.b.c = 42;
console.log(original.b.c); // 42 (참조 공유)
2) 스프레드 연산자(...) 사용
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
shallowCopy.b.c = 42;
console.log(original.b.c); // 42 (참조 공유)
깊은 복사는 객체의 모든 레벨을 복사하여, 원본 객체와 복사된 객체가 완전히 독립적이게 만든다.
중첩된 객체나 배열도 별도로 복사된다.
깊은 복사 방법
1) JSON 방법 (간단하지만 한계 존재)
const original = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.b.c = 42;
console.log(original.b.c); // 2 (독립적)
undefined, Symbol, 순환 참조(Circular Reference)를 포함한 객체는 제대로 복사되지 않는다.const obj = { a: 1, b: undefined, c: Symbol('test') };
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // { a: 1 } (b와 c는 복사되지 않음)2) 재귀 함수 사용: 깊은 복사를 직접 구현하려면 재귀적으로 객체의 모든 속성을 복사해야 한다.
function deepCopy(obj) {
if (obj === null || typeof obj !== 'object') {
return obj; // 원시 값은 그대로 반환
}
const copy = Array.isArray(obj) ? [] : {}; // 배열과 객체 구분
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopy(obj[key]); // 재귀 호출로 중첩 객체 처리
}
}
return copy;
}
const original = { a: 1, b: { c: 2 } };
const deepCopyObj = deepCopy(original);
deepCopyObj.b.c = 42;
console.log(original.b.c); // 2 (독립적)
hasOwnProperty를 사용해 객체의 자체 프로퍼티만 복사한다. (상속된 프로퍼티 제외)3) Lodash 라이브러리 사용: Lodash는 강력한 유틸리티 라이브러리로, _.cloneDeep 메서드를 제공하여 깊은 복사를 쉽게 수행할 수 있다.
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(original);
deepCopy.b.c = 42;
console.log(original.b.c); // 2 (독립적)
4) 구조화 복사(Structured Clone): 최신 JavaScript에서는 structuredClone() 메서드를 사용하여 객체를 깊게 복사할 수 있다.
const original = { a: 1, b: { c: 2 } };
const deepCopy = structuredClone(original);
deepCopy.b.c = 42;
console.log(original.b.c); // 2 (독립적)
undefined 등 JSON 방식의 한계를 극복한다.| 얕은 복사 | 깊은 복사 | |
|---|---|---|
| 복사 깊이 | 1단계 속성까지만 복사 | 객체의 모든 중첩 레벨 복사 |
| 내부 객체 처리 | 참조를 공유 | 별도의 새로운 객체 생성 |
| 성능 | 빠름 | 복잡한 객체일수록 느림 |
| 예제 사용법 | Object.assign, 스프레드 연산자 | JSON, 재귀, structuredClone 등 |