JavaScript 심화

ANN·2025년 11월 17일

OneBiteReact

목록 보기
2/4
post-thumbnail

한 입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지 by 이정환

📌 Truthy와 Falsy

자바스크립트는 어떠한 값이 불리언 타입에 해당하는 참이거나, 또는 거짓이지 않아도,
상황에 따라서 참으로 판단하거나, 거짓으로 판단하는 경우가 있음

위 코드에서처럼,
조건식에서 123이란 값이 참인지 거짓인지 물어보면, 자바스크립트는 참으로 판단
또 조건식에서 undefined 값이 참인지 거짓인지 물어보면 이때는 거짓으로 판단

이렇게 어떤 값이 참이나 거짓을 의미하는 값이 아닌데도,
조건문 내에서는 참이나 거짓으로 평가하는 특징을 가짐

이때 참으로 평가 받는 값 === Truthy
거짓으로 평가 받는 값 === Falsy

💡 자바스크립트의 모든 값은 Truthy 하거나 또는 Falsy 함

이 특징은 조건문을 간결하게 또는 강력하게 만들 수 있음

✅ 1. Falsy한 값

위 값들은 조건문에서 거짓으로 평가 받음


f1에 저장된 Falsy값이 NOT연산자를 만나 참이 되어 콘솔 로그 출력

✅ 2. Truthy한 값

Truthy한 값은 Falsy한 값처럼 일일히 나열할 수가 없음
위의 7가지 Falsy값 외에는 모두 Truthy

Truthy한 값을 조건문 안에 단독으로 명시하면 자바스크립트는 참으로 판단

✅ 3. 활용 사례

위와 같은 코드는 흔하지만,
printName으로 전달되는 인수 personundefined값이라면?

undefined에 점 표기법으로 프로퍼티에 접근하려면 에러 발생

매개변수로 객체를 받을 거라고 생각했는데, undefined값을 받는 상황은
실제로 서비스를 개발하다 보면 정말 자주 발생하는 일


➡️ 그래서 이렇게 객체의 특정 프로퍼티에 접근하는 기능을 담고 있는 함수에서는
조건문을 사용해서, person 매개변수의 값이 null이거나, undefined가 아님을 먼저 확인

첫 번째 조건 방식

위와 같이 리팩토링하면

  • 조건식이 점점 길어져서 복잡해지고 가독성을 해침
  • 또한 객체 프로퍼티에 접근하는 함수를 만들 때마다, 복잡한 조건문을 반복적으로 적어야 함

➡️ 비효율적

두 번째 조건 방식

매개변수로 값이 잘 들어오면 조건식을 통과하고,
원하는 로직을 수행


📌 단락평가

AND나 OR 같은 논리 연산식에서 첫 번째 피연산자의 값만으로도 해당 연산의 결과를 확정할 수 있다면,
두 번째 피연산자의 값에는 아예 접근도 하지 않는 특징

예를 들어

let varA = false;
let varB = true;

console.log(varA && varB);

위와 같은 코드가 있다면

var1의 현재 값이 현재 false
➡️ 두 번째 피연산자의 값이 무엇이든, 이 논리 연산의 결과는 무조건 false

근데 이게 왜?

원래 AND는 두 연산자 모두가 true여야 true인데,(즉, 두 연산자의 결과를 모두 봐야 하는데)
둘 중 하나라도 false면, 어차피 최종 결과는 false
이런 식으로 첫 번째 연산자만의 결과만으로도 논리 연산식의 결과를 얻는다는 것

아무튼, 위처럼 첫 번재 피연산자의 값만으로도 연산의 결과를 확정지을 수 있다면,
두 번째 피연산자에는 접근할 필요가 없음
➡️ varB는 꺼내오지도 않음

마찬가지로, OR연산에서도
둘 중 하나라도 true면, 어차피 최종 결과는 true라는 것
첫 번째 연산이 truetrue로 논리 연산을 확정지을 수 있음


위와 같은 특징을 사용하면
조건문을 이용하지 않고도,
특정 상황에서 어떤 함수를 호출하지 않도록 하거나,
어떤 값을 굳이 계산하지 않도록 제한하는 등

아주 다양한 기능 개발 가능

코드로 실습

위 코드만 보면,
returnFalse1의 결과값은 false이고,
returnTrue1의 결과값은 true니까
당연히 false를 출력

함수 실행 여부 확인

위 코드와 결과를 보면
"False 함수"만 출력
➡️ 마치 returnTrue2 함수가 아예 호출 자체가 안 된 것으로 보임

단락평가에 의해
첫 번째 피연산자의 결과만으로 이 논리 연산의 결과를 확정지어서,
두 번째 피연산자를 확인할 필요가 없으니 함수 호출 자체를 생략

위 코드와 결과를 보면
첫 번째 피연산자의 결과만으로는 이 논리 연산의 결과가 확정이 나지 않으므로
두 번째 피연산자까지 호출(확인)
그리고 최종 결과인 false 출력


한편 OR연산을 또 보면

단락평가에 의해
첫 번째 피연산자의 결과만으로 이 논리 연산의 결과를 확장지어서
두 번째 피연산자를 확인할 필요가 없으니 함수 호출 자체를 생략

returnTrue3(첫 번째 피연산자)이 Truthy한 값을 반환하니까,
OR 연산은 이미 true
➡️ returnFalse3(두 번째 피연산자)에는 접근도 하지 않음
returnTrue3에서 반환한 10을 출력

returnFalse3(첫 번째 피연산자)이 Falsy한 값을 반환하니까,
AND 연산은 이미 false
➡️ returnTrue3(두 번째 피연산자)에는 접근도 하지 않음
returnFalse3에서 반환한 undefined 출력

활용 사례(❗️)

객체로 들어온 값이 falsy하면 예외처리

위 코드를 좀 더 쉽게 리팩토링하면

personundefined이면 falsy이기 때문에,
person.name까지 접근하지 않고,
person의 값인 undefined가 출력

person이 존재한다면 truthy이기 때문에,
person.name에도 접근하고,
⭐️ 둘다 true일 경우, 자바스크립트는 두 번째 피연산자를 반환 ⭐️
➡️ person.name이 출력

