자바스크립트 톺아보기4

이지영·2025년 1월 2일

includes, startsWith, endsWith

비교적 근래에 나온 메서드 str.includes(substr,pos)는 str에 부분 문자열 substr이 있는지에 따라 true나 false를 반환한다.
부분 문자열의 위치 정보는 필요하지 않고, 포함 여부만 알고 싶을 때 적합한 메서드이다.

alert("widget with id".includes("widget")); // true
alert("Hello".includes("Bye")); // false

str.includes에도 str.indexOf처럼 두 번째 인수를 넘기면 해당 위치부터 부분 문자열을 검색한다.

alert("Widget".includes("id")); // true
alert("Widget".includes("id",3)); // false, 세 번째 위치 이후엔 "id"가 없다.

메서드 str.startsWith와 str.endsWith는 메서드 이름 그대로 문자열 str이 특정 문자열로 시작하는지(start with)여부와 특정 문자열로 끝나는지(end with)여부를 확인할 떄 사용할 수 있다.

alert("Widget".startWidth("Wid")); // ture
alert("Widget".endWith("get")); // true

부분 문자열 추출하기
자바스크립트에는 부분 문자열 추출과 관ㄹㄴ된 메서드가 세 가지 있다.
substring, substr, slice를 하나씩 알아보자.

str.slice(start[, end]);
문자열의 start부터 end까지(end는 미포함)를 반환합니다.

let str = "stringify"
alert(str.slice(0,5)); // strin
alert(str.slice(0,1)); // s

두 번째 인수가 생략된 경우엔, 명시한 위치부터 문자열 끝까지를 반환

let str = "stringify";
alert(str.slice(2)); // al

start와 end는 음수가 될 수도 있다. 음수를 넘기면 문자열 끝에서부터 카운팅을 시작한다.

let str = "stringify";
alert(str.slice(-4,-1)); // gif

str.substring(start,[, end]);
start와 end 사이에 있는 문자열을 반환한다.
substirng은 slice와 아주 유사하지만 start과 end보다 커도 괜찮다는데 차이가 있다

