[JS] Fill (Primitive vs Reference)

colki·2021년 3월 14일
1

fill()

배열의 시작 인덱스부터 끝 인덱스 이전 (끝 인덱스 자기자신은 미포함) 까지
하나의 값으로 채워주는 메서드이다.
원본배열을 직접 변경하며, 리턴값은 변형한 배열이다.

arr.fill (value [, start [, end] ] )
Array(n).fill(value)

  • value : 배열을 채울 값.
  • start : 시작인덱스, 기본값 0. 옵션
  • end : 끝인덱스, 기본값 arr.length. 옵션

감이 안 잡힌다면 다음 문제를 잠깐 확인해보자


Qu. 길이가 6이고 배열의 원소가 모두 null인 배열을 만들어보자.
const nullArray = [null, null, null, null, null, null];
대괄호로 표기하여 배열을 생성했으니 잘못된 것 없다. 
하지만 같은 값이 반복되고 있다. 뭔가 간단하게 만들고 싶은 내적욕구가 생긴다.

지금은 배열의 길이가 6밖에 안되지만, 10개를 넘어 몇 백개가 된다면??
대괄호로 쓰는 건 한계가 있다.
이럴때 fill 메서드를 사용해서 감각적이고 심플한 코드를 작성할 수 있다.
const nullArray = Array(6).fill(null);

console.log(nullArray);
// [null, null, null, null, null, null]
// 😁

fill 메서드에 대해서 간략하게 알아봤다.
그런데 value가 primitive(원시값)이냐, reference(객체)이냐에 따라 조금의 차이가 있다.




value <= Primitive

Primitive
- String
- Boolean
- Number
- Null
- Undefined
- Symbol

fill메서드를 이용할때, value에 원시값에 해당하는 데이터타입을 
넣어 단순히 일차원적인 배열을 만드는 것이라면 문제될 게 전혀 없다.
 

😃 arr.fill (value [, start [, end] ] )

(1)
const array1 = [0, 0, 0, 0].fill('a');
console.log(array1);
// ["a", "a", "a", "a"]
/* 시작인덱스 기본값 0, 끝인덱스 기본값 length이므로
[o, o, o, o].fill('a', 0, 4)와 같다. */

(2)
const array2 = [0, 0, 0, 0].fill('b', 1, 0);
console.log(array2);
// [0, 0, 0, 0]
/* 끝인덱스<시작인덱스 => 실행되지 않는다. */

(3)
const array3 = [0, 0, 0, 0].fill('b', 1);
console.log(array3);
// [0, "b", "b", "b"]
/* 끝인덱스 기본값 length이므로, 
인덱스 1부터 전부 value로 채워진다 */

(4)
const array4 = [0, 0, 0, 0].fill(10, 2, 4);
console.log(array4);
// [0, 0, 10, 10]
/* 인덱스 2부터 (4-1)까지 value값으로 채워진다. */

😃 Array(n).fill(value)


(5) 
const array5 = Array(5).fill('i');
console.log(array5);
// ["i", "i", "i", "i", "i"]

(6)
const array6 = Array(20).fill(100);
console.log(array6.length);
// 20

value <= reference


위 예제에서 value값들은 모두 원시값(primitive)들이었다.
그렇다면 객체가 value로 들어가면 결과는 똑같이 나올까?

Reference (Object)
- Object
- Array
- Function

객체리터럴, 배열리터럴을 넣어서 fill메서드를 사용해보자.

◼ 배열리터럴

*[[null], [null], [null]] 이렇게 [null]3개 들어있는 배열을 만들고 싶다. *

const array7 = Array(3).fill([null]);

console.log(array7);
// 실제 출력된 배열: [[null], [circular object Array], [circular object Array]]

// 기대한 배열: [[null], [null], [null]] 
// 😮 아니 이게 무슨 일이지? 확인해봐야겠다.

console.log(array7.length);
// 3
console.log(array7[0]);
// [null]               //?
console.log(array7[1]);
// [null]               //?
console.log(array7[2]);
// [null]               //?

🤔 보통일이 아니다. 이건 뭐지?   

MDN 을 다시 들여다보면, 다음과 같은 아리송한 문장이 있다.

value에 객체를 받을 경우참조만 복사해서 배열을 채웁니다.

