배열과 메서드 파트 링크 : https://ko.javascript.info/array-methods
배열의 맨 앞이나 끝에 요소를 추가, 제거하는 메서드는 앞서 push, pop, shift, unshift를 봤다.
위 네가지 메서드 외에 요소 추가, 제거에 관련된 메서드를 소개한다.
splice
배열에서 요소를 하나만 지우고 싶다면?
배열의 근본은 객체형이므로 객체에서 프로퍼티를 지울 때 쓰는 연산자 delete를 사용할 수 있다.
<script>
let arr = ["I", "go", "home"];
delete arr[1]; // "go"를 삭제
alert( arr[1] ); // undefined
// delete를 써서 요소를 지우고 난 후 배열
// --> arr = ["I", , "home"];
alert( arr.length ); // 3
</script>
인덱스 1의 값이 완전히 사라진 줄 알았더니 undefined 값으로 남아있어서 arr.length가 3이 된다.
delete obj.key는 key를 이용해 해당 키에 상응하는 값을 지우우므로 그 역할을 다한 것이다.
delete 메서드는 삭제된 요소가 있던 빈 공간을 나머지 요소들이 자동으로 채우게 만들어 배열의 길이를 줄여주지는 않는다.
다른 요소들이 당겨저서 삭제된 값의 자리를 채우게 하려면 메서드를 사용해야한다.
arr.splice(start) 메서드를 사용하면 요소 추가, 삭제, 교체가 모두 가능하다. 만능이다!
arr.splice(index[, deleteCount, elem1, ..., elemN])
index: splice를 적용할 첫 번째 요소를 가리키는 인덱스
deleteCount: 제거하고자 하는 요소의 개수
elem1, ..., elemN: 배열에 추가할 요소
예시)
<script>
let arr = ["I", "study", "JavaScript"];
arr.splice(1, 1); // 인덱스 1부터(인덱스1포함) 요소 한 개를 제거
alert( arr ); // ["I", "JavaScript"]
//-----------------------------------
let arr = ["I", "study", "JavaScript", "right", "now"];
// 인덱스 0,1,2 이렇게 세 개(0부터 3개)를 지우고 그 지운 자리에 "Let's"와 "dance"를 요소로 추가(=교체)
arr.splice(0, 3, "Let's", "dance");
alert( arr ) // ["Let's", "dance", "right", "now"]
</script>
1) splice는 삭제된 요소로 구성된 배열을 반환한다.
<script>
let arr = ["I", "study", "JavaScript", "right", "now"];
// 처음 두 개의 요소를 삭제함
let removed = arr.splice(0, 2);
alert( removed ); // "I", "study" <-- 삭제된 요소로 구성된 배열
</script>
2) splice 메서드의 인자 중 deleteCount를 0으로 설정하면 요소를 제거하지 않으면서 새로운 요소를 추가할 수 있다.
추가되는 요소는 시작 인덱스 뒤에 들어가는 게 아니라 그 시작 인덱스 자리에 들어간다.
따라서 [요소, 요소, 추가된 요소, 추가된 요소, 원래 시작인덱스에 있던 값] 이런 모양이 된다.
<script>
let arr = ["I", "study", "JavaScript"];
// 인덱스 2부터 0개의 요소를 삭제하고 "complex"와 "language"를 추가
arr.splice(2, 0, "complex", "language");
alert( arr ); // "I", "study", "complex", "language", "JavaScript"
</script>
3) 음수 인덱스도 사용할 수 있다.
slice 메서드 뿐만 아니라 배열 관련 메서드에서는 음수 인덱스를 사용할 수 있다. 이때 마이너스 부호 앞의 숫자는 배열 끝에서부터 센 요소 위치를 나타낸다.
<script>
let arr = [1, 2, 5];
// 인덱스 -1부터 (배열 끝에서부터 첫 번째 요소) 0개의 요소를 삭제하고
// 3, 4를 요소로 추가
arr.splice(-1, 0, 3, 4);
alert( arr ); // 1,2,3,4,5
</script>
slice
arr.slice([start], [end])
splice와 slice... 매번 헷갈려한다. 이제 정말 구분할 때가 된 것 같다.
이 메서드는 start 인덱스부터 (end를 제외한) end 인덱스까지의 요소를 복사한 새로운 배열을 반환한다. start와 end는 둘 다 음수일 땐, 배열 끝에서부터의 요소 개수를 의미한다.
1) arr.slice는 문자열 메서드인 str.slice와 유사하게 동작하는데 arr.slice는 서브 배열(subarray)을 반환, str.slice는 서브 문자열(substring)을 반환 한다는 점이 다르다.
예시:
<script>
let arr = ["t", "e", "s", "t"];
alert( arr.slice(1, 3) ); // e,s (인덱스가 1인 요소부터 인덱스가 3인 요소까지 복사(인덱스가 3인 요소는 제외))
alert( arr.slice(-2) ); // s,t (인덱스가 -2인 요소부터 제일 끝 요소까지 복사)
</script>
2) arr.slice()는 인수를 하나도 넘기지 않고 호출하여 arr의 복사본을 만들 수 있다.
기존의 배열은 건드리지 않으면서 해당 배열을 조작해 새로운 배열을 만들고자 할 때 자주 사용된다.
concat
arr.concat은 기존 배열의 요소를 사용해 새로운 배열을 만들거나 기존 배열에 요소를 추가하고자 할 때 사용한다.
arr.concat(arg1, arg2...)
인수로 배열이나 값이 오고 인수 개수에는 제한이 없다.
1) arr.concat은 arr에 속한 모든 요소와 arg1, arg2 등에 속한 모든 요소를 합친 새로운 배열을 반환한다.
인수가 배열일 경우 배열의 모든 요소가 복사 되고, 단순 값인 경우는 인수가 그대로 복사된다.
<script>
let arr = [1, 2];
alert( arr.concat([3, 4]) ); // 1,2,3,4
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
</script>
이렇게 concat은 모든 걸 합쳐버린 후 배열 형태로 반환시킨다.
2) concat은 인수로 들어온 배열의 요소를 복사해 활용한다.
객체(유사배열객체 포함)가 인자로 넘어오면 객체는 분해되지 않고 통으로 복사되고 합쳐진다.
<script>
let arr = [1, 2];
let arrayLike = {
0: "something",
length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
</script>
3) 인자로 받은 유사 배열 객체의 프로퍼티 중 특수한 프로퍼티인 Symbol.isConcatSpreadable이 있으면 concat은 이 객체를 배열처럼 취급하여 객체 전체가 아니라 객체의 프로퍼티 값을 합친다.
<script>
let arr = [1, 2];
let arrayLike = {
0: "something",
1: "else",
[Symbol.isConcatSpreadable]: true,
length: 2
};
alert( arr.concat(arrayLike) );
// 1,2,something,else
</script>
arr.forEach는 주어진 함수를 배열 요소 각각에 대해 실행할 수 있게 해준다.
arr.forEach(function(item, index, array) {});
<script>
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);
//각 요소들을 모두 alert 창으로 출력함
//----------------------------------
// 아래와 같이 item, index, array 같은 인수를 가져와서 쓸 수도 있다.
//차례로 해당 배열의 요소, 인덱스, 배열전체값을 의미한다.
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
alert(`${item} is at index ${index} in ${array}`);
});
</script>
인수로 넘겨준 함수(실제 작동 함수)의 반환값은 무시된다.
indexOf, lastIndexOf와 includes
arr.indexOf, arr.lastIndexOf, arr.includes는 같은 이름을 가진 문자열 메서드들과 문법, 기능이 같다. 차이점은 연산 대상이 문자열이 아닌 배열이라는 점이다.
1) arr.indexOf(item, from)
from(인덱스)부터 시작해 item(요소)을 찾는다. 요소를 발견하면 해당 요소의 인덱스를 반환, 발견하지 못했으면 -1을 반환한다.
2) arr.lastIndexOf(item, from)
indexOf와 동일한 기능을하는데 끝에서부터 검색을 시작한다는 점이 다르다.
3) arr.includes(item, from)
from(인덱스)부터 시작해 배열에 item이 있는지를 검색. 해당하는 요소를 발견하면 true를 반환한다.
<script>
let arr = [1, 0, false];
alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1
alert( arr.includes(1) ); // true
</script>
주의!
위 메서드들은 요소를 찾을 때 완전 항등 연산자 === 을 사용한다.
위의 예시로 예를 들면 false를 찾으라고 시키면 정확하게 false만 찾지 숫자 0을 찾지는 않는다!
includes는 NaN도 제대로 처리한다는 점에서 indexOf/lastIndexOf와 약간의 차이가 있습니다.
const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (완전 항등 비교 === 는 NaN엔 동작하지 않으므로 0이 출력되지 않습니다.)
alert( arr.includes(NaN) );// true (NaN의 여부를 확인하였습니다.)
find와 findIndex
객체를 요소로 갖는 배열에서 특정 조건에 부합하는 객체를 어떻게 찾을 수 있을까?
이럴 때 arr.find(fn)을 사용할 수 있다.
<script>
let result = arr.find(function(item, index, array) {
// true가 반환되면 반복을 멈추고 해당 요소를 반환한다.
// 조건에 해당하는 요소가 없으면 undefined를 반환한다.
});
</script>
요소 전체를 대상으로 find에 전달된 함수가 순차적으로 호출된다.
item – 함수를 호출할 요소
index – 요소의 인덱스
array – 배열 자기 자신
함수가 참을 반환하면 탐색은 중단되고 해당 요소(배열의 요소 중 인수로 들어간 값과 일치하는 요소)가 반환된다. 원하는 요소를 찾지 못했으면 undefined가 반환된다.
예시) 배열 내에서 id == 1 조건을 충족하는 사용자 객체를 찾아보자.
<script>
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
let user = users.find(item => item.id == 1);
// 함수를 호출할 요소의 id 프로퍼티가 1인 값을 찾아줌
alert(user.name); // John
</script>
filter 메서드와 흡사하다고 생각된다. 실제로 팀 프로젝트를 진행했을 때 map 함수와 filter 함수 조합을 많이 썼었다.(중첩 loop 같은 느낌이어서 웹사이트 성능에는 좋지 않은 로직으로 알고 있다.)
그런데 위 예시에서 처럼 find 안의 함수가 인자를 하나만(item만) 가지고 있는 패턴이 가장 많이 사용되는 편이다. 다른 인자들(index, array)은 잘 사용되지 않는다.
물론 저도 써본 기억이 없습니다...!
arr.findIndex는 find와 동일한 일을 하나, 조건에 맞는 요소 대신 해당 요소의 인덱스를 반환한다. 조건에 맞는 요소가 없으면 -1을 반환한다.
filter
find는 함수의 반환 값을 true로 만드는 단 하나의 요소를 찾는다.
조건을 충족하는 요소가 여러 개일 경우는 arr.filter(fn)를 사용한다.
filter는 find와 문법이 유사하지만, 조건에 맞는 요소 전체를 담은 배열을 반환한다.
<script>
let results = arr.filter(function(item, index, array) {
// 조건을 충족하는 요소는 results에 배열 형식으로, 순차적으로 추가된다.
// 조건을 충족하는 요소가 하나도 없으면 빈 배열이 반환된다.
});
예시:
let users = [
{id: 1, name: "John"},
{id: 2, name: "Pete"},
{id: 3, name: "Mary"}
];
let someUsers = users.filter(item => item.id < 3); // id가 3보다 작은 user들을 반환한다.
alert(someUsers.length); // 2
<script>
map
map은 배열 요소 전체를 대상으로 함수를 호출하고, 함수 호출 결과를 배열로 반환한다.
<script>
let result = arr.map(function(item, index, array) {
// 요소 대신 새로운 값을 배열 형태로 반환한다.
});
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6 => 각 요소인 문자열들의 길이를 반환
</script>
sort(fn)
arr.sort()는 배열의 요소를 정렬하여 배열 자체가 변경된다.
메서드를 호출하면 재정렬 된 배열이 반환되는데, 이미 arr 자체가 수정되었기 때문에 arr을 사용하지 반환 값은 잘 사용하지 않는다.
<script>
let arr = [ 1, 2, 15 ];
// arr 내부 재정렬
arr.sort();
alert( arr ); // 1, 15, 2
</script>
정렬이 된다 해서 1, 2, 15 순서대로 나오는 것을 기대했는데 1, 15, 2 가 출력됐다. 왜 이럴까?
sort는 요소들을 문자열로 취급하여 정렬시키기 때문이다.
모든 요소는 문자형으로 변환된 이후에 재정렬된다. 문자열 '2'는 문자열 '15'보다 크다.
arr.sort()에 새로운 함수를 넘겨주면 기본 정렬 기준이 아닌 새로운 정렬 기준을 적용시킬 수 있다.
인수로 넘겨주는 함수는 반드시 값 두 개를 비교해야 하고 반환 값도 있어야 한다.
<script>
// 값 두개를 비교하고 어떤 값을 반환함
function compare(a, b) {
if (a > b) return 1; // 첫 번째 값이 두 번째 값보다 큰 경우
if (a == b) return 0; // 두 값이 같은 경우
if (a < b) return -1; // 첫 번째 값이 두 번째 값보다 작은 경우
}
</script>
배열 요소를 숫자 오름차순 기준으로 정렬하는 예시
<script>
//새로운 정렬 기준으로 쓸 함수
function compareNumeric(a, b) {
if (a > b) return 1;
if (a == b) return 0;
if (a < b) return -1;
}
let arr = [ 1, 2, 15 ];
arr.sort(compareNumeric);
alert(arr); // 1, 2, 15
</script>
이제 기대했던 대로 요소가 정렬되었습니다.
멍청이 같이 이해를 못하고 있었다.
a, b 라고 인수를 주니까 이 a, b 자리에 arr의 요소를 넣어야 한다고 생각했다. 아주 멍청하다.
그냥 단순히 정말 기준을 정의하는 건데!!!
a > b => 1
a == b => 0
a < b => -1
정말 단순하게 보자. a가 b보다 크면 1, 같으면 0, 작으면 -1 을 반환한다.
이 1 -> 0 -> -1 가 어떤 정렬 처럼 보이시나요?
바로 오름차순 입니다!
만약
a > b => -1
a == b => 0
a < b => 1
이렇게 정렬 기준을 주면 arr은 15, 2, 1 이 된다.
-1 -> 0 -> 1 이 내림차순이니까요. ^^
이렇게 정렬 기준을 정의해주는 함수를 ordering function, 정렬 함수라고 한다. sort에 정렬 함수를 인수로 넘겨주지 않으면 이 메서드는 사전편집 순으로 요소를 정렬합니다.
arr.sort(fn)는 포괄적인 정렬 알고리즘을 이용해 구현되어있다. 대개 최적화된 퀵 소트(quicksort)를 사용하는데, arr.sort(fn)는 주어진 함수를 사용해 정렬 기준을 만들고 이 기준에 따라 요소들을 재배열하므로 개발자가 내부 정렬 동작 원리를 알 필요가 없다.
우리는 정렬 함수 fn을 잘 만들어서 sort함수의 인수로 넘겨주기만 하면 된다.
정렬 함수는 어떤 숫자든 반환 가능하고 반환 값에 제약이 없다.
양수를 반환하는 경우 첫 번째 인수(a)가 두 번째 인수(b)보다 '크다’를 나타내고, 음수를 반환하는 경우 첫 번째 인수(a)가 두 번째 인수(b)보다 '작다’를 나타내기만 하면 된다.
이 점을 이용하면 정렬 함수를 더 간결하게 만들 수 있다.
<script>
let arr = [ 1, 2, 15 ];
//여기서 a-b는 기본적으로 양수를 반환하는 걸, 즉 a가 b보다 큰 것을 전제로 한다.
//즉!! 오름차순을 의미한다!
arr.sort(function(a, b) { return a - b; });
alert(arr); // 1, 2, 15
<script>
위 예시에서 반대로 return b-a 로 바꾸면 내림차순으로 15, 2, 1 을 반환한다.
sort함수의 인수인 정렬함수를 넘길 때는 화살표 함수를 사용해 코드를 더 깔끔하게 만들 수 있다.
arr.sort( (a, b) => a - b );
화살표 함수를 활용한 코드와 함수 선언문을 사용한 코드는 동일하게 작동한다.
문자열엔 localeCompare를 사용하자!
localeCompare는 유니코드를 기준으로 문자열을 비교하여 Ö같은 문자가 있는 언어에도 대응하려면 str.localeCompare 메서드를 사용해 문자열을 비교하는 게 좋다.
<script>
let countries = ['Österreich', 'Andorra', 'Vietnam'];
alert( countries.sort( (a, b) => a > b ? 1 : -1) );
// Andorra, Vietnam, Österreich (제대로 정렬되지 않음)
// 여기서 조건식 a>b 는 기준이 된다. a>b return 이 1이면 오름차순 정렬
// a>b return 이 -1이면 내림차순 정렬이 된다.
// 반대로 a<b return이 1이면 내림차순 정렬, a<b return이 -1이면 오름차순 정렬이 된다.
// 여기서는 1과 -1이 각각 참, 거짓으로 작용하고 있는 것 같다.
alert( countries.sort( (a, b) => a.localeCompare(b) ) );
// Andorra,Österreich,Vietnam (제대로 정렬됨)
</script>
reverse
arr.reverse는 arr의 요소를 역순으로 정렬시켜주는 메서드로 재정렬된 배열을 반환한다.
<script>
let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1
</script>
split과 join
str.split(delim) 메서드는 구분자(delimiter) delim을 기준으로 문자열을 쪼갠다.
쪼갠 걸 배열에 담아서 반환한다.
아래 예시에선 쉼표와 공백을 합친 문자열(', ')이 구분자로 사용되고 있다.
<script>
let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
alert( `${name}에게 보내는 메시지` );
// Bilbo에게 보내는 메시지
// Gandalf에게 보내는 메시지
// Nazgul에게 보내는 메시지
}
</script>
split 메서드는 두 번째 인수로 숫자를 받을 수 있는데 이 숫자는 배열의 길이를 제한해주므로 길이를 넘어서는 요소를 무시할 수 있다. (실무에서 자주 사용하는 기능은 아니라고 한다. 나도 본적이 없다.)
<script>
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);
alert(arr); // Bilbo, Gandalf, 배열 길이를 2로 제한했으니까
</script>
split으로는 문자열을 글자 단위로 분리하는 것도 가능하다. split(s)의 s를 빈 문자열로 지정하면 문자열을 글자 단위로 분리할 수 있다.
<script>
let str = "test";
alert( str.split('') ); // t,e,s,t
</script>
이제 join 메서드를 보자!!
arr.join(glue)은 인수 glue를 접착제처럼 사용해 배열 요소를 모두 합친 후 하나의 문자열을 만든다.
<script>
let arr = ['Bilbo', 'Gandalf', 'Nazgul'];
let str = arr.join(';'); // 배열 요소 모두를 ;를 사용해 하나의 문자열로 합칩니다.
alert( str ); // Bilbo;Gandalf;Nazgul
</script>
reduce와 reduceRight
arr.reduce와 arr.reduceRight는 forEach, for, for~of, map과 유사한 유사한 작업을 한다.
사용법은 복잡한 편이고 reduce와 reduceRight는 배열을 기반으로 값 하나를 도출할 때 사용된다.
<script>
let value = arr.reduce(function(accumulator, item, index, array) {
// ...
}, [initial]);
</script>
accumulator – 이전 함수 호출의 결과. initial은 함수 최초 호출 시 사용되는 초기값.(생략가능)
item – 현재 배열 요소
index – 요소의 위치
array – 배열
요소들을 순회하며 순차적으로 함수가 호출된다.
이전 함수 호출 결과는 다음 함수를 호출할 때 첫 번째 인수(previousValue)로 사용된다.
첫 번째 인수는 앞서 호출했던 함수들의 결과가 누적되어 저장되는 '누산기(accumulator)' 역할을 하며 마지막 함수까지 호출되면 이 값은 reduce의 반환 값이 된다.
reduce를 이용해 코드 한 줄로 배열의 모든 요소를 더한 값을 구해보겠습니다.
<script>
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
//sum은 반환값들을 누산(accumulator)하는 역할을 하고, current는 현재 요소(item)를 의미한다. 0은 초기값(initial)을 의미! 즉, sum은 0으로 시작한다.
alert(result); // 15
</script>
reduce에 전달한 함수는 대개 이렇게 인수를 두 개만 받는다.
위 예시가 실행된 과정은 다음과 같다.
1) reduce의 마지막 인수인 0(초깃값)이 sum에 할당, current엔 배열의 첫 번째 요소인 1이 할당. 따라서 함수의 결과는 1이 된다.
2) 두 번째 호출 시, sum = 1 이고 여기에 배열의 두 번째 요소 2 가 더해지므로 결과는 3이 된다. 즉, sum = 3이 됨.
3) 세 번째 호출 시, sum = 3 이고 여기에 배열의 다음 요소가 더해진다. 계속 이런 과정이 이어짐~
좀 더 보기 쉽게 표로 정리하면 아래와 같다.
초기값은 위 설명에서 생략 가능하다고 언급한 바 있다.
<script>
let arr = [1, 2, 3, 4, 5];
// reduce에서 초기값을 제거함(0이 없음)
let result = arr.reduce((sum, current) => sum + current);
alert( result ); // 15
</script>
결과는 초기값을 설정했을 때와 동일하다. 이유는?
초기값이 없으면 reduce는 배열의 첫 번째 요소를 초기값으로 사용하고 두 번째 요소부터 함수를 호출하기 때문이다.
주의! 배열이 비어있는 상태면 초기값 없이 reduce 호출 시 에러가 발생한다.
<script>
let arr = [];
// TypeError: Reduce of empty array with no initial value
// 초기값이 있었다면 초기값을 반환했을 것이다!
arr.reduce((sum, current) => sum + current);
</script>
이런 예외상황 때문에 항상 초기값을 명시해 줄 것을 권장한다.
arr.reduceRight는 reduce와 동일한 기능을 하지만 배열의 오른쪽부터 연산을 수행한다.
정말 별의별 메서드가 다 있다.
자바스크립트에서 배열은 객체형에 속하기 때문에 typeof로는 배열과 객체를 구분할 수 없다.
alert(typeof {}); // object
alert(typeof []); // object
Array.isArray(value)는 배열을 식별할 때 사용하는 메서드이다. value가 배열이라면 true를, 배열이 아니라면 false를 반환한다.
<script>
alert(Array.isArray({})); // false
alert(Array.isArray([])); // true
</script>
thisArg는 아래와 같이 활용할 수 있다.
<script>
arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg는 선택적으로 사용할 수 있는 마지막 인수
</script>
thisArg는 선택적으로 사용할 수 있는 마지막 인수로 func의 this가 된다.
아래 예시에서 객체 army의 메서드를 filter의 인자로 넘겨주고 있는데, 이때 thisArg는 canJoin의 컨텍스트 정보를 넘긴다.
<script>
let army = {
minAge: 18,
maxAge: 27,
canJoin(user) {
return user.age >= this.minAge && user.age < this.maxAge;
}
};
let users = [
{age: 16},
{age: 20},
{age: 23},
{age: 30}
];
// army.canJoin 호출 시 참을 반환해주는 user를 찾음.
// 즉, army.canJoin이 반환하는 조건식에 맞는 user를 찾는 것.
// 아래 filter의 인자를 보면 army가 thisArg로 army.canJoin의 this를 의미한다.
let soldiers = users.filter(army.canJoin, army);
alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23
</script>
thisArgs로 army를 지정하지 않고 단순히 users.filter(army.canJoin)를 사용했다면 army.canJoin은 단독 함수처럼 취급되고, 함수 본문 내 this는 undefined가 되어 에러가 발생했을 것이다. 전혀 몰랐다.
users.filter(user => army.canJoin(user))를 사용하면 users.filter(army.canJoin, army)를 대체할 수 있긴 한데 thisArg를 사용하는 방식이 좀 더 이해하기 쉬우므로 더 자주 사용된다고 한다.
이게 자주 사용된다는 것도 전혀 몰랐다.
이 파트를 정리하는 데 시간이 너무 오래 걸렸다.
하지만 sort와 reduce를 드디어 제대로 알게된 것에 의의를 둔다.