여기서 더 친절하게 리팩토링하면

만약 persontruthy이면

즉 정상적으로 인수를 전달하면,

(둘다 true일 경우, 자바스크립트는 두 번째 피연산자를 반환하기 때문에)
name에는 person.name이 저장

➡️console.log(name || "person에 값이 없음");에 의해 첫 번째 피연산자가 반환 및 출력

만약 personfalsy(undefined)이면

name에는 undefined가 저장되고(단락평가),

console.log(name || "person에 값이 없음");에 의해 두 번째 피연산자가 반환 및 출력
➡️ 그러니까 위와는 다르게 personundefined여도 좀 더 친절한 디폴트 메시지 제공

단락평가 표로 정리(⭐️)

기억하자

표현식A값B값반환값이유
A && Btruthy(어떤 값이든)BAtruthy라서 B까지 평가
A && Bfalsy(평가 ❌)AAfalsy라서 단락
A ㅣㅣ Btruthy(평가 ❌)AAtruthy라서 단락
A ㅣㅣ Bfalsy(어떤 값이든)BAfalsy라서 B까지 평가

📌 구조분해 할당

✅ 1. 배열의 구조 분해 할당

이 배열에 들어있는 원소들을 각각의 변수에 일일이 할당을 해줘야 한다고 가정하자.
기존의 방식대로면,

이렇게 선언하고 할당하겠지만,
이 방식은 배열의 이름(arr)도 매번 적어야 하고 불편

➡️ 구조 분해 등장


1) 배열처럼 대괄호를 열고,
2) 대괄호 안에 각 변수를 선언
3) 초깃값으로 arr 배열 할당
4) 출력해서 확인

배열에 있는 원소가 순서대로 각 변수에 할당


위와 같이 첫 번째와 두 번째 원소만 할당할 수도 있음


위와 같이 추가로 변수를 선언하여 저장해도, 오류가 발생하진 않지만
undefined가 저장됨


할당할 값이 모자랄 것을 대비해서 기본값을 설정하는 것도 가능


이렇게 배열에 있는 원소를 간결하게, 변수의 순서대로 하나씩 분해해서 할당하는 문법을
배열의 구조 분해 할당이라고 한다.

✅ 2. 객체의 구조 분해 할당

위와 같은 객체가 있을 때,
객체의 프로퍼티를 변수에 할당해서 사용하려면

위와 같이 각각의 프로퍼티를 일일이 할당해주어야 했음

출력해보면 위와 같이 저장되어 있는 것을 확인 가능


구조 분해를 이용해서 프로퍼티를 할당해보자.

단, 객체의 프로퍼티의 키값을 기준으로 저장할 수 있음

객체의 구조 분해 할당은 배열의 구조 분해 할당과는 다르게,
객체를 상징하는 중괄호를 이용한다는 점도 기억할 것


위와 같이 값이 모자란 경우에는 extraundefined가 저장되고,

배열 구조 분해 할당에서 했던 것처럼, 기본값을 설정하는 것도 가능


또한 할당 받는 변수의 이름을 변경할 수 있음

만약 age라는 프로퍼티의 값을 지금처럼 age라는 변수 말고,
myAge라는 변수에 할당 받고 싶은 경우

위와 같이 구조 분해 할당을 하면
myAge라는 변수에 age 프로퍼티의 값이 담김


구조 분해 할당은 함수에 여러 개의 인수를 전달할 때 꽤나 자주 사용됨

✅ 3. 객체 구조 분해 할당 활용

func라는 함수를 호출하면서 인수로 person 객체를 전달하면,

함수 내부에서 person 객체의 프로퍼티를 활용하기 위해
위와 같이 접근해야 했음


그러나 구조분해 할당을 사용하면

위와 같이 사용이 가능

위와 같이 구조분해 할당 형태로 인자로 받을 때의 장점은,

  • 인자의 순서에 의존하지 않음
  • 필요한 속성만 선택적으로 받을 수 있음
  • 인자 이름이 명확해서 코드 가독성이 좋아짐
  • 기본값 지정이 쉬워짐

한 가지 주의해야 할 점은
func(person)처럼 객체를 넘겼을 때만 중괄호와 함께 구조 분해 할당이 가능
즉, func(10)처럼 일반적인 값을 넘기는 건 구조분해가 불가능

또한 ({name, age, hobby, extra})처럼 중괄호를 사용해 구조분해를 명시해야 함


📌 Spread 연산자와 Rest 매개변수

✅ spread 연산자

... 기호로 표기
전개 연산자라고도 함
-> 배열, 문자열, 객체 등과 같은 반복이 가능한 객체의 여러 개의 값을 개별 요소로 분리

스프레드 연산자와 배열

위와 같이, 배열의 인덱스를 사용해서 배열을 다룰 수는 있지만,
원래 배열이란 건 값을 바꿔가며 사용할 수 있는 것임

그래서 위와 같이 사용할 경우 추후 arrA의 값이 갑자기 바뀌거나 삭제되면, arrB 선언 부분도 수정해야 함


위와 같이 코드를 수정하면, arrA의 변경/수정 가능성과 별개로 안정적으로 arrA의 요소를 가져올 수 있음

스프레드 연산자를 쓰지 않으면?

아예 arrB 배열의 하나의 값을 arrA가 차지하게 되어버림

스프레드 연산자와 객체

객체 또한 사용 방식은 배열과 같음

(나한테 익숙하진 않지만)

스프레드 연산자와 함수

스프레드 연산자는 함수를 호출할 때도 이용

함수의 인수로 전달 시, 배열과 스프레드 연산자를 쓰면
배열 요소를 분리하여 함수의 인수로 전달됨

☑️ 매개변수에서 구조 분해 할당과 스프레드 연산자의 차이

함수의 매개변수에 구조 분해 할당하는 과정에선 함수를 호출할 때 전달하는 인수가 1개, 그리고 그 값은 필히 객체!!
반면, 스프레드 연산자를 이용해 인수를 전달하면, 전달 과정에서 인수가 1개가 아닌 여러 개로 나뉘어 전달됨
➡️ 따라서 함수의 매개변수 역시 여러 개 선언

