https://www.youtube.com/watch?v=R8rmfD9Y5-c&list=WL&index=29
오늘도 역시 Web Dev Simplified 의 Kyle 형님이시다.
개발자라면 JavaScript를 사용할 일이 없기는 아주 힘들 것이다.
그 중에서도 배열을 다루는 일은 기본기이면서도 아주 중요하다.
알고리즘 문제풀이를 하면서 stream의 필요성이 점점 더 크게 다가오는 시점에,
가볍게 자바스크립트에서 배열을 쉽게 다루는 메소드를 먼저 익혀보기로 했다.
영상 내용은 이해가 잘됐는데, 새로운 궁금증이 생겨서 이것 저것 시도해보고
공식문서를 참고해보며 추가로 알게된 것들을 함께 담아봤다.
아래는 공식문서 링크.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array
예제로 사용할 배열은 items 배열이다.
각 배열 요소는 이름과 가격이 담겨있다.
const items = [
{ name: 'Bike', price: 100},
{ name: 'TV', price: 200},
{ name: 'Album', price: 10},
{ name: 'Book', price: 5},
{ name: 'Phone', price: 500},
{ name: 'Computer', price: 1000},
{ name: 'Keyboard', price: 25},
];
다뤄볼 함수 목록은 다음과 같다.
filter, map, find, some, every, reduce, includes
이하는 간단한 사용 예시이다.
// 공통 사항
// 첫번째 파라미터는 필수값. 현재 처리할 요소를 지칭한다.
// 두번째, 세번째는 각각 index와 원본 array를 지칭하나 필수값은 아니다.
// 함수를 한 줄로 표현할 수 있을 경우 중괄호와 return생략 가능 ex) items.filter(item => item.price <=100);
// forEach는 잘 쓰고 있어서 생략
// 1. filter : 테스트를 통과(true/false)하는 요소들로 이뤄진 새로운 배열을 리턴
// 가격이 100 이하인 요소만 반환 요청 예시
const underHundredItems = items.filter(item => { return item.price <= 100; });
console.log(underHundredItems);
/*
underHunderedItems = [
{ name: 'Bike', price: 100},
{ name: 'Album', price: 10},
{ name: 'Book', price: 5},
{ name: 'Keyboard', price: 25}
]
*/
// 2. map : 각 요소에 대해 주어진 함수를 수행한 결과를 담은 새로운 배열을 반환
// 각 요소의 이름만 담은 배열을 반환 요청 예시
const itemNames = items.map(item => { return item.name; });
console.log(itemNames);
/*
itemNames = ['Bike', 'TV', 'Album', 'Book', 'Phone', 'Computer', 'Keyboard']
*/
// 3. find : 주어진 판별 함수를 만족하는 첫 번째 요소의 값을 반환. 없다면 undefined를 반환.
// 가격이 25 이하인 첫번째 요소 반환 요청 예시
const firstItemUnder25 = items.find(item => { return item.price <= 25; });
console.log(firstItemUnder25);
/*
firstItemUnder25 = { name: 'Album', price: 10 }
*/
// 4. some : 하나라도 테스트를 통과하면 true, 아니면 false. 배열이 비어있어도 false.
// 가격이 777인 요소가 있는지 검증 요청 예시
const hasPriceOf777 = items.some(item => { return item.price === 777 });
console.log(hasPriceOf777);
/*
false
*/
// 5. every : 전부 테스트를 통과하면 true, 아니면 false. 배열이 비어있으면 true?!
const hasNoFreeItem = items.every(item => { return item.price > 0 });
console.log(hasNoFreeItem);
/*
true;
*/
// 6. reduce : 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환
// reduce는 예외적으로 첫번째, 두번째가 필수 요소이다.
// 첫번째 파라미터 sum은 이전 배열 요소로 수행했던 함수의 결과값이 들어온다.
// reduce의 두번째 파라미터 0은 reduce 첫번째 파라미터의 함수의 첫번째 파라미터 sum의 초기값이다.
const totalPrice = items.reduce( (sum, item) => { return sum += item.price; }, 0);
console.log(totalPrice);
/*
1840
*/
// 7. includes : 배열이 특정 요소를 포함하면 true, 아니면 false
// 첫번째 파라미터로 찾을 값은 필수이고, 두번째 파라미터로 시작할 인덱스는 선택값이다
// items 배열에서 name 값만 꺼내서 새로운 배열로 만들고 그 배열에서 'Album' 문자열이 있는지 확인 예시
const hasAlbum = items.map(item => { return item.name; }).includes('Album');
console.log(hasAlbum);
/*
true
*/
여기까지의 내용은 카일 형님과 모질라 문서를 통해 요약한 각 함수의 사용법이다.
다시 한 번 요약하자면 아래와 같다. 이번엔 중괄호와 return을 생략해보았다.
const items = [
{ name: 'Bike', price: 100},
{ name: 'TV', price: 200},
{ name: 'Album', price: 10},
{ name: 'Book', price: 5},
{ name: 'Phone', price: 500},
{ name: 'Computer', price: 1000},
{ name: 'Keyboard', price: 25},
];
const underHundredItems = items.filter(item => item.price <= 100);
const itemNames = items.map(item => item.name);
const firstItemUnder25 = items.find(item => item.price <= 25);
const hasPriceOf777 = items.some(item => item.price === 777);
const hasNoFreeItem = items.every(item => item.price > 0);
const totalPrice = items.reduce( (sum, item) => sum += item.price, 0);
const hasAlbum = items.map(item => item.name).includes('Album');
그리고 이제부터는 각 함수를 사용해보며 시도해본 것들, 그리고 추가로 알게된 것들이다.
1. items 배열에서 가격이 100이 넘는 요소들에 대해서만 10% 할인을 한다면?
시나리오를 상상하고 코드를 짜봤다. filter는 테스트를 통과하는 요소들로 이뤄진 새로운 배열을 반환할 것이고,
그 새로운 배열에서 다시 map을 이용해 가격을 수정해주고자 했다.
그리고 예상하기로는 map, filter 등의 함수가 원본 배열에 영향을 주지 않는다고 했으니
이 함수를 수행해도 items 원본 배열의 가격은 바뀌지 않을 것이라 생각했다.
그런데 결과는 달랐다.
map 함수가 원본에 영향을 미치지 않는다는 것은, 배열을 순회하며 꺼냈던 값에
새로운 값을 대입하지 않았을 때만 해당하는 이야기였다.
만약 내가 i.price * 0.9 로 명령했다면, 10%할인된 가격들의 목록만 반환되었을 것이고
원본에 영향이 없었겠지만, 배열의 요소에 새로운 값을 대입했기에 이 경우는 달랐다.
어쩌면 내가 처음에 원했던 결과기도 했다.
원본 배열에서 100원이 넘는 요소들만 10%할인처리를 한 것이니까 말이다.
그러나 분명 새로운 배열로 처리하고 싶을 때가 있을 것 같았다.
2. map을 깊은 복사로 사용하기
새로운 배열을 반환한다고 했으니 배열은 새로운 주소값이겠지만,
복사되는 배열 내의 요소들은(이 경우 객체이므로) 얕은 복사로 주소값이 복사되는 것으로 생각했다.
그래서 아래와 같이 작성하여 의도대로 수행됨을 확인했다.
먼저 필터를 하고, 필터 배열을 깊은 복사를 한뒤에, 해당 배열에서 할인을 적용하는 식이었다.
3. some과 every는 빈 배열에 대한 결과값이 다르다.
직관적으로 생각했을 땐 some과 every 모두 빈 배열에 수행했을 때 결과가 false가 와야할 것 같은데...
some은 false, every는 true가 반환된다.
공식문서를 확인해보니 다음과 같이 작동에 차이가 있었다.
some은 전체를 돌며 조건에 대해 true를 반환하는 요소가 있는지 확인한다.
every는 전체를 돌며 조건에 대해 false를 반환하는 요소가 있는지 확인한다.
따라서 some은 default가 false이고 true인 요소가 하나라도 있으면 true반환.
every는 default가 true인데 false인 요소가 하나라도 있으면 false를 반환.
이를 적용해보면, 빈 배열일 경우 조건식 자체가 수행불가하여 기본값이 반환되므로
some에선 false가, every에선 true가 반환된다.
여기까지 이해하고 나니 빈 배열에 대해 수행 결과가 다름이 이해가 되었다.
아래는 예시 코드이다.
비어있을 때 some은 false, every는 true를 반환한다.
조건을 통과하는 요소가 배열 내에 일부만 존재할 경우 some은 true, every는 false를 반환한 모습이다.
4. reduce 의 초기값 설정
reduce를 배우고 나서 items 배열에서 각 객체의 price 값을 합산하여 반환하고 싶었다.
그래서 시도한 코드와 결과는 아래와 같다.
이게 뭔가 했는데.. 공식문서를 보고 원인을 알 수 있었다.
따라서 객체의 배열 items를 사용하고 있는 나의 경우, sum에 객체가 초기값이 주입되어
Object가 들어오고 그 뒤에 NUMBER 타입이 들어와도 문자열로 합산되고 있는 것이었다.
나의 경우 합산이 필요하기 때문에 초기값은 0으로 줘야했기에 reduce의 두번째 파라미터를 추가함으로써
다음과 같이 의도대로 수행되는 코드를 작성할 수 있었다.
그리고 아래와 같이 map으로 가격 배열을 새로 만든 뒤에 가격을 합산할 수도 있다.
이 경우 reduce에 제공된 배열이 모두 NUMBER이므로 정상 작동했다.
5. includes 로 배열 포함 여부 확인하기 (Primitive Type)
includes는 배열 내에 파라미터로 전달된 값이 존재하면 true 아니면 false를 반환한다.
원시값을 비교할 땐 당연히 별 문제가 없었지만,
배열 A가 배열 B를 모두 포함하는지 확인할 땐 생각처럼 작동하지 않았다.
1이 arrayB와 같은지, 2가 arrayB와 같은지.. 와 같이 비교하다보니 false가 나오는 것 같다.
생각해보니 그렇게 작동한다면 includes가 아니라 contains가 되어야할 것 같다.
그래서 더 작은 배열을 전체 순회하면서,
모든 배열 요소들이 더 큰 배열에 포함되는지를 확인하는 방식으로 의도대로 작동하게 했다.
6. includes 로 배열 포함 여부 비교하기 (Reference Type)
그 다음엔 객체 배열의 비교다.
items 배열에 filter를 사용해 새 배열을 만든 뒤, items가 새 배열을 includes하는지 확인해보자.
객체이지만 얕은 복사이기 때문에 주소값이 같아서
앞서 사용한 every와 includes의 조합으로 간단히 의도대로 성공하는 코드를 작성할 수 있었다.
그러나 만약, 주소값이 다르다면, 즉,
원본 배열의 일부 요소를 깊은 복사한 새로운 배열이 원본 배열에 포함되는지 확인한다면 어떨까?
주소값을 비교하기 때문에 얕은 복사와는 달리 false가 나온다.
그렇다면 주소값과 상관없이 내부 값을 비교하려면 어떻게 해야할까?
객체를 JSON으로 stringify하고 parse함으로써 깊은 복사를 했던 점에 착안하여,
원본 배열도 stringify하고 비교하려는 배열도 stringify한 뒤에 해당 문자열을 비교하면 될 것 같았다.
그래서 작성한 코드는 아래와 같다.
첫번째는 배열 내에 객체가 존재하는지 체크 시도해봤다. 주소값이 다르므로 당연히 false다
두번재는 배열 내에 JSON문자열화 된 객체가 존재하는지 체크시도해봤다. 역시 false다.
세번째는 배열도, 비교 대상 객체도 JSON 문자열화 한 뒤 체크시도해봤다. true가 반환됐다.
네번째는 비교대상을 객체에서 배열로 바꾸었다. 둘다 JSON 문자열화 했지만 false가 반환됐다.
다섯번째는 두 배열 모두 JSON 문자열화 하고, 작은 배열에서 every로 모든 요소가 큰 배열에 포함되는지 순회했다.
그리고 드디어 의도했던대로 배열 비교에 대해 true가 반환되었다.
아래는 확인 코드이다.
세번째 줄에서 깊은 복사 이후에 부모 배열에 포함되는지 확인을 시도해서 true가 반환됐다.
이것저것 시도해보고 내용을 정리하는데 생각보다 엄청난 시간이 소비되었다... ;ㅅ;
'JavaScript' 카테고리의 다른 글
자바스크립트 객체 파라미터 예외 처리 - 혼공 자바스크립트 57강 (0) | 2021.08.28 |
---|---|
TypeScript 첫 발 떼기 (0) | 2021.01.11 |
예약일 선택용 캘린더 구현하기 without 라이브러리 (0) | 2020.11.10 |
RMA : 취업포털에서 공고를 필터링해주는 크롬 확장 프로그램 (10) | 2020.10.24 |
24시간 간격 방문 카운트 구현 : 쿠키, 정규식, navigator.userAgent (0) | 2020.10.04 |