[JS] Copy by Value, Reference

wookhyung·2022년 8월 11일

JavaScript

목록 보기
1/1

코딩 테스트 관련한 글은 지금까지 블로깅했던 적은 없는데, DFS/BFS 문제를 풀다가 Array.fill 함수에 대해 새롭게 알게 된 점이 있어 기록할겸 남긴다.

🤔 Array.fill()?

Array.fill 함수는 MDN 설명에 따르면, 배열의 시작 인덱스부터 끝 인덱스의 이전까지 정적인 값 하나로 채운다.

const array1 = [1, 2, 3, 4];

// fill with 0 from position 2 until position 4
console.log(array1.fill(0, 2, 4));
// expected output: [1, 2, 0, 0]

// fill with 5 from position 1
console.log(array1.fill(5, 1));
// expected output: [1, 5, 5, 5]

console.log(array1.fill(6));
// expected output: [6, 6, 6, 6]

보통은 코딩테스트 문제를 풀 때, false나 0으로 특정 길이만큼 채워진 배열을 만들고 그 배열을 돌면서 true 혹은 특정 값을 넣고 싶을 때가 있는데 그럴 때 주로 fill 함수를 사용하곤 했다.

오늘 푼 문제는 백준 1260번, DFS와 BFS라는 문제였는데 문제 풀이를 위한 블로깅은 아니니까 본론으로 빠르게 넘어가서 코드의 일부분을 먼저 보자.

const paths = [[1, 2], [1, 3], [1, 4], [2, 4], [3, 4]]

const graph = new Array(N + 1).fill([]);
for (const [from, to] of paths) {
    graph[from].push(to);
    graph[to].push(from);
}

먼저, 구현하려던 것은 paths라는 2차원 배열을 graph라는 2차원 배열로 원하는 형태로 가공하는 것이였는
데 코드에서 보다시피 N + 1 길이만큼의 빈 배열을 만들고 그 안에 각각의 원소에 다시 빈 배열을 넣어 2차원 배열을 만들었다. 그러고 나서 paths 배열을 돌면서 key, value 각각을 graph 배열에 넣어준다.

그래서 원하던 결과는.. [ [], [ 2, 3, 4 ], [ 1, 4 ], [ 1, 4 ], [ 1, 2, 3 ] ] 이런 형태의 배열이 결과값으로 나올 거라고 생각했지만 생각과 달리 이상한 결과가 나왔다.

[[ 2, 1, 3, 1, 4, 1, 4, 2, 4, 3 ], 
 [ 2, 1, 3, 1, 4, 1, 4, 2, 4, 3 ], 
 [ 2, 1, 3, 1, 4, 1, 4, 2, 4, 3 ],
  ...
]

누구세요..?

왜 저런 결과값이 나왔는지 과정이 궁금해서, for문을 돌 때마다 console을 찍어서 확인해봤는데 뭔가 값이 들어가긴 들어가는데 graph[from], graph[to] 이렇게 지정한 배열에만 push가 되는 것이 아니라 모든 배열에 push가 되고 있었다.

뭔가.. 변수 자체를 잘못 넘겨줬나? 생각했지만 그것도 아니였고, 아래처럼 변수없이 따로 코드를 돌려 본 결과 모든 배열에 push가 되고 있는게 맞았다.

const arr = new Array(3).fill([]);
// arr = [[], [], []]

arr[0].push(1)

console.log(arr);
// [ [1], [1], [1] ] => ?

왜 특정 인덱스를 지정하는데 모든 배열에 값이 들어가는지 의문이 생겼고 빈 2차원 배열을 fill을 통해서 만들지 않고 직접 만들어서 반복문을 돌려봤다.

const arr = [[], [], []]

for (const [from, to] of paths] {
 	arr[from].push(to);
    arr[to].push(from);
}
     
console.log(arr);
// [ [], [ 2, 3, 4 ], [ 1, 4 ], [ 1, 4 ], [ 1, 2, 3 ] ]