✅ rest 매개변수

나머지 매개변수
... 기호로 표기
➡️ 스프레드 연산자와 반대로, 개별 요소를 배열로 묶음

func처럼 여러 개의 매개변수를 받아야 할 때, 배열 형태로 한 방에 여러 개의 매개변수를 받아옴

매개변수로 사용할 변수의 이름 앞에 ...을 붙이면 rest 매개변수가 됨
➡️ rest 매개변수는 함수에 전달된 인수들은 순차적으로 배열에 저장함
따라서 rest에 저장된 값을 출력하면, 인수로 전달된 값들임

rest 매개변수 사용 시 주의 사항

rest 매개변수와 다른 매개변수를 함께 사용하는 경우,
인수는 순차적으로 왼쪽부터 오른쪽으로 할당

따라서 매개변수 param에는 첫 번째 인수 1이, 나머지 인수들은 순차적으로 변수 rest에 배열로 할당됨


rest 매개변수는 먼저 선언한 매개변수에 할당된 인수를 제외하고 나머지를 모두 배열에 저장
➡️ 반드시 매개변수에서 마지막에 선언되어야 함


📌 원시 타입 vs 객체 타입

자바스크립트의 타입은 원시 타입과 객체 타입으로 나뉨

구분하는 이유

: 원시 타입과 객체 타입은 값이 저장되거나, 복사되는 과정이 서로 다르기 때문

  • 원시 타입: 값 자체로써 변수에 저장되고 복사
  • 객체 타입: 값 자체가 아닌, 값에 접근할 수 있는 주소값인 참조값이라고 불리는 특별한 값이 변수에 저장되고 복사

원시 타입의 예시

1이라는 값을 메모리 어딘가에 저장
그리고 p1이라는 변수를 만들어 해당 메모리 공간을 가리킴
➡️ p1 변수가 그 값을 가지게 됨

그리고 p2를 위해서,
역시, 1이라는 값을 새로운 메모리 어딘가에 저장
p2라는 변수를 만들어, 새로 만든 메모리 공간을 가리킴
➡️ p2 변수가 그 값을 가지게 됨

(사실 실제 자바스크립트 엔진은 메모리 상의 1을 재사용할 수도 있지만, 일단 독립적으로 갖는 값인 건 맞기 때문에 위 그림의 구조로 이해해보자)


만약 위 p2에 2를 재할당하는 경우,

현재 메모리 공간에는 2라는 값이 없기 때문에,
2라는 값을 새로운 메모리 공간에 저장
그리고 p2가 해당 공간을 가리킴

중요한 건,
변수의 값을 변경하더라도,
메모리 공간에 저장되어 있던 값은 실제로 수정되지 않음

대신 변경해야 할 값을 새로운 메모리 공간에 추가적으로 저장하고,
변수가 해당 메모리를 가리키도록 동작

또한, 1이나 2처럼 실제로 메모리 공간에 저장된 원본 데이터 값들을 불변값이라고 함

객체 타입의 예시

객체 o1을 선언하고, 그 안에 여러 값을 저장

o1 같은 객체는, 값을 여러 개 저장하기도 하고
또 저장하는 값의 개수가 늘어났다가 줄어들기도 함
따라서 별도의 메모리 공간(힙 영역)에 값을 저장

그리고 o1에는 그 메모리 공간의 주소(=참조값)를 저장

(사실 그 참조값은, 스택 영역에 저장되지만 그정도는 넘어가자)

위처럼 o2를 추가로 선언하고, o1의 값을 복사하도록 초기화
➡️ 이 두 변수가 모두 같은 참조값을 가리킴

위처럼 객체의 프로퍼티 값을 바꾸게 되면,
원시 타입처럼 새로운 메모리에 값을 할당해서 가리키는 게 아니라,
가리키고 있던 값을 메모리 상에서 수정

➡️ o2의 값만 바뀌는 게 아니라, o1의 값도 함께 바뀜

위처럼 객체 타입의 값은,
원시타입과는 다르게 메모리 상에서도 수정되기 때문에
가변값이라고 부름

원시 타입과 객체 타입의 비교

객체 타입을 다룰 때 주의할 점

1. 의도치 않게 값이 수정됨

위처럼 o2name 프로퍼티를 바꾸게 되면
o1name 프로퍼티의 값도 함께 바뀜

이처럼 o1의 값도 함께 수정되는 걸 의도하지 않았거나,
수정되었다는 사실 자체를 모르고 있을 경우에
➡️ 큰 오류가 발생할 수도 있음

이처럼 우리가 의도하지 않았는데,
하나의 변화가 또 다른 변수의 변화를 가져오는 것 = Side Effect

그럼 원시 타입처럼, 객체 타입을 복사하고 싶으면?

let o2 = o1;처럼 대입 연산자로 해당 변수(객체)의 이름을 써서,
변수의 참조값 자체를 복사하는 게 아니라,

새로운 객체를 생성하고, 그 내부에 스프레드 연산자를 이용해서,
새로운 객체를 생성하는 동시에 내부 프로퍼티를 따로 복사하는 방식으로 객체를 복사

o2는 아예 새로운 객체를 생성해서 값을 저장하기 때문에,
오른 쪽 메모리 상에서도 새로운 참조값에 새로운 객체로 따로 저장되는 걸 확인

이제 아예 다른 객체이므로(다른 참조값을 가지므로)
o2의 값이 바뀌어도, o1의 값은 변경되지 않음
➡️ 좀 더 안전하게 객체의 값 수정 가능

  • 객체의 참조값 복사 = "얕은 복사"
  • 새로운 객체를 생성하면서 프로퍼티만 따로 복사 = "깊은 복사"

정리

얕은 복사는 원본 객체가 수정되어 버리는 상황이 발생할 수 있어서,
신경쓰지 않으면 위험한 상황이 발생 가능

