배열 메서드 직접 구현
포스트
취소

배열 메서드 직접 구현

해당 포스트는 Array.prototype의 특정 메서드들을 직접 구현한 포스트입니다.

⭕ -> 원본 자체를 변경하는 메서드
❌ -> 원본을 변경하지 않는 메서드

✈️ 일반 배열 메서드들

0️⃣ ( ❌ ) Array.prototype.slice()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * ❌ "Array.prototype.slice()" 구현
 * @param { number } begin 시작 인덱스
 * @param { number } end 끝 인덱스
 * @returns { any[] } 복사된 배열
 *
 * + 조건
 * 1.1 "begin"이 배열의 길이보다 길다면 빈 배열 반환
 * 1.2 "begin"이 음수라면 뒤에서부터 시작
 * 1.3 "begin"이 "undefined"라면 "0"부터 시작
 *
 * 2.1 "end"번째 인덱스를 제외
 * 2.2 "end"가 음수라면 뒤에서부터 계산
 * 2.3 "end"가 생략되면 끝까지
 */
Array.prototype.mySlice = function (begin = 0, end = this.length) {
  const myArray = [];

  // 1.2 "begin"이 음수라면
  if (begin < 0) {
    begin = this.length + begin;
  }

  // 2.2 "end"가 음수라면
  if (end < 0) {
    end = this.length + end;
  }

  for (let index = begin; index < end; index++) {
    myArray.push(this[index]);
  }

  return myArray;
};

1️⃣ ( ⭕ ) Array.prototype.splice()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
 * ⭕ "Array.prototype.splice()" 구현
 * @param { number } start 배열의 변경을 시작할 인덱스
 * @param { number } deleteCount 배열에서 제거할 요소 수
 * @param { ...any } items 배열에 추가할 요소들
 * @return { any[] } 제거한 요소들을 담은 배열 반환 ( 제거하지 않은 경우 빈 배열 )
 *
 * + 조건
 * 1.1 "start"가 배열의 길이보다 크다면 배열의 길이
 * 1.2 "start"가 음수면 뒤에서부터 시작
 * 1.3 "start"의 음수이면서 절댓값이 배열의 길이보다 큰 경우 0
 *
 * 2.1 "deleteCount" 생략하거나 "array.length - start"보다 크면 "start"부터 모든 요소 제거
 * 2.2 "deleteCount" 0이하라면 제거하지 않으며 최소 하나의 "item"이 필요함
 */
Array.prototype.mySplice = function (start, deleteCount, ...items) {
  const deletedItems = [];
  let copyItems = [];

  // 1.2 음수라면
  if (start < 0) {
    start = this.length + start;
  }
  // 1.3 음수면서 절댓값이 배열의 길이보다 크면
  if (start < 0) {
    start = 0;
  }

  // 잘려진 뒷부분 아이템들 ( 나중에 결합 )
  copyItems = this.mySlice(start);

  // 뒷부분 잘라버리기
  this.length = start;

  // 원본 배열에 추가하기
  for (let index = 0; index < items.length; index++) {
    this.push(items[index]);
  }

  // 버러진 아이템 등록
  for (let index = 0; index < deleteCount; index++) {
    deletedItems.push(copyItems.shift());
  }

  // 원본 배열에 남은 아이템 넣기
  for (let index = 0; index < copyItems.length; index++) {
    this.push(copyItems[index]);
  }

  return deletedItems;
};

2️⃣ ( ❌ ) Array.prototype.indexOf()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * ❌ "Array.prototype.indexOf()" 구현
 * @param { any } target 인덱스를 찾고 싶은 아이템
 * @returns { number } 찾은 인덱스 ( 없으면 -1 )
 *
 * + 조건
 * 1. 특정 대상을 찾을 수 없으면 "-1"반환
 */
Array.prototype.myIndexOf = function (target) {
  let targetIndex = -1;

  for (let index = 0; index < this.length; index++) {
    if (target === this[index]) {
      targetIndex = index;
      break;
    }
  }

  return targetIndex;
};

3️⃣ ( ❌ ) Array.prototype.includes()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * ❌ "Array.prototype.includes()" 구현
 * @param { any } target 존재하는지 찾을 아이템
 * @returns { boolean } 존재 여부
 */
Array.prototype.myIncludes = function (target) {
  let isExist = false;

  for (let index = 0; index < this.length; index++) {
    if (target === this[index]) {
      isExist = true;
      break;
    }
  }

  return isExist;
};

4️⃣ ( ❌ ) Array.prototype.join()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * ❌ "Array.prototype.join()" 구현
 * @param { string } separator 구분자
 */
