📝 Array.prototype.map 명세
arr.map(callback(currentValue[, index[, array]])[, thisArg])
매개변수
1. callback
새로운 배열 요소를 생성하는 함수
1. currentValue
현재 순회중인 배열의 요소
2. index
현재 배열 요소의 인덱스
3. array
map()을 호출한 배열
2. thisArg (생략가능)
callback 함수가 실행될 때 바인딩 될 this 값
반환값
배열의 각 요소에 대해 실행한 callback 함수의 결과를 모은 새로운 배열
기타
- 희소 배열의 경우 값이 정의되지 않은 인덱스에 대해서는 callback 함수가 호출되지 않는다. (undefined가 할당된 인덱스는 호출된다.)
- callback 함수에 의해 원본 배열이 변형될 때, callback 함수에 전달되는 배열 요소의 값은 map이 방문하는 시점의 값이다.
- 배열의 중간이 비어 있는 경우(희소 배열), 반환값 또한 동일한 인덱스를 빈 값으로 유지한다.
- 첫 callback 함수 호출 전에 map이 처리할 배열 요소의 범위가 결정된다.
- map 시작 이후 배열에 추가된 배열 요소에 대해서 callback 함수가 호출되지 않는다.
- map 시작 이후 배열 요소가 변경되었다면 callback 함수가 호출되는 시점에서의 값이 전달된다.
- map 시작 이후 삭제된 배열 요소는 아직 순회하지 않았다면 해당 요소에 대해 callback 함수가 호출되지 않는다.
💻️ 구현
const myMap = function (callback, thisArg) {
const array = [];
for (const key in this) {
const intKey = Number(key);
if (Number.isInteger(intKey)) {
array[key] = callback.bind(thisArg)(this[key], intKey, this);
}
}
return array;
};
Array.prototype.myMap = myMap;
myMap 내부의 this는 myMap 메서드를 호출한 배열을 가리킨다.
for in 문으로 배열 객체의 key를 순회하며 array[key]에 callback 함수의 반환값을 할당한다.
이 때, for in 문은 프로토타입 체인 상에 있는 모든 enumerable 어트리뷰트가 true인 프로퍼티를 순회하므로, 반드시 정수인 key에 대해서만 callback 함수를 실행해야 한다.
callback 함수는 호출 전에 Function.prototype.bind로 인자로 받은 thisArg로 this 바인딩을 해준다.
callback 함수의 인자로 배열의 value, index, 배열의 참조를 넘겨준다.
이렇게 해서 만든 새로운 배열 array를 반환한다.
🧪 테스트 (Jest)
describe('Array.prototype.map 테스트', () => {
[
['value 테스트 1', v => v],
['value 테스트 2', v => typeof v],
['index 테스트 1', (_, i) => i],
['index 테스트 2', (_, i) => typeof i]
].forEach(([testName, callback]) => {
test(testName, () => {
const arr = [1, 2, 3, 4, 5];
expect(arr.myMap(callback)).toEqual(arr.map(callback));
});
});
test('array 테스트', () => {
const arr = [1, 2, 3, 4, 5];
expect(arr.myMap((_, __, array) => array).every(value => value === arr))
.toBe(true);
});
test('희소 배열 테스트', () => {
const arr = [1, , 2, , 3];
expect(arr.myMap(v => v)).toEqual(arr.map(v => v));
expect(arr.myMap((_, i) => i)).toEqual(arr.map((_, i) => i));
});
[
[
'순회 중 추가된 배열 요소 테스트',
(v, i, array) => {
array.push(0);
return v;
}
],
[
'순회 중 배열 요소 변경 테스트',
(v, i, array) => {
array[i + 2] = 0;
return v;
}
],
[
'순회 중 배열 요소 삭제 테스트',
(v, i, array) => {
delete array[i + 2];
return v;
}
]
].forEach(([testName, callback]) => {
test(testName, () => {
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [1, 2, 3, 4, 5];
expect(arr1.myMap(callback))
.toEqual(arr2.map(callback));
});
});
test('thisArg 테스트', () => {
const arr = [1, 2, 3, 4, 5];
const obj = {
sum: 0,
callback(value) {
this.sum += value;
}
};
arr.map(obj.callback, obj);
expect(obj.sum).toBe(15);
});
});
실제 Array.prototype.map의 명세대로 구현이 되었는지 확인하는 테스트 코드다.
- value 테스트와 index 테스트에서는 callback 함수 내부에 전달되는 value와 index의 값과 타입을 확인한다.
- array 테스트는 callback 함수 내부로 전달되는 array가 원본 배열을 가리키는지 확인한다.
- 희소 배열 테스트는 값이 정의되지 않은 인덱스에 대해 callback 함수가 실행되지 않는지 확인한다.
- 순회 중 추가된 배열 요소 테스트는 순회를 하면서 추가(push)된 배열 요소들에 대해 callback 함수가 실행되지 않는지 확인한다.
- 순회 중 배열 요소 변경 테스트는 순회를 하면서 배열 요소가 변경된 경우 callback 함수가 실행되는 시점의 배열 요소 값이 전달되는지 확인한다.
- 순회 중 배열 요소 삭제 테스트는 순회를 하면서 배열 요소가 삭제된 경우, 아직 순회하지 않은 요소라면 callback 함수가 실행되지 않으며, 해당 인덱스가 비어 있는지 확인한다.
- thisArg 테스트는 this 바인딩이 올바르게 되었는지 확인한다.
참고자료
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map
'연습장 > 낙서장' 카테고리의 다른 글
[React/TS] 이벤트 핸들러 함수의 타입 (0) | 2022.11.27 |
---|---|
[JavaScript] Array.prototype.splice 직접 구현하기 (0) | 2022.04.12 |