깊은 복사는 원본 객체와는 다른 새로운 객체를 만들어내는 방식으로 동작하기 때문에,
원본 객체 자체가 수정될 일이 없어서 비교적 훨씬 안전한 방식

2. 객체 간의 비교는 기본적으로 참조값을 기준으로 이루어짐

(강의자료 가져오기...)

o2는 얕은 복사
➡️ o1o2는 같은 참조값을 갖게 되고, 그렇기 때문에 같은 객체를 가리킴

o3는 깊은 복사
➡️ o3는 새로운 객체이고, 새로운 참조값으로 해당 객체를 가리킴

세 개의 객체를 선언 후, 각각의 객체를 비교하면?

o1o3는 프로퍼티의 구조만 보면 같은 객체지만,
객체 간의 비교 연산은 참조값을 기준으로 이루어지기 때문에
참조값이 다른 객체이므로, 다르다고 평가해서 연산의 결과가 false


만약 참조값이 아닌, 프로퍼티를 기준으로 o1o3를 비교하고 싶다면,
JSON.stringify같은 내장함수(객체를 문자열로 형변환)를 이용해서,
참조값이 아닌 프로퍼티를 기준으로 비교

3. 배열과 함수도 객체

자바스크립트의 배열과 함수는 특수한 객체이기 때문에,
배열과 함수 또한 일반 객체에 존재하는 프로퍼티와 메서드를 가지고 있음


📌 반복문으로 배열과 객체 순회하기

순회(Iteration)란?

배열, 객체에 저장된 여러 개의 값에 순서대로 하나씩 접근하는 것

배열은 인덱스 기준 / 객체는 프로퍼티 키 혹은 밸류 기준으로 순회 가능

배열 순회하기

1.1 배열 인덱스

length는 배열이 가지는 프로퍼티
= 배열의 길이

배열도 객체이기 때문에 프로퍼티와 메서드를 가질 수 있음

결과

1.2 for of 반복문

arr에 있는 값을 순서대로 꺼내서 item에 저장
-> item의 값에 arr의 값들이 저장되어 있음

결과


인덱스와 for ... of의 방법 다 성능의 차이는 없지만,
인덱스를 이용하는 방식은, 카운터 변수에 인덱스가 저장되기 때문에
for문 안에서 인덱스를 통한 활동을 할 수 있음

객체 순회하기

2.1 Object.keys 사용 (내장함수)

객체에서 key값들만 뽑아서 새로운 배열로 반환

위 배열을 출력한
결과


위 keys 배열을 순회하면서 출력하면,

결과


키와 밸류를 동시에 순회하려면?

결과

2.2 Object.values 사용 (내장함수)

객체에서 value 값들만 뽑아서 새로운 배열로 반환

위 배열을 출력한
결과


위 values 배열을 순회하면서 출력하면?

결과

2.3 for in

in 뒤에 있는 객체의 프로퍼티의 키를 순서대로 앞에 있는 변수에 할당

즉, person의 키를 key에 계속 할당

결과

정리

for of는 배열에만 쓸 수 있고
for in은 객체에만 쓸 수 있는 방식


📌 배열 메서드 1. 요소 조작

0️⃣ 예제 배열

1️⃣ push

원본 배열의 맨 뒤에 새로운 요소를 추가하는 메서드
반환값은 새로운 배열의 길이

기존 배열에 요소 하나가 추가 된 것 확인 가능


여러 개를 push하면 이 또한 순서대로 추가

push 함수의 반환값

push함수는 배열의 새로운 길이를 반환함
배열 그 자체를 반환하지 않음

2️⃣ pop

원본 배열의 맨 뒤에 있는 요소를 제거하고 반환하는 메서드
반환 값은 제거된 요소

pop을 호출한 배열에서는 마지막 요소가 빠져 있고,
pop의 결과값으로는 빠진 요소가 반환

3️⃣ shift

원본 배열의 맨 앞에 있는 요소를 제거하고 반환하는 메서드
반환 값은 제거된 요소

shift를 호출한 배열에서는 맨앞의 요소가 빠져 있고,
shift의 결과값으로는 빠진 요소가 반환

4️⃣ unshift

원본 배열의 맨 앞에 새로운 요소를 추가하는 메서드
반환 값은 새로운 배열의 길이

unshift함수는 배열의 새로운 길이를 반환
(아무래도 추가하고 나니까 그런가...?)
배열 그 자체를 반환하지 않음

☑️ 참고

shiftunshiftpushpop보다 느리게 동작함

왜냐하면 배열이라는 건 인덱스와 함께 순차적으로 자료를 저장하는 자료형

push 메서드처럼 배열의 맨뒤에 새로운 값을 추가하면,
그냥 인덱스를 하나 올려 추가하면 되고,

pop 메서드처럼 배열의 맨뒤에 있는 요소를 제거하면,
뒤에 있는 인덱스를 날려버리면 되지만,

shift 메서드처럼 맨앞에 있는 요소를 제거하거나,
unshift 메서드처럼 배열의 맨앞에 요소를 추가하게 되면
기존 인덱스의 값들을 밀어주거나 당겨주어야 하기 때문에
조금 더 비효율적

(포스팅하다가 생각났는데, shift는 옮기다란 뜻이 있음,
그렇게 이해해보면 좋겠다.)

5️⃣ slice

가위처럼, 배열의 특정 범위를 잘라내서 새로운 배열로 반환

Array.prototype.slice(자르기 시작할 index, 자르기 멈출 index(이 index는 제외))
위처럼 활용

slice로 배열을 잘라도, 원본 배열의 값은 바뀌지 않음


범위를 지정하지 않고,
끝까지 자를 경우에는 두 번째 인수는 생략해도 무관

두 번째 인수를 생략하면, 자동으로 배열의 끝까지 잘라냄


인자값을 -로 주면 뒤에서 자를 수 있음

뒤에서 1개 자름


뒤에서 3개 자름

6️⃣ concat

두 개의 서로 다른 배열을 이어 붙여서 새로운 배열 반환

[1, 2]와 [3, 4]를 합쳐라


📌 배열 메서드 2. 순회와 탐색

1️⃣ forEach