Array.prototype.myJoin = function (separator = ",") {
  let joinedString = "";

  for (let index = 0; index < this.length; index++) {
    if (index === this.length - 1) {
      joinedString += this[index];
    } else {
      joinedString += this[index] + separator;
    }
  }

  return joinedString;
};

5️⃣ ( ⭕ ) Array.prototype.reverse()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * ⭕ "Array.prototype.reverse()" 구현
 * @returns { any[] } 반전된 배열
 */
Array.prototype.myReverse = function () {
  for (let index = 0; index < Math.floor(this.length / 2); index++) {
    [this[index], this[this.length - 1 - index]] = [
      this[this.length - 1 - index],
      this[index],
    ];
  }

  return this;
};

6️⃣ ( ❌ ) Array.prototype.flat()

FIXME: depth에 따라서 평탄화되도록

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * ❌ "Array.prototype.flat()" 구현
 * @param { number } depth 평탄화 할 깊이
 * @returns { any[] } 평탄화된 배열
 */
Array.prototype.myFlat = function (depth = 1) {
  let copiedArray = this.mySlice();
  let tempArray = null;

  // depth 사용해야 함
  for (let index = 0; index < copiedArray.length; index++) {
    if (Array.isArray(copiedArray[index])) {
      [tempArray] = copiedArray.mySplice(index, 1);
      copiedArray = [...copiedArray, ...tempArray];
    }
  }

  return copiedArray;
};

🚀 고차 함수를 사용하는 배열 메서드들

FIXME: <empty>는 순회에서 제외하기 ( for ~ in으로 가능하긴 하지만 반복문에 사용하긴 비효율적이라고 생각함, in 생각해보기 )

0️⃣ ( ❌ ) Array.prototype.forEach()

1
2
3
4
5
6
7
8
9
10
/**
 * ❌ "Array.prototype.forEach()" 구현
 * @param { (value: any, index: number, self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { object } thisArgs 바인딩할 "this" 값
 */
Array.prototype.myForEach = function (callback, thisArgs) {
  for (let index = 0; index < this.length; index++) {
    callback.call(thisArgs || globalThis, this[index], index, this);
  }
};

