indexOf, slice, substring 메서드 사용시, 나를 헷갈리게 하는 인자들

이나리·2022년 9월 19일
0

메서드라는 것은 사실 자주 사용하면 그만큼 손에 익기 때문에, 어떤 때에 쓰는지 외우지 않아도 자연스럽게 알 수 있습니다. map, filter, 이런 메서드들이 그런데요.

indexOf, slice, substring 등의 메서드들도 많이 사용하지만, 이 메서드들을 사용할 때 첫번째 인자만 전달할 때는 문제가 없는데, 옵셔널 인자인 2번째 인자까지 있을 때는 코드를 바로 해석하지 못하는 문제가 있더라구요.

이 문제를 해결해보기 위해, 일부 메서드들에 대해 정리해보는 시간을 가져보고자 합니다.

indexOf, lastIndexOf의 fromIndex

array.indexOf(searchElement, fromIndex);
array.lastIndexOf(searchElement, fromIndex);

이 메서드는 문자열에 대해서도 실행 가능하지만, 배열을 기준으로 설명하겠습니다.

indexOf와 lastIndexOf는 각각 요소를 배열의 앞, 뒤에서부터 찾은 후 그 요소의 인덱스를 리턴하고, 해당 요소가 없으면 -1을 리턴합니다.

fromIndex 는 요소를 찾을 때, 시작할 인덱스입니다.
옵셔널 인자이므로, 별도의 값이 주어지지 않으면 배열의 전체 요소를 검색합니다. 이 값이 어떤 값을 가지느냐에 따라, 두 메서드의 동작 방식에는 차이가 발생합니다.

1. fromIndex가 배열의 길이보다 크거나 같은 경우

indexOf 는 배열에서 검색을 시도하지 않고, -1을 리턴합니다.
lastIndexOf 는 전체 배열을 검색합니다.

indexOf는 요소를 배열의 앞에서부터 검색하기 때문에, 이 값이 배열의 길이보다 같거나 클 경우에는 검색을 시작할 요소가 없게 됩니다. 검색할 요소가 없으므로 찾을 요소도 당연히 없겠죠? 따라서, -1이 리턴됩니다.

그렇다면, lastIndexOf는 어떨까요?
lastIndexOf의 fromIndex 기본값은 array.length - 1 입니다. 이는 배열의 마지막 요소의 위치를 의미합니다. 요소를 뒤에서부터 검색하기 때문에, 가장 마지막 인덱스가 기본값이 됩니다.

fromIndex 값이 이 인덱스보다 더 크다고 해도 요소를 배열의 실제 마지막 요소에서부터 검색하기 때문에, 기본값을 전달한 것과 동일한 결과를 보여줍니다.

2. fromIndex가 음수인 경우

indexOf 는 배열의 뒤에서부터, lastIndexOf 는 배열의 앞에서부터 요소를 검색합니다.
그러나, 요소를 찾는 방향은 여전히 이전과 동일합니다. indexOf는 앞에서부터 n번째, lastIndexOf는 뒤에서부터 n번째 그대로입니다.

음수가 전달되면, 두 메서드는 자신의 기본 동작 방향과 반대되는 위치에서 요소를 검색합니다.
하지만 찾은 인덱스 결과는 이전과 동일한 방향에서부터의 인덱스 값을 리턴하기 때문에, 시각적으로 혼란을 일으킵니다.

아래 예제의 답을 한번 생각해보세요.

const array = [3, 2, 4, 19, 1, 5, 20];
const index = array.indexOf(1, -3); // index는 몇일까요? 콘솔에 직접 찍어보세요

어떤가요? 헷갈리지 않는 분들도 계실 수 있을 겁니다. 하지만 저는 헷갈립니다!
저와 같이 헷갈릴만한 분들을 위한 약간의 팁이 존재합니다.

MDN 문서에 보니까 음수가 전달될 때, fromIndex 값은 array.length + fromIndex 값을 가진다고 설명되어 있습니다. 저는 이 방법을 통해, 앞서 어렵게 느껴졌던 코드가 조금 쉽게 다가왔습니다.

또 한가지 특징은 음수가 전달되었기 때문에, 앞서 1번의 케이스가 반대로 나타납니다.
lastIndexOffromIndex 값에 배열의 길이보다 더 작은 값이 전달되면 -1을 리턴하고, indexOf 는 전체 배열을 검색합니다.
왜 그럴까요? 요소의 검색 위치는 바뀌었는데, 요소를 찾는 방향은 그대로이기 때문입니다.