모든 요소를 순회하면서, 각각의 요소에 특정 동작을 수행

forEach 메서드를 호출한 다음,
이 메서드에 인수로 콜백함수를 넣음

➡️ forEach 메서드가 이 배열의 요소를 마치 반복문처럼 순회하면서,
매 반복마다 이 콜백함수를 호출하고,
매개변수로 현재 요소의 값과 현재 반복 카운트(현재 요소의 인덱스)와, 전체 배열의 값 전달

  • item : 현재 요소의 값
  • idx : 현재 요소의 인덱스
  • arr1 : 전체 배열의 값

console.log(idx, item * 2);
각 요소의 인덱스와, 그 값을 2배 한 결과를 출력

0번째: 1에 2를 곱해서 2 ➡️ 0 2 출력
1번째: 2에 2를 곱해서 4 ➡️ 1 4 출력
2번째: 3에 2를 곱해서 6 ➡️ 2 6 출력

따라서 이 콜백함수는 배열의 요소의 개수만큼 호출
그런데, 기존 배열 arr1은 원래의 모양 유지

따라서 forEach 메서드를 이용하면,
배열의 모든 요소를 한 번씩 순회하면서
콜백함수로 해당 요소를 이용해서 무언가 동작을 수행하도록 만들 수 있음

let doubledArr = [];
결과를 담을 빈 배열 선언

arr1.forEach(...)
arr1의 각 요소(item)를 하나씩 순회

doubledArr.push(item * 2);
콜백함수 내의 로직
각 요소의 값을 두 배로 계산한 뒤 doubleArr에 추가

따라서 doubleArr에 계산된 요소들이 저장

2️⃣ includes

배열에 특정 요소가 있는지 확인

let isInclude1 = arr2.includes(3);
배열 arr2에 3이란 값이 있는지 판단해서, 그 결과(True/False)를 isInclude1에 저장

그래서 True가 저장


let isInclude2 = arr2.includes(10);
배열 arr2에 10이란 값이 있는지 판단해서, 그 결과(True/False)를 isInclude2에 저장

그래서 False가 저장

3️⃣ indexOf

특정 요소의 인덱스(위치)를 찾아서 반환

includes -> 포함되었냐, 아니냐
indexOf -> 포함된 위치

포함 시

배열 arr3에 2이란 값이 있는지 판단해서, 있으면 그 요소의 인덱스를 index에 저장

찾으려는 인덱스가 여러 개 포함 시

배열 arr3_2에 2이란 값이 있는지 판단해서, 있으면 그 요소의 인덱스를 index2에 저장
근데 찾으려는 인덱스가 여러 개 있을 경우,
indexOf는 배열의 맨 앞에서부터 탐색을 시작하기 때문에
가장 첫 번째로 찾아낸 요소의 인덱스를 반환하게 됨

미포함 시

배열 arr3_2에 2이란 값이 있는지 판단해서,
없으면 존재하지 않는다는 의미로 -1 반환

4️⃣ findIndex

배열을 순회하면서, 가장 처음으로 콜백 함수를 만족하는 요소의 위치를 찾아 반환

예시


여러 개의 요소가 콜백 함수를 만족하는 경우, 가장 처음으로 만족하는 요소의 위치 반환


화살표 함수로 간결하게 만들기

조건식을 따로 복사해서,
중괄호 내부를 지우고 조건식만 기재


존재하지 않기 때문에 -1 반환

의의

다만, 특정 조건을 만족하는 요소의 위치를 탐색할 때는
굳이 findIndex까지는 필요 없고, indexOf를 쓰면 됨

findIndex가 존재하는 이유는
indexOf라는 메서드는 배열의 원시 타입의 값이 들어있을 때가 아니라
객체 타입의 값들이 저장된 배열에서는 정확한 요소의 위치를 찾아낼 수가 없기 때문

객체 타입 안의 요소 찾기

두 개의 객체를 저장하는 배열

objectArr 배열에서 name이 안뮤뮤인 객체 값의 위치를
indexOf라는 메서드로 찾으라고 하면 위와 같이 -1 반환

왜냐하면 indexOf는 기본적으로 얕은 비교로 동작
그러나 객체는 참조값을 기준으로 비교하기 때문에
이렇게 프로퍼티를 기준으로는 비교가 이루어지지 않음

콜백함수를 이용해서 itemname이 안뮤뮤인 요소의 위치를 찾아서 반환하라 했기 때문에, 정확한 위치를 찾을 수 있음

결론적으로,

특정 값을 배열에서 찾을 때
indexOf라는 메서드는 무조건 얕은 비교로만 진행하기 때문에 객체 값은 찾지 못하지만,

findIndex는 콜백함수를 이용해서 직접 특정 프로퍼티의 값을 기준으로 비교가 가능

➡️ 복잡한 객체값도 조건식만 잘 만들어주면 쉽게 찾아낼 수 있음

5️⃣ find

모든 요소를 순회하면서 콜백함수를 만족하는 요소를 찾고, 그 요소를 그대로 반환

{ name: "안뮤뮤" } 배열 그자체를 반환


📌 배열 변형

1️⃣ filter

기존 배열에서 조건을 만족하는 요소들만 필터링하여 새로운 배열로 반환

그렇다 보니까, findfindIndex와 비슷한 문법을 가지고 있음

filter 메서드를 이용하면, 이 배열 중 hobby가 "테니스"인 요소들만 필터링해서, 새로운 배열로 반환 가능
➡️arr1의 각 itemhobby프로퍼티가 "테니스"인 item(객체)만 새로운 배열인 tennisPeople에 저장

따라서 tennisPeople을 출력하면 객체 두 개가 출력

콜백 함수를 이용해서 모든 요소들을 순회하면서 조건을 만족하는 값만 새로운 배열로 반환

개인적으로...

웹 서비스를 개발할 때 특정 조건에 의해서 검색하거나,
기능이나 또는 카테고리별 필터 같은 기능을 만드는 데 거의 필수적으로 사용되기 때문에

알아두면 좋을 듯🥹

2️⃣ map