1️⃣ ( ❌ ) Array.prototype.filter()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * ❌ "Array.prototype.filter()" 구현
 * @param { (value: any, index: number, self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { object } thisArgs 바인딩할 "this" 값
 * @returns { any[] } "callback"의 리턴 값이 참인 아이템을 모아서 새로운 배열로 만들어 반환
 */
Array.prototype.myFilter = function (callback, thisArgs) {
  const filteredArray = [];

  for (let index = 0; index < this.length; index++) {
    if (!callback.call(thisArgs || globalThis, this[index], index, this)) {
      continue;
    }

    filteredArray.push(this[index]);
  }

  return filteredArray;
};

2️⃣ ( ❌ ) Array.prototype.map()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * ❌ "Array.prototype.map()" 구현
 * @param { (value: any, index: number, self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { object } thisArgs 바인딩할 "this" 값
 * @returns { any[] } "callback"의 리턴들을 모아서 새로운 배열로 만들어 반환
 */
Array.prototype.myMap = function (callback, thisArgs) {
  const mappedArray = [];

  for (let index = 0; index < this.length; index++) {
    mappedArray.push(
      callback.call(thisArgs || globalThis, this[index], index, this)
    );
  }

  return mappedArray;
};

3️⃣ ( ❌ ) Array.prototype.every()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * ❌ "Array.prototype.every()" 구현
 * @param { (value: any, index: number, self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { object } thisArgs 바인딩할 "this" 값
 * @returns { boolean } 모두 참이면 true 아니면 false
 */
Array.prototype.myEvery = function (callback, thisArgs) {
  for (let index = 0; index < this.length; index++) {
    if (!callback.call(thisArgs || globalThis, this[index], index, this))
      return false;
  }

  return true;
};

4️⃣ ( ❌ ) Array.prototype.some()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * ❌ "Array.prototype.some()" 구현
 * @param { (value: any, index: number, self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { object } thisArgs 바인딩할 "this" 값
 * @returns { boolean } 하나라도 true면 true 아니면 false
 */
Array.prototype.mySome = function (callback, thisArgs) {
  for (let index = 0; index < this.length; index++) {
    if (callback.call(thisArgs || globalThis, this[index], index, this)) {
      return true;
    }
  }

  return false;
};

5️⃣ ( ❌ ) Array.prototype.find()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * ❌ "Array.prototype.find()" 구현
 * @param { (value: any, index: number, self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { object } thisArgs 바인딩할 "this" 값
 * @returns { any } 조건에 만족하는 아이템 리턴
 */
Array.prototype.myFind = function (callback, thisArgs) {
  let result = null;

  for (let index = 0; index < this.length; index++) {
    result = callback.call(thisArgs || globalThis, this[index], index, this);

    if (result) {
      return this[index];
    }
  }
};

6️⃣ ( ❌ ) Array.prototype.findIndex()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * ❌ "Array.prototype.findIndex()" 구현
 * @param { (value: any, index: number, self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { object } thisArgs 바인딩할 "this" 값
 * @returns { any } 조건에 만족하는 아이템의 인덱스 리턴
 */
Array.prototype.myFindIndex = function (callback, thisArgs) {
  let result = null;

  for (let index = 0; index < this.length; index++) {
    result = callback.call(thisArgs || globalThis, this[index], index, this);

    if (result) {
      return index;
    }
  }
};

7️⃣ ( ❌ ) Array.prototype.reduce()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * ❌ "Array.prototype.findIndex()" 구현
 * @param { (accumulator: any, currentValue: any, currentIndex: number self: any[]) => void } callback 각 인자에 맞게 호출할 콜백함수
 * @param { any } initialValue 초깃값
 * @returns { any } 모든 배열을 순회하고 누적된 값 반환
 */
Array.prototype.myReduce = function (callback, initialValue) {
  let accumulatorValue = initialValue;

  for (let index = 0; index < this.length; index++) {
    if (accumulatorValue && index === 0) {
      continue;
    }

    accumulatorValue = callback(accumulatorValue, this[index], index, this);
  }

  return accumulatorValue;
};

8️⃣ ( ⭕ ) Array.prototype.sort()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
 * ⭕ "Array.prototype.sort()" 구현
 * ( Merge Sort 사용 )
 *
 *  @param { (a, b) => number } compareFunction 0보다 작으면 내림차순, 0이면 그대로, 0보다 크면 오름차순
 */
Array.prototype.mySort = function (compareFunction) {
  const merge = (left, right, compareFunction) => {
    if (!left || !right) return [...left, ...right];

    const tempArray = [];

    // 비교 함수가 있는 경우
    if (typeof compareFunction === "function") {
      while (left.length !== 0 && right.length !== 0) {
        const isAscending = compareFunction(left[0] + "", right[0] + "");

        // 0보다 큰 경우 ( 내림차순 )
        if (isAscending > 0) {
          tempArray.push(right.shift());
        }
        // 0보다 작은 경우 ( 오름차순 )
        else if (isAscending < 0) {
          tempArray.push(left.shift());
        }
        // 0인경우 아무것도 하지 않음
        else {
          return [...tempArray, ...left, ...right];
        }
      }
    }
    // 비교 함수가 없는 경우 ( 오름차순 )
    else {
      while (left.length !== 0 && right.length !== 0) {
        // 좌측이 더 크다면 우측 값을 먼저 추가
        if (left[0] + "" > right[0] + "") {
          tempArray.push(right.shift());
        }
        // 우측이 더 크다면 좌측 값을 먼저 추가
        else {
          tempArray.push(left.shift());
        }
      }
    }

    return [...tempArray, ...left, ...right];
  };
  const mergeSort = (array, compareFunction) => {
    // 배열의 요소가 하나 남을 때까지 반으로 나눈다.
    if (array.length === 1) return array;

    const mid = array.length / 2;
    const left = array.slice(0, mid);
    const right = array.slice(mid);

    return merge(
      mergeSort(left, compareFunction),
      mergeSort(right, compareFunction),
      compareFunction
    );
  };

  let sortedArray = mergeSort(this, compareFunction);

  // 원본 변경
  this.length = 0;
  this.push(...sortedArray);
};

📮 레퍼런스

  1. MDN - Array.prototype.slice()
  2. MDN - Array.prototype.splice()
  3. MDN - Array.prototype.indexOf()
  4. MDN - Array.prototype.includes()
  5. MDN - Array.prototype.reverse()
  6. MDN - Array.prototype.flat()
  7. MDN - Array.prototype.forEach()
  8. MDN - Array.prototype.filter()
  9. MDN - Array.prototype.map()
  10. MDN - Array.prototype.every()
  11. MDN - Array.prototype.some()
  12. MDN - Array.prototype.find()
  13. MDN - Array.prototype.findIndex()
  14. MDN - Array.prototype.reduce()
  15. MDN - Array.prototype.sort()
  16. 1-blue - Merge Sort
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

babel

이펙티브 타입스크립트 3장 ( 19 ~ 27 )