```jsx
let str = "stringify";
//동일한 부분 문자열을 반환한다.
alert(str.substring(2,6)); // ring
alert(str.substring(6,2)); // ring

//slice를 사용하면 결과가 다르다.
alert(str.slice(2,6)); // ring(같음)
alert(str.slice(6,2)); // ''(빈 문자열)

str.substr(start [, length])

start에서부터 시작해 length개의 글자를 반환

substring은 음수 인수를 허용하지 않는다. 음수는 0으로 처리된다.
또한 끝 위치 대신에 길이를 기준으로 문자열을 추출한다는 점에서 substring과 slice와 차이가 있다.

let str = "stringify";
alert(str.substr(2,4)); // ring

첫 번째 인수가 음수면 뒤에서부터 개수를 센다.

let str = "stringify";
alert(str.substr(-4,2)); // gi, 끝에서 네 번째 위치부터 글자 두 개

정리

|메서드|추출할 부분 문자열|음수 허용 여부|
|slice(start,end)|start 부터 end까지 (end는 미포함)| 음수 허용|
|--|--|--|
|substring(start,end)|start부터 end사이|음수는 0으로 취급|
|substr(start,length)|start부터 length개의 글자|음수 허용

?어떤 메서드를 선택해야하는가?

모두 사용해도 된다 하지만 substr에는 단점이 있다.
substr는 구식 스크립트에 대응하기 위해 남겨 둔 브라우저 전용 기능들을 명시해 놓은 부록에 정의되어있다.
거의 모든 곳에서 이 메서드가 동작하긴 하지만 브라우저 이외의 호스트 환경에서는 제대로 동작하지 않을 수 있다.

남은 두 메서드 중 slice 는 음수 인수를 허용한다는 측면에서 substring보다 좀 더 유연하다.
따라서 세 메서드 중 slice만 외워놓고 사용해도 충분하다.

문자열 비교하기

문자열을 비교할 떈 알파벳 순서를 기준으로 글자끼리 비교가 이루어진다.
1. 소문자는 대문자보다 항상 크다.
2. 발음 구별 기호가 붙은 문자는 알파벳 순서 기준을 따르지 않는다.

alert( 'Österreich' > 'Zealand' ); // true (Österreich는 오스트리아를 독일어로 표기한 것임

문자열 제대로 비교하기

언어마다 문자 체계가 다르기 때문에 문자열을 제대로 비교하는 알고리즘을 만드는 건 간단하지 않다.
문자열을 비교하려면 일단 페이지에서 어떤 언어를 사용하고 있는지 브라우저가 알아야한다.
str.localeCompare(str2)를 호출하면 ECMA-402에서 정의한 규칙에 따라 정수가 반환된다.

  • str이 str2보다 작으면 음수를 반환
  • str이 str2보다 크면 양수를 반환
  • str과 str2가 같으면 0을 반환

이 외에도 문자열에 쓸 수 있는 유용한 메서드 몇 가지가 있다.

str.trim() - 문자열 앞과 끝의 공백 문자를 다듬어준다.(공백제거)

str.repeat(n) - 문자열을 n번 반복한다.

배열

객체는 태생이 순서를 고려하지 않고 만들어진 자료구조이기 때문에
객체를 이용하면 새로운 프로퍼티를 기존 프로퍼티 사이에 끼워 넣는 것이 불가능하다.
이럴 땐 순서가 있는 컬렉션을 저장할 때 쓰이는 자료구조인 배열을 사용할 수 있다.

배열 선언

let arr = new Array(생성자)
let arr = []; 리터럴

또한 배열 요소으 ㅣ자료형엔 제약이 없다.
객체, 함수 등이 들어갈 수 있다.

push 요소를 스택 끝에 집어넣는다.

pop 스택 끝 마지막 요소를 추출한다.

shift 스택의 맨 앞 요소를 제거한다.

unShift 스택의 맨 앞에 요소를 추가한다.

배열의 내부 동작 원리

배열은 특별한 종류의 객체이다.

배열 요소를 arr[0]처럼 대괄호를 사용해 접근하는 방식은 객체문법에서 왔다.

다만 배열은 키가 숫자라는 점만 다르다.

성능 push와 pop은 빠르지만 shift와 unshift는 느리다.

반복문 for...of

현재ㅇ 요소의 인덱스는 얻을 수 없고 값만 얻을 수 있다.

let fruits = ["사과","오렌지","배"];
for(let fruit of fruits) { //fruits배열을 fruit변수에 하나씩 담아서 출력
  alert(fruit);
}

배열엔 toString메서드가 구현되어있어 이를 호출하면 요소를 쉼표로 구분한 문자열이 반환된다.

let arr = [1,2,3];
alert(arr); // 1,2,3
alert(arr.toString() === '1,2,3')
alert(String(arr) === '1,2,3')

Summary

배열은 특수한 형태의 객체로, 순서가 있는 자룔르 저장하고 관리하는 용도에 최적화된 자료구조이다.

  • 선언 방법은 리터럴과 생성자가 있다. [], new Array()
  • length 포로퍼티는 배열의 길이를 나타내준다. 정확히는 숫자형 인덱스 중 가장ㄷ 큰 값에 1을 더한 겂이다. 배열 메서드는 length프로퍼티를 자동으로 조정해준다.
  • length값을 수동으로 줄이면 배열 끝이 잘린다.

다음 연산을 사용하면 배열을 데큐처럼 사용할 수 있다.

  • push() - items를 배열 끝에 더해준다.
  • pop() - 배열 끝 요소를 제거하고 제거한 요소를 반환
  • shift() - 배열 첫 요소를 제거하고 제거한 요소를 반환
  • unshift() - 배열 처음에 더해준다.

아래 방법을 사용하면 모든 요소를 대상으로 반복작업 할 수 있다.

for(let item of arr) - 배열 요소에만 사용되는 모던한 문법

for (let i in arr) - 배열에는 절대 사용하지 않기

splice

배열 요소를 하나만 지우고 싶다면?
delete의 경우 지우고 난 후 빈 공간이 남아있고, 자동으로 채워지지 않으며 undefined가 들어있다.
하지만 splice를 사용하면 공간까지 제거할 수 있다.

let arr = ["I","study","JavaScript"];
arr.splice(1,1); // 인덱스 1부터 요소 한 개를 제거
alert(arr); // ["I", "JavaScript"]

또한 요소를 지우고 그 자리를 다른 요소들로 교체할 수 있다.

let arr = ["I", "study", "JavaScript", "right", "now"];
// 0번째 인덱스부터 3개의 요소를 지우고 이 자리를 다른 요소로 대체
arr.splice(0,3,"Let's", "dance");
alert(arr); // ["Let's", "dance", "right", "now"]

slice

splice와 유사하지만 훨씬 간단하다.
arr.slice([start], [end]);

start인덱스부터 end를 제외한 인덱스까지의 요소를 복사한 새로운 배열을 반환한다.
arr.slice는 문자열 메스드인 str.slice와 유사하게 동작하는데 arr.slice는 서브 문자열 대신 서브 배열을 반환한단는 점이 다르다.

concat

기존 배열의 요소를 사용해 새로운 배열을 만들거나 기존 배열에 요소를 추가하고자 할 때 사용할 수 있다.

arr.concat(arg1,arg2...)

인수에는 배열이나 값이 올 수 있는데, 인수 개수에는 제한이 없다.

forEach로 반복작업하기

주어진 함수를 배열 요소 각각에 대해 실행할 수 있게 해준다.

arr.forEach(function(item, index, array){
// 반복할 무언가
}

배열 탐색하기

indexOf, lastIndexOf, includes

문자열 메서드와 문법이 동일하며, 하는일도 같다 다만 연산 대상이 문자열이 아닌 배열이라는 점만 다르다.

  • arr.indexOf(item,from)는 인덱스 from부터 시작해 item 요소를 찾는다. 요소를 발견하면 해당 요소의 인덱스를 반환하고 발견하지 못했으면 -1을 반환한다.
  • arr.lastIndexOf(item,from)는 위 메서드와 동일한 기능을 하는데, 검색을 끝에서부터 시작한다는 점만 다르다.
  • arr.includes(item,from)는 인덱스 from부터 시작해 item이 있는지를 검색하는데 해당하는 요소를 발견하면 true를 반환한다.

위 메서드들은 요소를 찾을 때 완전 항등 연산자 === 을 사용한다는 점에 유의해야한다.

find와 findIndex

객체로 이루어진 배열이 있다고 가정해보자
특정 조건에 부합하는 객체를 배열 내에서 어떻게 찾을 수 있을까?
이럴 때 arr.find(fn)을 사용할 수 있다.

let result = arr.find(function(){
  // true가 반환되면 반복이 멈추고 해당 요소를 반환
  // 조건에 해당하는 요소가 없으면 undefind반환
})

요소 전체를 대상으로 함수가 순차적으로 호출된다.

  • item - 함수를 호출할 요소
  • index - 요소의 인덱스
  • array - 배열 자기 자신

함수가 참을 반환하면 탐색은 중단되고 해당 요소가 반환된다.
원하는 요소를 찾지 못했으면 undefined가 반환된다.

let users = [
{ id: 1 , name: "John" },
{ id: 2 , name: "Pete" },
{ id: 3 , name: "Mary" }
];
let user = users.find(item=>item.id == 1);
alert(user.name); // John

실무에서 객체로 구성된 배열을 다뤄야 할 일이 잦기 때문에 find메서드 활용법을 알아두자!
findIndex는 find와 동일한 일을 하나, 조건에 맞는 요소를 반환하는 대신 해당 요소의 인덱스를 반환한다는 점이 다르다.
조건에 맞는 요소가 없으면 -1이 반환된다.

filter

find메서드는 함수의 반환 값을 true 로 만드는 단 하나의 요소를 찾는다.
조건을 충족하는 요소가 여러 개라면 arr.filter(Fn)을 사용해야한다.
filter는 find와 문법이 유사하지만, 조건에 맞는 요소 전체를 담은 배열을 반환한다는 점에서 차이가 있다.

let users = [
{ id: 1 , name: "John" },
{ id: 2 , name: "Pete" },
{ id: 3 , name: "Mary" }
];

let someUsers = users.filter(item => item.id < 3);
alert(someUsers.length); // 2;

배열을 변형하는 메서드

배열을 변형시키거나 요소를 재정렬해주는 메서드
map 은 사용 빈도가 아주 높은 메서드 중 하나이다.
map은 배열 요소 전체를 대상으로 함수를 호출하고 함수 호출 결과를 배열로 반환해준다.

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths) = 5,7,9;

sort(fn)

배열의 요소를 사전순으로 정렬해준다.
요소는 문자열로 취급되어 재졍렬된다.


기본 사전순 정렬 기준 대신 새로운 정렬 기준을 만드려면 arr.sort()에 새로운 함수를 넘겨줘야한다.
인수로 넘겨주는 함수는 반드시 값 두 개를 비교해야하고 반환 값도 있어야 한다.

function compare(a,b){
  if(a>b) return 1; // 첫 번째 값이 두 번째 값보다 큰 경우
  if(a==b) return 0;
  if(a<b) return -1 // 첫 번째 값이 두 번째 값보다 작은 경우
}
let arr = [1,2,15];
arr.sort(compare);
alert(arr); // 1,2,15

또한 아래와 같이 정렬 함수를 더 간결하게 만들 수 있다.

// 오름차순
arr.sort( (a, b) => a - b );

reserve

arr의 요소를 역순으로 정렬시켜주는 메서드이다. arr.reserve();

split과 join

let name = "Bilbo, Gandalf, Nazgul";
let arr = names.split(', ');

for (let name of arr){
  alert(`${name}에게 보내는 메시지`)
}

또한 split 메서드는 두 번째 인수로 숫자를 받을 수 있다.
이 숫자는 배열의 길이를 제한해주므로 길이를 넘어서는 요소를 무시할 수 있다.


arr.join은 split과 반대 역할을 하는 메서드이다. 인수 glue를 접착제처럼 사용해 배열 요소를 모두 합친 후 하나의 문자열을 만들어준다.

let arr = ['Bilbo','Gandalf','Nazgul'];
let str = arr.join(';'); // 배열 요소 모두를 ;를 사용해 하나의 문자열로 합친다.
alert(str); // Bilbo;Gandalf;Nazgul

reduce와 reduceRight

forEach, for, for..of 를 사용하면 배열 내 요소를 대상으로 반복 작업을 할 수 있다.
각 요소를 돌면서 반복 작업을 수행하고, 작업 결과물을 새로운 배열 형태로 얻으려면 map을 사용하면 된다.
arr.reduce와 arr.rdeuceRight도 이런 메서드들과 유사한 작업을 해준다.
그런데 사용법이 조금 복잡하다.
reduce와 reduceRight는 배열을 기반으로 값 하나를 도출할 때 사용된다.

let value = arr.reduce(function(accumulator, item, index, array){
// ...
})

인수로 넘겨주는 함수는 배열의 모든 요소를 대상으로 차례차례 적용되는데,
적용 결과는 다음 함수 호출 시 사용된다.

  • accumulator : 이전 함수 호출의 결과. initial은 함수 최초 호출 시 사용되는 초깃값을 나타냄(옵션)
  • item : 현재 배열 요소
  • index : 요소의 위치
  • array : 배열


    이전 함수 호출 결과는 다음 함수를 호출할 때 첫 번째 인수(previousValue)로 사용된다.
    첫 번째 인수는 앞서 호출했던 함수들의 결과가 누적되어 저장되는 누산기라고 생각하면 된다.
    마지막 함수까지 호출되면 이 값은 reduce의 반환값이 된다.
let arr = [1,2,3,4,5];
let result = arr.reduce((sum, current) => sum + current, 0)'

함수 최초 호출 시 reduce의 마지막 인수인 0(초깃값)이 sum에 할당
current엔 배열의 첫 번째 요소인 1이 할당된다.
두 번째 호출 시 sum 의 값은 1, 이처럼 누적된 값을 가지고 있다.


arr.reduceRgiht는 reduce와 동일한 기능을 하지만 배열의 오른쪽부터 연산을 수행한다.

Array.isArray로 배열 여부 알아내기

자바스크립트에서 배열은 독립된 자료형으로 취급되지 않고 객체형에 속한다.
따라서 typeof로는 일반 객체와 배열을 구분할 수 없다.
Array.isArray([]); // true
Array.isArray({}); // false

배열 메서드와 'thisArg'

함수를 호출하는 대부분의 배열 메서드(find, filter, map등)는 thksArg라는 매개변수를 옵션으로 받을 수 있다.
자주 사용되는 인수가 아니다.
ex)

arr.find(func,thisArg);
arr.filter(func,thiisAfg);
arr.map(func,thisArg);
// thisArg는 선택적으로 사용할 수 있는 마지막 인수이다.

thisArg는 func의 this가 된다.
아래 예시에서 객체 army의 메서드를 filter의 인자로 넘겨주고 있는데,
이때 thisArg는 canJoin의 컨텍스트 정보를 넘겨준다.

ex)

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를 찾음
let soldiers = users.filter(army.canJoin, army);

alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23

즉, argy.canJoin메서드를 호출할 때 thisarmy객체를 가리키게 된다.

filter메서드를 사용할 때, army.canJoin을 인자로 넘겨주면

filter는 이 메서드를 호출할 때 this를 기본적으로 undefined로 설정한다.

하지만 thisArg를 사용하면 this의 값을 명시적으로 설정할 수 있다.

즉, thisArg를 통해 canJoin메서드 내에서 thks가 army객체를 가리키도록 지정하는 것이다.

summary

  • 요소를 더하거나 지우가
    • push : 맨 끝에 요소 추가하기
    • pop : 맨 끝에 요소 추출하기
    • Shift : 첫 요소 추출하기
    • unShift : 맨 앞에 요소 추가하기
    • splice(pos,deleteCount, ...items) : pos부터 deleteCount개의 요소를 지우고 items추가하기
    • slice(start, end) : start부터 end바로 앞까지의 요소를 복사해 새로운 배열을 만듦
    • concat(...items) : 배열의 모든 요소를 복사하고 items를 추가해 새로운 배열을 만든 후 이를 반환함
  • 원하는 요소 찾기
    • indexOf / lastIndexOf(item, pos) : pos부터 원하는 item을 찾음. 찾게되면 해당 요소의 인덱스를 아니면 -1을 반환
    • includes(value) : 배열에 value가 있으면 true를 그렇지 않으면 false를 반환
    • find/filter(func) : func의 반환값을 true로 만드는 첫 번째 / 전체 요소를 반환함
    • findIndex 는 find와 유사함 다만 요소 대신 인덱스를 반환
  • 배열 전체 순회하기
    • forEach(func) : 모든 요소에 func을 호출함. 결과는 반환되지 않음
  • 배열 변형하기
    • map(func) : 모든 요소에 func를 호출하고 반환된 결과를 가지고 새로운 배열을 만듦
    • sort(func) : 배열을 정렬하고 정렬된 배열을 반환
    • reserve() : 배열을 뒤집어 반환함
    • split/join : 문자열을 배열로, 배열을 문자열로
    • reduce(func, initial) 요소를 차례로 돌면서 func를 호출함, 다음 함수 호출에 전달함. 최종적으로 하나의 값이 도출됨
  • 기타
    • Array.isArray(arr) : arr이 배열인지 여부를 판단

sort, reverse, splice는 기존 배열을 변형시킨다는 점에 주의
arr.some(fn)과 arr.eveery(fn)은 배열을 확인한다.
some은 함수의 반환값을 true로 만드는 요소가 하나라도 있는지 여부를 확인 하고
every는 모든 요소가 함수의 반환값을 true로 만드는지 여부 를 확인한다.
두 메서드 모두 조건을 충족하면 true를 그렇지 않으면 false를 반환한다.

arr.fill(value,start, end)은 start부터 end까지 value를 채워넣는다.


arr.copyWithin(target,start,end)은 start부터 end까지 요소를 복사하고 복소한 요소를 target에
붙여넣는다. 기존 요소가 있다면 덮어쓴다.

과제

border-left-with를 borderLeftWidth로 변경하기

function camelize(str) {
  // split으로 자르기
  return str.split("-")
  // 1번째 배열부터 첫 글자 대문자로 변경
  .map((word,index)=> index == 0 ? word : word[0].toUpperCase() + word.slice(1) );
  .join(''); //  join으로 문자열로 연결
}

특정 범위에 속하는 요소 찾기
배열 arr의 요소 중 a이상 b 이하 범위에 속하는 요소만 골라 새로운 배열에 집어넣고, 해당 요소를 출력해주는 함수 fiterRange(arr,a,b)를
작성해라

let arr = [5,3,8,1];
let filtered  =  fiterRange(arr,1,4);
function filterRagne(arr,a,b){
  return arr.filter((item)=> item >= a && item <= b )
}

특정 범위에 속하는 요소 찾기(배열 변경하기)
배열 arr의 요소 중 a와 b 사이에 속하지 않는 요소는 삭제해주는 함수 filterRangeInPlace(arr,a,b)를 작성해보세요
배열의 모든 요소(i)는 다음 조건을 만족해야 합니다. a<=arr[i]<=b
작성한 함수는 기존 배열을 변경하기만 하고 아무 것도 반환하지 않아야 합니다.

function filterRangeInPlace(arr,a,b){
 for(let i = 0; i < arr.length; i++ {
  if( arr[i] < a  || arr[i] > b {
    arr.slice(i,1);
    i--;
    }
  } 
}

내림차순으로 정렬하기

let arr = [5,2,1,-10,8];
arr.sort((a,b)=> b-a);

배열 복사본을 정렬하기
문자열이 담긴 배열 arr을 복사한 다음 해당 배열을 정렬해봅시다.
단 이때 arr은 변경되면 안됩니다.
함수 copySorted(arr)는 복사 후 정렬된 배열을 반환해야 합니다.

let arr = ["HTML", "JavaScript", "CSS"];
function copySorted(arr) {
  return arr.slice().sort();
}
let sorted = copySorted(arr);

확당 가능한 계산기
기능을 확장할 수 있는 계산기 객체를 만들어주는 생성자 함수 Calculator를 작성해봅시다.
Calculator는 두 단계를 거쳐 만들 수 있습니다.
1. 펏 번째 단계는 "1+2"와 같은 문자열을 받아서 "숫자 연산자 숫자" 형태(공백으로 구분)로 바꿔주는 메서드
calculate(str)을 구현하는 것입니다. 이 함수는 + 와 - 를 처리할 수 있어야 하고, 연삭 결과를 반환해야 합니다.
2. 두 번째 단계는 계산기가 새로운 연산을 학습할 수 있도록 해주는 메서드 addMethod(name, func) 를 추가해주는 것입니다.
3. 연산자 이름을 나타내는 name과 인수가 두 개인 익명함수 func(a,b)를 받는 새 메서드를 구현해야하죠
4. 구현된 메서드를 위용해 곱셈과 나눗셈 거듭제곱 연산자를 추가해주는 에시는 아래와 같습니다.

let powerClac = new Calculator;
powerCalc.addMethod("*", (a,b) => a*b);
powerCalc.addMethod("/", (a,b) => a/b);
powerCalc.addMethod("**", (a,b) => a**b);
let result = powerCalc.calculate("2**3));

정답

function calculator(str) {

 this.methods = {
    "-" : (a,b) => (a-b),
    "+" : (a,b) => (a+b)
  }

  this.calculate = function (str) {
    let split = str.split(" "),
      a = +split[0],
      op = split[1],
      b = +split[2];

    if(!this.method[op] || isNaN(a) || isNaN(b)) {
      return NaN;
    }
    return this.methods[op](a,b);
  };
  this.addMethod = function(name,func) {
    this.methods[name] = func;
  };
}

0개의 댓글