배열의 모든 요소를 순회하면서, 각각 콜백함수를 실행하고, 그 결과값들을 모아서 새로운 배열로 반환

  • item : 현재 요소의 값
  • idx : 현재 요소의 인덱스
  • arr : 전체 배열의 값

그래서 인덱스와 각 요소를 돈다는 걸 확인할 수 있고,
해당 요소의 반환값도 설정 가능

콜백 함수 내부에서 원본 배열을 사용하고 싶은 경우 arr 사용


위와 같이 반환값을 저장할 배열(변수)를 선언하면,
해당 배열에 저장


map 메서드를 이용해 arr1 배열에 있는 객체 값들에서
name 프로퍼티에 있는 값만 뽑아서 새로운 배열로 추출
➡️ item이라는 매개변수를 통해서, 배열의 각 요소를 순회하면서 name 프로퍼티만 반환

3️⃣ Sort

배열을 사전순으로 정렬

해당 배열을 사전순으로 요소를 정렬


단, sort 메서드는
문자열이 아니라 숫자 값으로 이루어진 배열이면, sort 메서드는 정상적으로 동작하지 않음

숫자의 대소 관계를 기준으로 정렬하는 게 아니라,
사전순으로 정렬하는 것이기 때문

따라서 숫자의 대소 관계를 기준으로 정렬하고 싶으면,
sort를 호출하면서,
비교 기준을 설정하는 콜백함수를 함께 넘겨줘야 함

비교하는 두 수 중
뒤의 수를 앞에 보낼 거면, 양수 반환
앞의 수를 앞에 보낼 거면, 음수 반환

앞과 뒤의 수를 부등호로 비교

내림차순은 반대!!!

자세한 로직은... 생략(?)

4️⃣ toSorted

sort와 똑같은 메서드인데,
차이점은 sort처럼 원본 배열을 정렬하는 게 아니고,
정렬한 새로운 배열을 반환

정렬은 하되,
기존 배열의 요소의 순서는 바뀌지 않는 걸 확인할 수 있음

5️⃣ join

배열의 모든 요소를 하나의 문자열로 합쳐서 반환

반환되어 저장된 문자열을 확인하면 합쳐져 있는 것 확인

콤마는 separator, 즉 구분자로
배열의 요소와 요소 사이 들어가는 문자인데,
바꾸고 싶다면 join의 인수로 바꾸고 싶은 구분자를 넣으면 됨

공백을 넣으면 이와 같이 반환됨


📌 Date 객체와 날짜

1️⃣ Date 객체를 생성하는 방법

이렇게 new라는 키워드와 함께 내장함수를 호출해서 생성 가능

위와 같이 날짜 정보를 담은 객체를 생성한 것을 확인

Date 객체를 생성하면서,데이터의 날짜와 시간을 지정할 수 있음
형식은 위와 같게 사용 가능

2️⃣ 타임 스탬프

특정 시간이 "1970.01.01 00시 00분 00초"(협정 세계시, UTC)로부터 몇 ms가 지났는지를 의미하는 숫자값

  • UTC: 세계의 모든 나라가 표준으로 사용하는 시간이 시작되는 지점

date1에 저장되어 있는 시간에 해당하는 타임스탬프 계산해서 반환
➡️ UTC로부터 지금 몇 밀리세컨즈가 지났는지 해당 시간을 ts1에 저장


해당 ts1을 다시 Date를 사용해서 객체 생성 ➡️ date4

date4ts1의 원본(?)이었던 데이터와 비교하면 같은 것 확인

3️⃣ 시간 요소를 추출하는 방법

GET함수를 통해 Date 객체에서 연, 월, 일, 시, 분, 초를 추출할 수 있음

4️⃣ 시간 수정하기

SET함수를 통해 Date 객체의 시간 데이터를 수정할 수 있음

5️⃣ 시간을 여러 포맷으로 출력하기

외... 외워야 하나?
아무튼 이런 식으로 출력할 수 있다.


📌 동기와 비동기

동기적으로 처리

여러 개의 작업이 있을 때 이 작업들을 순서대로 한 번에 하나씩만 처리

이렇게 순차적으로 처리하는 것

예를 들어 은행 창구에서, 한 번에 한 명의 고객만 응대 가능
누군가 이미 창구에 있으면 기다려야 함

(특별한 함수를 사용해서 비정기적으로 동작하도록 설정한 게 아닌 이상)
자바스크립트는 기본적으로 동기적으로 동작
동기적으로 동작하는 코드는 작성된 순서에 따라 작업이 진행되므로 작업의 흐름을 파악하기 쉬움


그런데 오래 걸리는 작업을 빨리 끝날 작업보다 먼저 실행하면 지연 문제 발생

위 코드에서 함수 shortTask는 빨리 끝나는 작업이지만 longTask가 완료되어야 실행 가능

➡️ 따라서 진행할 모든 작업의 속도는 전체적으로 느려짐

이러한 지연 문제를 해결하려면,
앞의 작업과 관계없이 다른 작업을 별도로 진행해야 함

비동기적으로 처리

특정 작업을 다른 작업과 관계없이 독립적으로 동작하게 만드는 것

JAVAC#같은 언어에서는 여러 개의 스레드를 동시에 사용하는 멀티 스레드라는 기법을 활용

➡️ 중간에 오래 걸리는 작업이 포함되어 있어도
해당 작업이 전체 프로그램의 성능을 악화시키는 데 영향을 주지 않음

이처럼 비동기적인 처리는, 동기 방식의 단점을 보완


그러나 자바스크립트 엔진에는 스레드가 하나뿐
비동기적으로 처리하려면, 명시적으로 코드를 작성해야 함

위와 같이 여러 개의 작업이 주어졌을 때,
앞선 작업이 종료되지 않아도 기다릴 필요 없이 다른 작업도 동시에 진행 가능


또한 각각의 작업이 종료되었을 때,
해당 작업의 결과 값을 이용해서 다른 동작을 수행시켜 줘야 한다면,

자바스크립트에서는 각각의 작업에 콜백 함수를 붙여서 처리하는 것도 가능


위와 같은 코드는 그냥 동기적으로 실행


