관련 지식
#javascript #array

유사배열?

  1. var arr1 = ['aaa', 'bbb', 'ccc'];
  2. var arr2 = document.body.children;
  3. console.log(arr1);
  4. console.log(arr2);

브라우저에서 위의 코드를 실행해보면 arr2의 출력결과는 대부분 다르게 나오겠지만 아래와 비슷한 형태로 출력될 것입니다.

  1. > (3) ["aaa", "bbb", "ccc"]
  2. > HTMLCollection(10) [header.main-header, aside.main-sidebar, div.wrapper, div#playerArea, iframe#history, script, div.ax5-ui-toast-container.top-right, div.ax5-ui-toast-container.top-right, div.ax5-ui-toast-container.top-right, div#syno-nsc-ext-comp-1008.x-tip-invalid, playerArea: div#playerArea, history: iframe#history, syno-nsc-ext-comp-1008: div#syno-nsc-ext-comp-1008.x-tip-invalid]

둘다 배열값으로 보이지만 arr1은 배열이고 arr2는 유사배열 입니다. 데이터로는 구분이 어렵지만 각 변수의 prototype 을 확인해보면 배열에서 제공하는 concat()push() 함수등이 없는것을 볼수 있습니다.

  1. console.log(arr1.__proto__.push); //정의된 항목이 있음
  2. console.log(arr2.__proto__.push); //정의된 항목이 없음

Array 객체의 isArray() 함수를 이용해서도 확인이 가능합니다. 함수 이름처럼 배열이 맞으면 true를 리턴합니다. 아래 샘플을 실행해 보면 arr1 은 배열이고 arr2 는 배열이 아니라는 것을 확인할 수 있습니다.

  1. console.log(Array.isArray(arr1)); //true 출력
  2. console.log(Array.isArray(arr2)); //false 출력

이처럼 배열의 형태를 가지고 있지만 배열이 아닌것, 그것을 유사배열이라고 합니다.

유사배열을 어디서 사용하나

유사 배열이 이용되는 대표적인 사례는 arguments 객체 입니다.(args 는 배열)

  1. function nonArray(...args) {
  2. console.log(arguments);
  3. console.log(Array.isArray(arguments)); //유사배열
  4. console.log(args);
  5. console.log(Array.isArray(args)); //배열
  6. }
  7. nonArray('a', 'b', 'c');
  8. nonArray('a', 'b', 'c', 'd');

따라서 고정되지 않은 갯수의 파라미터를 처라하기 위해 arguments를 객체를 사용할 경우, 그것이 배열이 아니라는 것을 모른다면 난감한 상황을 마주할 수도 있을 것입니다.

또한 JQuery 에서도 유사 배열을 사용하고 있습니다. ‘$(“li”)’ 를 이용해 li 태그의 목록을 뽑아서 위에서 사용한 방법으로 배열인지 확인해보시면 됩니다.

  1. > $("li")
  2. > init(487) [li.on, li, li, li, li, li, li.on, li, li, li, li, li, li, li, li, li, li, li.hBox, li, li, li, li, li, li.hBox, li, li, li, li, li, li, li, li, li, li, li, li, li, li.hBox, li, li, li, li, li.hBox, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li, li.hBox, li, li, li, li, li, li, li, li, li, li, li, li, li.hBox, li, li, li, li, li, li, li, li, li.hBox, li, li, li, li, li, li, li, li, li, li, …]

유사배열 만드는 법

유사배열은 객체의 호출 방법을 이용한 트릭으로 만들수 있습니다. 먼저 아래 예제를 보겠습니다.

  1. var ex = {
  2. key1 : '첫번째',
  3. key2 : '두번째',
  4. key3 : '세번째'
  5. };
  6. console.log(ex);
  7. console.log(ex.key1);

key1을 사용하기 위해서 위 방법뿐 아니라 아래 형태로도 호출 가능합니다.

  1. var ex = {
  2. key1 : '첫번째',
  3. key2 : '두번째',
  4. key3 : '세번째'
  5. };
  6. console.log(ex);
  7. console.log(ex.key1);
  8. console.log(ex['key1']); //호출 가능

만약 ex의 각 키가 숫자였다면 어떻게 될까요?

  1. var ex = {
  2. 0 : '첫번째',
  3. 1 : '두번째',
  4. 2 : '세번째'
  5. };
  6. console.log(ex);
  7. //console.log(ex.0); <- 이 형태는 사용불가
  8. console.log(ex[0]);

키 이름 0 을 호출하는 방식이 배열 값을 사용하는것과 동일해보이지 않나요? 즉 키 이름을 숫자로 지정하면 유사배열을 만들수 있습니다. 그런데 배열이라고 하기엔 뭔가 부족해 보이네요. 당연히 length 속성도 있어야 할 것입니다.

  1. var ex = {
  2. 0 : '첫번째',
  3. 1 : '두번째',
  4. 2 : '세번째',
  5. length : 3
  6. };
  7. console.log(ex); //배열처럼 보이지 않음
  8. for(var i = 0; i < ex.length; i++) { //배열처럼 사용할 수 있음
  9. console.log(ex[i]);
  10. }

여기까지만 해도 배열처럼 사용할수 있지만 콘솔에서 호출해보면 [] 형태로 보이지 않기 때문에 배열이 아닌것 같습니다. 배열처럼 보이게 하기 위해선 splice 속성을 만들어주어야 합니다.

  1. var ex = {
  2. 0 : '첫번째',
  3. 1 : '두번째',
  4. 2 : '세번째',
  5. length : 3,
  6. splice : function() {}
  7. };
  8. console.log(ex);

splice() 함수의 내용은 없지만 훌륭히 배열처럼 보이게 되었습니다. splice 기능을 동작하도록 하고 싶다면 아래처럼 할수도 있습니다.

  1. var ex = {
  2. 0 : '첫번째',
  3. 1 : '두번째',
  4. 2 : '세번째',
  5. length : 3,
  6. splice : [].splice
  7. };
  8. console.log(ex);

만약 ‘var arr = new Array();’ 와 비슷한 모양을 하는 형태로 만들고자 한다면 아래처럼 작성할 수도 있습니다. splice() 함수가 prototype으로 만들어지기 때문에 객체값에서 보이지 않는것에 주목하자. 그리고 위에서 사용하는 방식보단 이번 예제 형태로 만드는게 더 좋습니다.

  1. function Arr() {
  2. this[0] = '하나요';
  3. this[1] = '둘이요';
  4. this[2] = '셋이요';
  5. this.length = 3;
  6. }
  7. Arr.prototype.splice = [].splice;
  8. var ex = new Arr();
  9. console.log(ex);

정리

유사배열은 왜 사용할까요? 이유는 간단합니다. 어떤 함수에서 실행 결과로 배열값을 돌려주고 싶을때, 원래의 배열 객체가 가지고 있는 기능(함수)를 제공하고 싶지 않거나 원래의 배열 객체에 없는 기능을 제공하고 싶을때 유사배열을 사용합니다. 즉 원래의 Array 와 다른 배열을 사용하고 싶을때 유사배열을 이용하는 것이죠.

JQuery에서 리턴해주는 값을 생각해 보겠습니다. 기존 배열에는 없는 addClass() 함수 같은 것이 있죠. 만약 유사배열을 사용하지 않고 addClass() 함수를 넣고 싶었다면 아래와 같이 추가했을 것입니다.

  1. Array.prototype.addClass = function() {
  2. ...
  3. };

그러나 위 형태는 Array 에 직접 추가되므로 addClass() 함수와 전혀 관련이 없는 배열에서도 마치 사용가능한 함수처럼 노출 될 것입니다. 동작은 제대로 할 수 있을까요? 유사 배열을 사용해야 하는 이유입니다.

그 외의 함수

유사배열은 forEach()push()concat() 등의 함수가 없기 때문에 사용할 수 없습니다. 따라서 그러한 기능이 필요할 경우 상황에 맞는 처리가 필요한데, 예를들어 forEach() 함수의 경우 아래와 같은 방법으로 Array 객체의 함수를 사용할수 있습니다.

  1. var ex = {
  2. 0 : '첫번째',
  3. 1 : '두번째',
  4. 2 : '세번째',
  5. length : 3,
  6. splice : [].splice
  7. };
  8. console.log(ex);
  9. [].forEach.call(ex, function(val) {
  10. console.log(val);
  11. });

push() 함수도 가능합니다.

  1. var ex = {
  2. 0 : '첫번째',
  3. 1 : '두번째',
  4. 2 : '세번째',
  5. length : 3,
  6. splice : [].splice
  7. };
  8. [].push.call(ex, "네번째");
  9. console.log(ex);

prototype을 이용한다면 아래처럼 추가도 가능합니다.

  1. function Arr() {
  2. this[0] = '하나요';
  3. this[1] = '둘이요';
  4. this[2] = '셋이요';
  5. this.length = 3;
  6. }
  7. Arr.prototype.splice = [].splice;
  8. Arr.prototype.forEach = [].forEach;
  9. Arr.prototype.push = [].push;
  10. var ex = new Arr();
  11. console.log(ex);
  12. ex.forEach(function(a, b) { console.log(a); });
  13. ex.push('ddd');
  14. ex.forEach(function(a, b) { console.log(a); });

모든 함수가 위 처럼 기존 배열의 함수를 사용할수 있는 것은 아닙니다. 그럴때는 직접 기능을 구현해야 합니다.