30 Day Vanilla JS Coding Challenge
이번 글에서는 프론트엔드 온라인 교육으로 유명한 Wesbos의 무료 코스인 Javascript30 강의에서 흥미로웠던 Javascript Array Method 다루기에 대해 소개해보겠습니다.
이렇게나 많은 Method들이 있습니다...
여러분은 Javascript 배열을 다룰 때 어떻게 하시나요? 저는 map 을 써서 배열을 순회하며 callback 함수의 return 값으로 새로운 배열을 만들어 내는 경우가 많았던 것 같습니다. 그래서 위에 그림처럼 splice나 reduce, indexOf 등의 Method 들은 책이나 MDN에서 보기만 하고 실제로 사용한 적은 별로 없었는데요. 이번 글에서는 이런 Method 들의 구체적인 활용법과 꿀팁들을 알아보도록 하겠습니다! 달려보죠~ 🏃
오늘의 작업은 브라우저 console에서 이루어집니다! (Firefox가 예쁘더라구요)
const inventors = [
{ first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 },
{ first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 },
{ first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 },
{ first: 'Marie', last: 'Curie', year: 1867, passed: 1934 },
{ first: 'Johannes', last: 'Kepler', year: 1571, passed: 1630 },
{ first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 },
{ first: 'Max', last: 'Planck', year: 1858, passed: 1947 },
{ first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 },
{ first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 },
{ first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 },
{ first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 },
{ first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 },
]
const transport = [
'car',
'car',
'truck',
'truck',
'bike',
'walk',
'car',
'van',
'bike',
'walk',
'car',
'van',
'car',
'truck',
]
const authors = [
{ name: 'Wes', year: 1988 },
{ name: 'Kait', year: 1986 },
{ name: 'Irv', year: 1970 },
{ name: 'Lux', year: 2015 },
]
const comments = [
{ text: 'Love this!', id: 523423 },
{ text: 'Super good', id: 823423 },
{ text: 'You are the best', id: 2039842 },
{ text: 'Ramen is my fav food ever', id: 123523 },
{ text: 'Nice Nice Nice!', id: 542328 },
]
위에 우리가 오늘 다뤄볼 배열 데이터가 나열되어 있습니다. 이 배열들을 가지고 하나씩 미션을 클리어해 봅시다!
흐음… 출생연도를 기준으로 필터링을 해야하니까 filter를 써야겠군요!
MDN의 설명에 따르면 filter는 배열의 각 요소를 테스트하는 함수로서 인수로 (element, index, array) 와 함께 호출될 수 있고, element 를 (새 배열에) 계속 두려면 true, 그렇지 않으면 false 를 반환합니다.
코드를 보면서 알아보죠!
const fifteenth = inventors.filter(
inventor => inventor.year >= 1500 && inventor.year < 1600
)
console.table(fifteenth)
정답은...갈릴레이와 케플러
inventors 배열의 element 객체들에서 이름과 성만 뽑아 배열을 만들면 되겠군요! 이럴땐 map이 제격이죠!
MDN의 설명에 따르면 map은 배열 내의 모든 요소 각각에 대하여 제공된 함수(callback)를 호출하고, 그 결과를 모아서, 새로운 배열을 반환합니다.
const fullNames = inventors.map(
inventor => `${inventor.first} ${inventor.last}`
)
console.log(fullNames)
ES6 템플릿 문법을 활용하면 쉽게 해결 가능!
year를 기준으로 오름차순으로 정렬하면 되겠군요! 과연 언제 쓸까 싶었던 sort를 쓰면 되겠습니다.
MDN의 설명에 따르면 sort는 정렬 순서를 정의하는 compareFunction 함수를 인자로 넘기면 다음과 같이 세 가지 경우로 나뉘어 새로운 배열을 만들지 않고 원 배열을 정렬합니다.
compareFunction(a, b)이 0 보다 작은 경우 a 를 b 보다 낮은 색인으로 정렬합니다. 즉, a 가 먼저 옵니다.compareFunction(a, b)이 0 을 반환하면 a 와 b 를 서로에 대해 변경하지 않고 모든 다른 요소에 대해 정렬합니다.compareFunction(a, b)이 0 보다 큰 경우, b 를 a 보다 낮은 인덱스로 정렬합니다. 즉, b 가 먼저 옵니다.const ordered = inventors.sort((a, b) => (a.year > b.year ? 1 : -1))
console.table(ordered)
ES6 Arrow function으로 깔끔하게 클리어!
각 발명가마다 passed에서 year를 뺀값을 다 더하면 되겠군요! 이럴 때는 바로 누적계산기로 불리는 reduce를 쓰면 편합니다.
MDN의 설명에 따르면 reduce는 왼쪽에서 오른쪽으로 이동하며 배열의 각 요소마다 누적 계산값과 함께 함수를 적용해 하나의 값으로 줄입니다.
다른 Array Method 들 보다 reduce는 조금 복잡한데요… 자세히 알아보면 다음과 같습니다.
arr.reduce(callback[, initialValue])
callback: 배열의 각 (요소) 값에 실행할 함수. 인수(argument) 4 개를 취할 수 있다.
accumulator: accumulator는 callback의 return값을 누적한다. initialValue (주어진 경우) 또는 직전의 callback 호출이 return한 누적 계산값이다.currentValue: 배열 내에서 현재 처리되고 있는 요소(element)다.currentIndex: 배열 내에서 현재 처리되고 있는 요소의 인덱스. initialValue가 주어진 경우 0 부터, 그렇지 않으면 1 부터 시작한다.array: reduce 가 호출된 배열 그 자체.initialValue: 옵션으로서, callback의 첫 호출에 첫 번째 인수로 사용하는 값이다.
참고: initialValue 가 주어지면, 콜백 함수는 인덱스 0 에서 시작하며, initialValue 가 주어지지 않은 경우, 인덱스 1 에서 시작한다.
정의만 보면 오히려 헛갈리는데, 실제로 코드를 보면서 이해하면 쉬울 것 같습니다.
// callback 함수의 인자로 accumulator와 currentValue를 넘기고 각 발명가의 수명을 누적해나간다.
// initialValue를 0으로 넘겨줘서 callback 함수는 index 0에서 시작한다.
const totalYears = inventors.reduce((total, inventor) => {
return total + (inventor.passed - inventor.year)
}, 0)
console.log(totalYears)
다 더하면 861!!
reduce가 조금 어려웠으니 한 번 더 연습해봅시다! callback 함수는 배열을 순회하면서 각 교통수단마다 ++
const transportation = transport.reduce((obj, item) => {
if (!obj[item]) obj[item] = 0
obj[item]++
return obj
}, {})
console.log(transportation)
car가 5대로 가장 많군요!
authors 배열에서 각 글쓴이마다 현재 날짜에서 year를 뺀 나이를 계산해서 19 세 이상인지 아닌지 체크하면 되겠습니다. 그런데 적어도 한 명만 있는지 확인하면 되므로 some을 쓰면 제격일 것 같습니다.
MDN의 설명에 따르면 some은 배열 내 일부 요소가 제공된 함수에 의해 구현된 테스트를 통과하는지를 체크합니다.
결론적으로, callback 함수가 배열 요소에 대해 참인(truthy) 값을 return 하는 경우 some Method 는 true를, 그렇지 않으면 false를 return합니다.
const isAdult = people.some(
person => new Date().getFullYear() - person.year >= 19
)
console.log({ isAdult })
글쓴이 중에 성인이 있습니다..!
흠… comments 배열에서 ID가 823423인 것을 찾으면 되는데 배열이 아닌 element로 반환해야 하므로 이럴 때는 filter가 아닌 find를 쓰면 되겠습니다!
MDN의 설명에 따르면, find는 해당 배열 안의 값을 하나 반환합니다. 이 때, 콜백으로 전달받은 테스트 함수가 요구하는 조건을 만족하는 값을 반환합니다. 그렇지 않으면 undefined 를 반환합니다.
const comment = comments.find(comment => comment.id === 823423)
console.log(comment)
Super good
배열에서 원소를 지우려면 어떻게 해야할까요… 기억을 더듬어보면 splice나 slice를 활용하면 될 것 같습니다. 그 전에 그 원소의 index를 알아야하니 findIndex Method 를 쓰면 되겠군요!
MDN의 설명에 따르면, findIndex는 제공된 테스트 함수를 만족하는 배열의 첫번째 요소에 대한 인덱스를 반환합니다. 그렇지 않으면 -1 이 리턴됩니다.
const index = comments.findIndex(comment => comment.id === 823423)
console.log(index)
index 1은 배열에서 2번째 값!!
그럼 이제 index를 알아냈으니 그 댓글을 지우기만 하면 되겠네요!
두 가지 방법이 있습니다.
MDN의 설명에 따르면, splice는 배열에 있는 요소를 삭제하거나 배열에 새 요소를 추가하고, 삭제된 요소들의 배열을 반환하거나 만약 아무런 요소도 삭제되지 않았으면 빈 배열을 반환합니다. 그리고 총 3 개의 인자를 받을 수 있습니다.
start
: 배열의 변경을 시작하는 인덱스입니다(초기 index : 0). 만약 배열 길이보다 길면 실제 시작 인덱스는 배열의 길이로 설정됩니다. 음수의 경우, 배열의 끝에서 부터 요소를 세어나가며 (초기 index : 1), 그 값의 절대값이 배열의 길이 보다 큰 경우 0 으로 설정됩니다.deleteCount
: 배열에서 제거를 할 요소의 수 입니다. 만약 deleteCount 가 0 의 경우, 아무런 요소도 제거되지 않습니다. 이경우, 최소한 하나의 새 요소를 특정해 주어야 합니다. 만약, deleteCount 가 start 에서 부터의 남은 요소 수 보다 많을 경우, 남은 요소를 모두 제거합니다.itemN
: 배열에 추가될 요소입니다. 만약 아무런 요소도 특정되지 않을 경우, splice()는 요소를 추가하지 않고 오직 삭제만 합니다.그럼 이제 댓글을 지워보죠!
comments.splice(index, 1)
console.log(comments)
삭-제
“엥 slice는 배열의 특정 index 기준으로 잘라서 새로운 배열로 반환하는거 아니냐?”라고 의문을 가지실 수도 있는데요…
MDN의 설명에 따르면, slice는 어떤 배열의 begin 부터 end 까지(end 는 불포함)에 대한 shallow copy 를 새로운 배열 객체로 반환합니다. 원본 배열은 수정되지 않습니다.
하지만, 이를 댓글을 삭제하는 미션에도 활용할 수 있습니다. 자 ~ 보시죠!
const newComments = [...comments.slice(0, index), ...comments.slice(index + 1)]
console.table(newComments)
ES6 spread operator(...)를 활용하면 됩니다!!
오늘은 지금까지 GraphQL 시리즈 글과 달리 아무런~ 라이브러리도 쓰지 않고 Vanilla Javascript 만 가지고 설명을 해보았는데요! 프로젝트를 할 때마다 Javascript 기본기를 잘 닦을 필요가 있다는 생각이 들어 오랜만에 기본적인 내용들을 되돌아보고 이렇게 포스팅까지 해보았습니다. 재미있게 보신 분들은 이러한 기본적인 내용 외에도 Vanilla Javascript 만으로 Native Speech Recognition을 구현해보기도 하고, 두더지 게임을 만들어보기도 하는 Javascript30 강의를 들어보면 좋을 것 같습니다!!
긴 글 읽어주셔서 감사드리며 그럼 다음에는 조금 더 실용적인 글로 찾아오도록 하겠습니다! 감사합니다 ~ 🙇