간단한 예제를 통해 한번 확인해볼까요?

const array = [3, 2, 4, 19, 1, 5, 20];
const index = array.indexOf(1, -8); // index = -1
const index2 = array.lastIndexOf(1, -8); // index2 = 4

이 결과에 대해서 일일이 파고드는 것보다는, 음수가 전달될 때 요소의 검색 위치가 바뀐다는 것만 알고 있어도 충분할 겁니다. 그 외의 것은 부가적인 계산에 해당할 테니까요.

slice, substring의 indexStart, indexEnd

이번에 살펴볼 메서드는 indexOf와 달리, 주어진 2개의 인자의 값이 무엇인지에 따른 차이를 확인해 볼 겁니다.

str.slice(indexStart, indexEnd);
str.substring(indexStart, indexEnd);

slice 메서드는 배열에서도 사용 가능하지만, substring 메서드가 문자열에 대해서만 실행 가능하므로, 문자열을 기준으로 설명합니다.

이 두 메서드는 비슷한 기능을 갖고 있습니다. indexStart 에 해당하는 위치의 문자열부터 indexEnd 의 위치 전까지의 문자열을 복사하죠. 이때, indexEnd 의 값을 전달하지 않게 되면, 시작 위치부터 끝까지 복사합니다.

또한, 어떤 인자도 전달하지 않으면 전체 문자열을 복사하지만, 두 인자의 값을 동일하게 전달하면 어떤 문자열도 복사되지 않습니다.

const hello = 'hello';
const str1 = hello.slice(1); // 'ello'
const str2 = hello.slice(1, 3); // 'el'
const str3 = hello.slice(); // 'hello'
const str4 = hello.slice(1, 1); // ''

겉으로 보기에, 두 메서드는 매우 비슷하게 동작하죠? 하지만 완전히 같지는 않습니다.

1. indexStart > indexEnd

slice 는 각 인덱스 값의 크기에 관계없이 기존 방식과 동일하게 동작하지만,
substring 은 시작 인덱스가 끝 인덱스보다 크다면, 항상 시작 인덱스와 끝 인덱스와 바뀐 것처럼 동작합니다.

const hello = 'hello';

hello.slice(3, 1); // ''
hello.substring(3, 1); // 'el'

따라서, 위의 예제에서 slice는 복사할 문자열이 없으므로 빈 문자열을 리턴하지만, substring의 경우는 hello.substring(3, 1) == hello.substring(1, 3) 가 되어, 복사한 문자열이 리턴됩니다.

2. 음수가 전달되는 경우

slice 는 복사 방향이 문자열의 끝에서부터 시작하는 것으로 바뀌지만,
substring 은 무조건 0으로 처리한 후 실행됩니다.

const hello = 'hello';

hello.slice(-3, -1); // 'll'
hello.substring(-3, -1); // 'hello'

음수가 전달되면, 앞에서 배운 indexOf 처럼 slice도 length + index 값으로 계산되는 것을 알 수 있습니다.

그에 반해, substring은 조금 독특한 동작 방식을 갖고 있습니다. 앞선 예제에서 전달된 음수 값은 모두 0으로 처리되어 hello.substring() 과 동일한 결과를 가져다 줍니다.

마치며

정리를 해보니, 메서드의 2번째 인자가 무엇이냐는 별로 중요하지 않습니다.
음수 값이 전달될 때를 제외하고는 메서드가 실행되는 시작 위치를 변경하는 경우는 없었으니, 그냥 메서드의 동작을 기반으로 계산만 하면 되는 것이었습니다.

다만, 그 값이 자주 사용되지 않는 패턴에 해당한다면 코드가 바로 읽히지 않는 것이죠.

짚고 넘어가야 하는 부분에 대해 정리를 해보자면, 일부 메서드 간에 공통적인 특징이 있었습니다.

  • 음수 인덱스 값을 전달할 경우, 메서드가 실행되는 시작 위치를 반대로 변경한다.
  • 음수 값은 length + index 의 값으로도 계산할 수 있다.

substring 메서드는 다소 특이하게 동작하니, 알아두는 것도 좋을 것 같습니다.
알기 싫다면, 비슷한 역할을 하는 slice 메서드를 이용해서 코드를 작성하면 되겠죠?

0개의 댓글