그랬더니, 원하는 결과값이 제대로 나왔다. 여기서 뭔가 Array.fill() 함수를 통해 만든 배열이 뭔가 다른 점이 있는 것 같다는 생각이 들어서 구글링을 해봤다.

찾아본 결과, fill 함수의 다른 점이 있었는데 MDN 문서에 따르면 fill 메서드는 변경자 메서드로, 복사본이 아니라 this 객체를 변형해 반환하며 value에 객체를 받을 경우 그 참조만 복사해서 배열을 채웁니다. 라고 되어있다.

이해한대로 정리해보면 value에 객체를 받을 경우 각각 다른 객체가 만들어지는게 아니라, 처음 value에 들어간 객체를 참조하여 복사하고, 복사된 객체들로 배열을 채운다는 것 같다. 그래서 내가 원하는 결과가 나오지 않고, 각 배열들이 같은 메모리 주소를 참조하여 모두 값을 공유하는 것이었다.

왜 이런 현상이 나타날까? 이를 이해하기 위해서는 Copy By Value, Copy By Reference라는 개념을 알아야 한다.

📎 Copy By Value, Copy By Reference

먼저, 자바스크립트의 데이터 타입은 크게 두 종류로 나뉘는데, 원시 데이터 타입(number, string, boolean, null, undefined 등)과 객체 데이터 타입(object, array, function)이다.

여기서 객체와 원시 타입의 근본적인 차이 중 하나는 객체는 '참조에 의해(by reference)' 저장되고 복사된다는 것이다. 반면에, 원시값은 '값 그대로' 저장, 할당되고 복사된다.

1️⃣ 그래서 Copy By Value가 뭔데?

Copy By Value는 복사할 대상을 새롭게 만든 변수에 그 값 자체를 할당한다라는 뜻이다.

let a = "Hello!"; 
let b = a;
a = "Bye!";

console.log(a); // Bye!
console.log(b); // Hello!

b 변수에 a의 값을 할당한 뒤에, a의 값을 변경하였음에도 서로 영향을 끼치지 않는다. 그 이유는 a와 b는 원시값이므로 메모리가 각 변수에 할당되어 a의 메모리 주소와 b의 메모리 주소가 다르기 때문이다.

2️⃣ 그렇다면, Copy By Reference란?

Copy By Reference는 새롭게 만든 변수에 복사할 대상이 참조하고 있는 메모리 주소값을 할당한다라는 뜻이다.

let user = {
  'name': "John"
}

let admin = user;

user.name = "Pete";

console.log(user.name); // 3
console.log(admin.name); // 3 

왜 위와 같은 결과가 나오게 될까?

왜냐하면, a와 b 두 변수는 객체 데이터 타입이기 때문에 참조에 의한 복사가 발생하기 때문이다. 내부적으로 같은 메모리 주소를 바라보고 있기 때문에 두 변수는 객체를 공유하고 있는 것이다.

일반적으로 값을 복사한다고 생각했을 때 '값 그대로' 복사하는 원시 데이터 타입과 달리, 객체 데이터 타입은 복사할 때 내부적으로 주소 값을 공유하는 원리를 가지고 있기 때문에 이런 결과가 발생하는 것이다.

아래의 예시를 보면, 이해하는데 더 도움이 될 것 같다.

let a = {};
let b = a; // 참조에 의한 복사

alert( a == b ); // true, 두 변수는 같은 객체를 참조합니다.
alert( a === b ); // true

두 변수가 같은 객체를 참조하고 있기 때문에 일치·동등 비교 모두에서 참이 반환된다.

let a = {};
let b = {}; // 독립된 두 객체

alert( a == b ); // false

두 변수가 비어있다는 객체라는 점에서 같아보이지만 서로 독립된 객체이기 때문에 비교연산자를 사용해보면 false가 반환된다.


참고

https://taesung1993.tistory.com/33
https://han7096.medium.com/javascript-copy-by-reference-%EC%99%80-copy-by-value-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-9f09c9982a1
https://ko.javascript.info/object-copy

0개의 댓글