(일단 오류 메시지는 무시...)
3이 2보다 먼저 실행된 것만 보자

setTimeout 함수를 사용해서
원하는 코드를 특정 시간이 지난 이후에 비동기적으로 실행 가능

두 번째 인수로 전달한 숫자 값(ms)만큼 대기했다가,
그 시간이 지나면 첫 번째 인수로 전달한 콜백함수 실행

자바스크립트는 어떻게 비동기처리가 가능한가?

자바스크립트는 싱글 스레드라면서?
어떻게 여러 개의 작업을 동시에 처리?

➡️ setTimeout 같은 비동기 작업은 자바스크립트 엔진에 있는 스레드가 실행하는 게 아니라,

Web APIs라는 브라우저가 직접 관리하는 별도의 영역에서 따로 실행되기 때문

Web APIs

위에서 말했듯이 웹 브라우저가 직접 관리하는 별도의 영역

브라우저에 탑재되어 있는 자바스크립트 엔진은
코드를 한 줄씩 실행하다가 비동기 함수를 만나면,

이 비동기 작업을 브라우저의 Web APIs에게 실행해달라고 요청

그리고 타이머가 끝나면 실행할 콜백함수까지 같이 넘겨줌

이어서, 자바스크립트 엔진은 타이머를 기다리지 않고 아래 작업을 이어서 실행
➡️ console.log("3")을 실행


마지막으로 Web APIs에 있는 타이머가 완료되면,
전달받은 콜백함수를 다시 자바스크립트 엔진에게 돌려줌
➡️ 자바스크립트 엔진은 그제서야 돌려 받은 콜백함수 실행

이런 식으로 비동기 처리가 이루어짐


📌 비동기 작업 처리하기 (1) 콜백함수

아주 간단한 작업 비동기로 처리

위와 같은 형태의 비동기적으로 작업을 처리하는 함수를 좀 더 업그레이드 해보자.


3초 뒤에 3이란 메시지 출력

위 비동기 처리의 결과값인 sum이란 변수를 add 함수 바깥에서도 사용하려면?
➡️ add 함수를 호출할 때, 비동기 처리의 결과 값을 사용하고자 하는 콜백함수를 함께 전달


- add 함수가 호출되면서 setTimeout 함수가 호출
- setTimeout 함수는 이 콜백함수를 3초 뒤에 실행
- 3초 뒤에 실행된 이 콜백 함수에서 sum이란 값을 계산한 다음,
- 매개변수로 받은 콜백 함수를 호출하면서, 계산한 값을 전달

- setTimeout 함수가 끝났을 때,
- 이 콜백함수가 실행되면서 매개변수로 3이라는 값이 들어오게 되고,
- 콘솔에 3이 출력됨

적용

음식을 주문하는 상황

배달 앱으로 음식을 주문하면,
보통 배달 시간이 걸린 후 음식을 받게 됨

orderFood 함수를 호출 = 음식을 주문
➡️ 콘솔에 출력 = 음식을 받음

콘솔에 출력하기까지(음식을 받기까지) 3초 소요

바로 먹으면 뜨거우니까, 식혀서 먹는 상황 + 냉동 시키는 상황

주문을 했는데,
해당 음식을 너무 뜨거워서 좀 식히고,
먹다보니 남겨 냉동시키는 과정

떡볶이를, 식히고, 냉동하려면
계속 같은 떡볶이를 대상으로 작업
보면, 계속해서 이전의 작업 결과를 다른 비동기 작업의 인수로 전달 중

그러나 이런 식으로 콜백함수를 이용해서 받아온 이 비동기 작업의 결과를, 또 다른 비동기 작업의 인수로 넣어주는 코드가 반복이 되면,
➡️ 콜백 함수 안에서 계속 함수를 호출하는 문법으로 코드가 작성됨

이러한 형태를 콜백 지옥이라고 함

이걸 피하려면 Promise라는 비동기 작업을 도와주는 객체를 이용


📌 비동기 작업 처리하기 (2) Promise

Promise란?

비동기 작업을 효율적으로 처리할 수 있도록 도와주는 자바스크립트의 내장 객체

즉, 특수한 목적을 위해 다양한 기능을 추가한 객체

Promise의 상태

  • 대기(Pending) 상태 : 작업을 아직 완료하지 않음
  • 성공(Fulfilled) 상태 : 작업을 성공적으로 완료함
  • 실패(Rejected) 상태 : 작업이 모종의 이유로 실패함

대기 상태에서, 작업을 성공적으로 완료하는 것 ➡️ 해결(resolve)
작업을 해결하면, 해당 작업은 성공 상태

대기 상태에서 작업이 모종의 이유(오류 발생 등)로 실패하는 것 ➡️ 실패(Rejected)
작업이 거부되면, 해당 작업은 실패 상태


예를 들면,

유튜브로 동영상을 시청하는 상황

시청자는 특정 영상을 시청하기 위해 클릭
➡️ 영상 로딩
= 비정기적인 동작 : 영상 로딩은 비정기적인 동작이므로 영상 로딩 중에 다른 영상을 탐색하거나 댓글을 달 수 있음

영상이 로딩 중인 상태 = 대기 상태
영상이 정상적으로 로딩되면(해결) = 성공 상태
영상이 로딩되는 데 실패 하면(거부) = 실패 상태

Promise 객체 만들기

const promise = new Promise(실행 함수);

Promise 객체를 생성하여 상수 promise에 저장

실행 함수란, 비동기 작업을 수행하는 함수
프로미스 객체를 생성함과 동시에 실행되며 2개의 매개변수를 제공받음

Promise 객체를 생성할 때 전달된 인수 안의 함수가 실행 함수
Promise 객체가 생성됨과 동시에 자동으로 이 콜백함수를 호출해서 이 안에 있는 비동기 작업 실행

이 콜백함수를 Executor라고 함

이를 출력해보면
상태는 pending, 대기 상태
결과는 아직 대기 상태이니 undefined

비동기 작업의 상태를 성공으로 변경

실행 함수가 제공받는 2개의 매개변수는 대기 상태의 비동기 작업을 성공 또는 실패 상태로 변경

