MDN에서 Array.prototype.slice() 스펙을 확인하다가 모순처럼 느껴지는 문장을 발견했다.
slice() 메서드는 어떤 배열의 begin 부터 end 까지(end 미포함)에 대한 얕은 복사본을 새로운 배열 객체로 반환합니다. 원본 배열은 바뀌지 않습니다.
얕은 복사는 원본과 복사본이 같은 객체 데이터 다발 주소를 참조하는 것을 뜻한다. 그렇다면 Array.slice
를 통해 반환된 객체 또한 원본과 같은 객체 주소를 참조하고 있어야 하는데, 새로운 배열 객체가 반환된다니?
스택오버플로우에도 나와 같은 의문을 느낀 한국인이 있었다.
Does Javascript slice method return a shallow copy? 라는 글이다. (먼저 질문해 주셔서 정말 감사합니다. 당신은 최고입니다!) 여기에서 긍정적 평가를 가장 많이 받은 답변을 읽어보았다. 그랬더니 한국어로 볼 때에는 와닿지 않았던 행간의 의미를 읽을 수 있었다.
Naresh Kumar의 답변
slice does not alter the original array. It returns a shallow copy of elements from the original array.
그의 답변에 따르면 Array.slice
는 원본 배열의 각 요소의 얕은 복사본을 가진 새로운 배열 객체를 반환한다. 케이스는 다르나 내용물은 같은 것이다. 이 때문에 원본 배열의 각 요소가 원시 자료형이냐 참조 자료형이냐에 따라 개발자의 의도와 다르게 동작하는 문제가 발생할 수 있다.
원본 배열의 요소가 원시 자료형인 경우 원본에서 수정이 일어나더라도 복사본에서 수정이 일어나지는 않는다. 그 반대의 경우도 마찬가지다. 값 자체의 복사가 일어나기 때문이다.
원본 배열의 어떤 요소가 참조 자료형인 경우에는 원본이나 복사본 중 어느 하나에서 수정이 일어나면 다른 하나에도 영향을 미친다. 예시를 보며 설명하겠다.
const arr = [1, 2, [3, 4]]; // arr[2]는 참조자료형이다.
const copied = arr.slice();
console.log(arr === copied); // false (1)
console.log(arr[2] === copied[2]); // true (2)
우리는 (1)을 통해 arr와 그 복사본인 copied가 다른 객체임을 알 수 있다. (= 케이스는 다르다)
그러나 각각의 객체 안에 담긴 요소는 같은 요소이다. 따라서 (2)에서는 true라는 결과가 나온다. 만약 Array.slice
가 원본 배열의 각 요소의 깊은 복사본을 가진 새로운 객체를 반환하는 메소드였다면 false 가 나왔을 것이다.
JSON.stringify()
와 JSON.parse()
를 활용해야 한다.