[circular object Array]에 대해서 MDN을 찾아보면 다음과 같다.
(js bin에서는 찍히지만 콘솔창에서 바로 하면 [circular object Array]로 나오지는 않는다.)

The JavaScript exception "cyclic object value" occurs when object references were found in JSON.
JSON에서 객체 참조가 발견되면 자바 스크립트 예외 '순환 객체 값'이 발생한다.

객체인 배열은 값이 저장된 저장소(memory)의 위치값을 보관하는 참조방식으로 작동하는 자료형이다. [0]인덱스에는 실제값이 할당되고, 그 다음원소들은 자신과 동일한 값을 가진 [0]의 저장소 위치값을 받아온다.

즉, fill메서드로 만든 배열안의 객체들은 동일한 메모리 위치값을 가진다는 것!
그래서 main이 되는 [0]에 값을 추가하면 배열안의 모든 값들이 함께 변경된다.

/* 첫번째 요소인 배열의 2번째 인덱스에 값을 주고 확인해보았다.
나머지 2개의 요소까지 동일하게 바뀐 걸 확인할 수 있다. */

array7[0][1] = 2

console.log(array7);
// [[null, 2], [circular object Array], [circular object Array]]

console.log(array7[0]);
// [[null, 2]

console.log(array7[1]);
// [[null, 2]

console.log(array7[2]);
// [[null, 2]
const array8 = Array(3).fill([null]);

array8[0][0]=2;

console.log(array8[0]);
// [2]

console.log(array8[1]);
// [2]

console.log(array8[2]);
// [2]

그런데 아래와 같이 [0]인덱스의 값을 Array[0][0] =value로 값을 주는 게 아니라, Array[0]=value 방식으로 값을 바꾸면 [0]만 수정되는 걸 알 수 있다.

  • Array[0][0] =value => 메모리의 주소값을 먼저 지정하고 값을 주는 것이다.
  • Array[0]=value => 리터럴안의 상수를 직접 바꿔버렸기 때문에 새로운 객체가 되고 메모리 주소값을 새로 할당 받은 것이다. 다른 배열의 요소들은 이미 지정받았던 주소값이 있기 때문에 같이 변하지 않는다.
const array7 = Array(3).fill([null]);
console.log(array7);
// [[null], [circular object Array], [circular object Array]]


array7[0]=[2];

console.log(array7[0]);
// [2]

console.log(array7[1]);
// [null]

console.log(array7[2]);
// [null]

◼ 객체리터럴


const array8 = Array(3).fill({name: 'colki'});

console.log(array8);

/* Js bin:   
[
[object Object] {name: "colki"}, 
[circular object Object], 
[circular object Object]
] */

객체리터럴도 동일하게 value에 객체가 들어갔기 때문에 다른 요소들이
참조를 복사해서 주소값을 할당받은 것을 확인할 수 있다.

보다 간결하게 나오는콘솔창에서
배열리터럴에 했던 대로 값을 추가하고 또 변경하는 테스트를 해보았다.

const array8 = Array(3).fill({name: 'colki'});

** [0]의 메모리주소값에 연결된 배열안의 요소들이 다같이 변경됨 **
array8
// [{…}, {…}, {…}]
0: {name: "colki"}
1: {name: "colki"}
2: {name: "colki"}

array8[0].age = 20;

array8
// [{…}, {…}, {…}]
0: {name: "colki", age: 20}
1: {name: "colki", age: 20}
2: {name: "colki", age: 20}
length: 3
__proto__: Array(0)


array8[0].name = 'hany'

array8
// [{…}, {…}, {…}]
0: {name: "hany", age: 20}
1: {name: "hany", age: 20}
2: {name: "hany", age: 20}

** [0]만 변경 **
array8[0] = {name: "hani", gender:'female'} 

array8
// [{…}, {…}, {…}]
0: {name: "hani", gender: "female"}
1: {name: "hany", age: 20}
2: {name: "hany", age: 20}
profile
매일 성장하는 프론트엔드 개발자

1개의 댓글

comment-user-thumbnail
2021년 4월 23일

자질구레한 개념설명 없고 활용 예시가 좋네요
아주 유익하게 배워갑니다~

답글 달기