따라서 위의 코드를 살펴보면,

- Promise 생성자는 즉시 실행
- 콜백 함수도 바로 실행
- setTimeout 내의 로직이 실행되며, 2초 뒤에 "안녕"을 출력
- 비동기 작업을 resolve()를 호출하면서, Promise가 2초 뒤에 fulfilled 상태

3초 후

- setTimeout 콜백 실행 → console.log(promise) 실행
- 2초 시점에 resolve()가 호출되어 상태가 바뀌었으므로, 바뀐 상태(fulfilled)의 Promise 객체가 콘솔에 출력

resolve()에 아무 값도 넘기지 않아 결과값은 undefined
결과 값을 저장하려면 인수로 받아야 함


위 예시와 다르게 resolve() 안에 인수로 결과값을 전달하면, 출력 시 해당 결과값 확인 가능

비동기 작업의 상태를 실패로 변경

비동기 작업을 실패로 변경하면,
오류 메시지가 출력
➡️ Promise 객체의 작업 상태가 실패가 되었고, 오류가 무엇인지 메시지가 나옴

3초 뒤 Promise 객체가 출력

또, 인수로 전달한 메시지가 저장되어 있는 것 확인 가능

- Promise 생성자 함수가 즉시 실행
- 콜백 함수(executor)도 즉시 실행
- setTimeout이 실행되면, 2초 뒤에 num 값을 검사하고 resolve 또는 reject를 호출하도록 예약
(Promise는 아직 pending 상태)
- setTimeout 콜백 실행 → num이 숫자이므로 resolve(num + 10) 호출
- Promise의 상태가 fulfilled(이행) 으로 바뀌고, 결과값은 20

3초 후

- setTimeout 콜백 실행 → console.log(promise) 실행
- 이미 2초 시점에 resolve()가 호출되어 상태가 바뀌었으므로, 바뀐 상태(fulfilled)의 Promise 객체가 콘솔에 출력

결과값과 상태가 출력

비동기 작업 처리

위 코드와 달라진 점은 then 메서드
비동기 작업의 결괏값을 비동기 작업이 아닌 곳에서 이용할 때 사용

then 메서드는 Promisefulfilled 상태가 되는 순간 자동으로 실행

resolve(num + 10)이 호출되면, 그 값을 value로 받아서 출력
➡️ 콘솔에 20 출력


위처럼 reject가 호출되면 then 메서드는 실행하지 않음

그럼 어떤 식으로 처리?
➡️ catch 메서드 처리


이렇게 코드 작성을 하면,
reject 시의 예외처리도 가능

에러 메시지가 출력됨

catch 메서드를 호출하고 있는 이 Promise나, then 메서드가 반환한 Promise는 같은 객체
➡️ 점 표기법으로 catch를 연결해서 사용하는 것도 가능


해당 객체의 성공과 실패했을 때 실행할 로직 설정 가능

위와 같이 thencatch를 연달아 사용하는 건,
체이닝 같아서 Promise Chaining이라 함


위 코드의 로직을 살펴 보면

const p = add10(0);
add10 실행, Promise 생성과 동시에 실행 함수 실행
p에 아직 완료되지 않은 Promise 객체 저장

p의 결과가 성공일 경우 then 메서드 실행
(then은 성공 시 실행되는 콜백을 등록)
해당 결과를 출력 ➡️ 10 출력
그리고 해당 결과를 인수로 add10 실행,
newP에 아직 완료되지 않은 Promise 객체 저장

newP의 결과가 성공일 경우 then 메서드 실행
해당 결과 출력 ➡️ 20 출력

그러나, 자꾸만 then 안에서 then을 쓰고 중첩됨으로서
콜백 지옥 구조가 형성


콜백 지옥 구조를 벗어나기 위해...

then 내부에서 return newP 를 하면,
p.then(...)의 결과값이 newP가 됨
즉 then 자체가 “2초 뒤에 20을 반환하는 프로미스”가 되어버림

따라서 아래처럼
then 메서드를 뒤로 쓸 수 있음

위와 같은 형태가 됨

체이닝을 더 붙이면 아래와 같은 양상을 띄움

앞의 then에서 반환된 프로미스가 다음 then으로 넘어가며 계속 이어지는 전형적인 프로미스 체이닝


마지막으로 혹여나 비동기 작업의 실패를 가정해서

위처럼 중간에, 오류가 발생하면,

마지막 catch 메서드가 실행 되어서,
오류메시지가 출력되는 것 확인 가능


📌 비동기 작업 처리하기 (3) Async & Await

Async

어떤 함수를 비동기 함수로 만들어주는 키워드

함수가 프로미스를 반환하도록 하는 키워드

저 return 문 내의 객체를 그대로 반환하는 게 아니라,
그 객체를 결과값으로 갖는 새로운 프로미스를 반환

이때의 state는 성공
결과는 반환한 객체

위와 같이,
async를 붙이면 아주 간단하게,
동기적으로 작동하는 함수를 비동기 작업을 하는 프로미스를 반환하는 함수로 바꿔줄 수 있음


이렇게 async가 붙어있는 함수가, 일반적인 객체값을 반환하는 게 아니라 애초에 프로미스를 반환하는 함수였다면, 이 프로미스 객체 자체를 반환하도록 함

프로미스 객체 자체를 출력하는 걸 확인

정리하자면 async는 프로미스를 반환하지 않는 함수에 붙여서, 자동으로 해당 함수를 비동기로 작동하도록 변환하는 기능

await

asyncawait과 함께 사용해야 그 위력이 발휘됨
➡️ 비동기 작업을, 동기 작업 처리하듯 간결한 코드로 수행 가능

awaitasync 함수 내부에서만 사용 가능

비동기 함수가 다 처리되길 기다리는 역할

then이 콜백 기반 스타일로 코드를 작성하고 비동기 작업을 처리한다면,
async/await은 동기처럼 직관적임

실제로는 비동기적으로 처리하고 있지만, 코드만 봤을 때는 동기적으로 작업하는 것처럼 보임!

0개의 댓글