[JavaScript] 8. 배열 & 반복 더보기

bien·2024년 2월 7일
0

javascript

목록 보기
5/7

"반복"과 "유사 배열 객체"란?

1. Iterable 반복

Technically: 기술적으로

  • iterable 프로토콜을 구현한 객체. @@iterator 메서드를 Symbol.iterator 프로퍼티로 갖는 것.
  • 심볼형(Symbol)은 JavaScript의 특별한 유형의 값인데 지엽적이고 특정한 개념이기에 추후 제대로 살펴볼 것.

To us humans: 일반적으로

  • Objects where you can use the for-of loop.
  • for-of 반복문을 사용할 수 있는 것.

Not every iterable is an array! Other iterables are (for example): NodeList, String Map, Set
모든 iterable이 배열인 것은 아닙니다! 다른 iterables는 NodeList, String,Map 등이 있습니다.

2. Array-like Object 유사 배열 객체

Technically & To us humans

  • Object that have a length property and use indexes to access tiems
  • 크기를 가지고 아이템에 접근하는 것에 배열을 사용하는 객체

Not every array-like object is an array! Other array_likes are (for example: NodeList, String)
모든 array-like 객체가 array인 것은 아닙니다! 다른 array_lie는 다음과 같습니다.(예: NodeList, String)


배열 생성하기

배열을 생성할 수 있는 다양한 방법


색깔은 별 의미 없다

1. 대괄호[] 사용

  • 배열을 만드는 가장 흔한 방법. 틀리는 경우가 없어 권장된다. 기본적으로 이 방법을 사용하고, 특수한 경우 다른 방법을 사용.
  • 대괄호 안에 쉼표로 구분하여 요소 나열 (제일 끝에 쉼표를 추가해도 됨)
const numbers = [1, 2, 3];
console.log(numbers);

2. 생성자 함수 사용

  • new Array();안에 요소를 나열하여 배열을 만드는 방법.
  • 단일 숫자만 입력하는 경우, 요소로 인정되지 않는다.
const moreNumbers = new Array(); // 생성자에 아무것도 넣지 않았다.
console.log('moreNumbers', moreNumbers);

const twoStrings = new Array('Hi', 'Welcome');
console.log('twoStrings', twoStrings);

const twoNumbers = new Array(1, 5);
console.log('twoNumbers', twoNumbers);

cf. Array()에 단일 숫자를 넣는 경우

  • 단일 숫자가 들어있는 배열 대신에, 빈 배열에 입력된 단일숫자 크기의 길이가 조절된 형태로 출력된다.
    • ex. new Array(5);함수는 5개의 empty 배열을 반환한다.
const oneNumber = new Array(5);
console.log('oneNumber', oneNumber);

📒 empty

  • 비어있는 배열을 할당할 때는 undefined가 아닌 empty가 지정된다.
    • 크기가 주어진 빈 배열을 선언할 때
    • 빈 배열을 순회했을 때. (완전히 비어있을 때는 순회조차 하지 않음)

3. new가 없는 생성자 함수 사용

const moreNumbers = Array(5); // [empty * 5]
const moreNumbers2 = Array(1, 5); // [1, 2]
console.log(moreNumbers);
console.log(moreNumbers2);
  • new가 있을때와 동일하게 작동
    • 단일 숫자 입력시, 해당 크기의 빈 배열을 반환하는 것 까지 동일함
  • 배열 생성 자체는 대괄호 표기법으로 생성하는 것이 권장된다. (성능도 더 뛰어남)

4. Array.of

const yetMoreNumbers = Array.of(1, 2);
console.log(yetMoreNumbers);
  • Array.of()
    • 전역에서 사용 가능한 배열 객체에 대한 특별한 메서드
  • 이 방법보다도 대괄호 표기법이 권장됨. (성능 면에서 더 뛰어나다)
  • 다만, 특징 걸이의 배열을 다양한 방식으로 만들어야 하므로, 필요한 경우가 있을 수 있어 알아두면 좋다.

5. Array.from() 📒

The Array.from() static method creates a new, shallow-copied Array instance from an iterable or array-like object.

  • Array.from() 정적 메서드는 인수로 넘겨받은 iterable 또는 유사 배열 객체(array-lie object)로 부터 새롭게 shallow-copied된 배열 객체를 반환한다.
  • 즉, 유사 배열 객체를 배열로 변환하기 위해 사용된다. 인수로 변환할 유사배열 객체를 넘겨주면 된다.

예시1. String to Array

const words = Array.from('Hi!');
console.log(words);
Array.isArray(words);

예시2. DOM요소를 배열로 가져오기

유사 배열 객체를 배열로 교환해주는 것은 아주 유용한 기능인데, DOM의 노드 리스트나 HTML collection에 선택하고 싶은 요소가 있는 경우, 진짜 배열로만 가능한 작업을 수행하려면, 이 Array.from을 이용할 수 있다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Arrays & Iterables</title>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const listItems = document.querySelectorAll('li');
            console.log(listItems);

            const arrayListItems = Array.from(listItems);
            console.log(arrayListItems);
        });
    </script>
</head>
<body>
<ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>
</body>
</html>
  • javascriptdocument.querySelectorAll('li');
    • querySelectorAll()로 리스트 아이템을 전부 선택했다. 이 메서드로 노드 리스트(NodeList)가 호출된다.
  • Array.from(listItems);
    • 노드 리스트를 배열로 변환한다.
      • 이제 배열에서 제공하는 모든 메서드를 사용할 수 있다.

노드 리스트(NodeList)

: 선택한 요소의 컬렉션. 각각의 요소는 노드(Node)로 취급 된다. NodeList는 배열과 유사하나 배열이 아니다. 배열과 달리 NodeList는 일반적으로 요소를 수정하는 데에는 제한적이며, 배열의 메소드와 속성을 모두 사용할 수는 없다.

cf. 여러 인수를 넘기면 예외가 터진다.

const towNumbers = Array.from(1, 2); //Uncaught TypeError: number 2 is not a function
console.log(moreNumbers);

app.js

const numbers = [1, 2, 3];
console.log(numbers);

const moreNumbers = Array(5, 2);
console.log(moreNumbers);

const yetMoreNumbers = Array.of(1, 2);
console.log(yetMoreNumbers);

const towNumbers = Array.from(1, 2);
console.log(moreNumbers);

const words = Array.from('Hi!');
console.log(words);
Array.isArray(words);

const listItems = document.querySelectorAll('li');
console.log(listItems);

const arrayListItems = Array.from(listItems);
console.log(arrayListItems);

배열에 저장 가능한 데이터

  • 배열 내부의 데이터는 큰 유연성을 갖는다. 하위에 나열되는 단일, 혼합, 중첩, 다차원 등 다양한 데이터를 가질 수 있다.
  • 인덱스를 기반으로 하여 0부터 접근 가능하다.

목록

  • 딘일 유형: 문자열 등
  • 혼합 유형
  • 중첩 배열(단일 혹은 혼합 다 가능)
  • 다차원 배열

단일 유형: 문자열

const hobbies = ['Cooking', 'Sports'];

혼합 유형

  • 동일하거나 다른 유형의 데이터를 가진 배열이 될 수 있다.
const personalData = [30.'Max', {moreDetail: []}];

중첩 배열 or 다차원 배열

  • 더 많은 차원을 갖거나 배열 안에 배열을 가질 수도 있다.
  • 배열과 배열을 혼합해도 객체나 문자열인 다른 요소를 가질 수 있다.
    • = 다차원 배열에 혼합 데이터를 가질 수도 있다.
const analyticsData = [[1, 1.6], [-5.4, 2.1]];
const extraAnalyticsData = [[1, 1.6], [-5.4, 2.1], 5];

for (const data of analyticsData) {
	for (const dataPoint of data) {
    	console.log(dataPoint);
	}
}

console.log('-----------------------');

for (const data of extraAnalyticsData) {
	for (const dataPoint of data) {
    	console.log(dataPoint);
	}
}

cf. extraAnalyticsData의 5는 출력되지 않는다.

const extraAnalyticsData = [[1, 1.6], [-5.4, 2.1], 5];의 5는 iterable요소가 아니므로 외부의 반복문으로 해당 객체는 출력되지 않는다.


요소 추가 & 제거

app.js

const hobbies = ['Sports', 'Cooking'];
hobbies.push('Reading'); // 끝에 새로운 요소 추가
hobbies.unshift('Coding'); // 처음에 새로운 요소 추가 (오른쪽으로 이동)
const poppedValue = hobbies.pop(); // 마지막 요소 출력
hobbies.shift(); //
console.log(hobbies);

push()

  • 배열의 끝에 새로운 데이터 추가.
  • 해당 메서드로 값을 받으면 새로운 배열의 길이를 반환한다.

shift()

  • 첫번째 요소가 삭제.
  • shift의 의미: 모든 요소를 왼쪽으로 한 자리 이동시킨다.
    • 따라서 shift() 호출 시 첫번째 요소가 삭제되는 것.
  • 배열의 모든 요소에 영향을 미친다. => 실행이 느리다.

unshit()

  • unshift의 의미: 모든 요소를 오른쪽으로 한 자리 이동시킨다.
  • 배열의 처음에 새로운 요소 추가.
  • 해당 메서드로 값을 받으면 새로운 배열의 길이를 반환한다.
  • 배열의 모든 요소에 영향을 미친다. => 실행이 느리다.

pop()

  • 배열의 가장 마지막 요소 삭제 & 출력.

인덱스: 배열의 특정 요소 접근

배열의 특정 요소에 접근해 값을 조회하거나 변환하고 싶은 경우 인덱스를 통해 배열에 접근할 수 있다. 이 때, 설정하지 않은 인덱스를 접근하면 어떻게 될까?

const hobbies = ['Sports', 'Cooking'];
hobbies[5] = 'Readking';
console.log(hobbies);

마지막 요소로부터 추가된 인덱스까지 비어있는 인덱스가 자동으로 생성된다. 해당 인덱스의 요소를 조회하면 undefiend를 얻는다.
이런 식으로 요소를 추가하는 것은 흔하지 않지만, 존재하지 않는 인덱스를 타겟으로 값을 할당할 수 있다는 점은 알아두자.


배열메서드 & 배열 고차 함수

배열메서드 목록

- isArray
- indexof
- push, pop
- unsift, shfit
- concat, splice, slice
- join
- reverse
- fill
- includes
- flat

📌 배열 고차 함수

  • 고차 함수(Higher-Order Function, HOF) : 함수를 인자로 전달받거나 함수를 반환하는 함수
    • 자바스크립트에서 함수는 일급 객체이므로 함수를 값처럼 인수로 전달할 수 있으며 반환할 수도 있다.
    • 외부 변경이나 가변(mutable) 데이터를 피하고 불변성(immutability)을 지향하는 함수형 프로그램을 기반으로 한다.
  • 함수형 프로그래밍의 패러다임
    • 순수함수(pure function)와 보조함수의 조합을 통해 로직내에 존재하는 조건문과 반복문을 제거하여 복잡성을 제거
    • 변수의 사용을 억제해 상태 변경을 피하려는 프로그래밍
    • 순수 함수를 통해 부수 효과를 최대한 억제하여 오류를 피하고 안정성을 높이려는 노력의 일환
- sort
- forEach
- map
- filter
- reduce
- some
- every
- find
- findIndex
- flatMap

splice()

  • 배열로 모든 것이 가능하도록 하는 메서드.
    • 배열의 요소를 제거, 대체하고 새로운 요소를 추가할 수 있다.
  • 배열 내장 메서드로, iterable 혹은 array-like(배열같은) 객체에 사용할 수 없다. (Array.from으로 변환하고 사용할 것)

사용법

array.splice(startIndex, deleteCount, item1, item2, ...)
  • startIndex: 요소를 추가하거나 제거할 시작 위치를 나타내는 인덱스
    • 이때 인덱스로 음수값도 사용할 수 있는데, 음수 인덱스배열의 끝으로 이동해 오른쪽에서부터 확인한다.
    • ex. array.spliace(-1, 1): 마지막 요소(오른쪽 끝 1개의 요소)를 제거한다.
      • 음수 인덱스는 배열 접근 인덱스에서는 사용할 수 없다.
  • deleteCount: (optioinal) 제거할 요소의 수. 이 값이 0이면 요소를 제거하지 않는다. 이 값이 생략되면 startIndex 이후의 모든 요소가 삭제된다.
  • item1, item2, ...: (optional) 배열에 추가할 요소. 이 값들을 startIndex에 지정된 위치에 추가한다.

반환값

제거한 요소를 담은 배열. 하나의 요소만 제거한 경우 길이가 1인 배열을 반환한다. 아무것도 제거하지 않았으면 빈 배열을 반환한다.

예시

const myArray = [1, 2, 3, 4, 5];

// 1. 요소 추가
myArray.splice(2, 0, 6); // index 2에서부터 아무것도 제거하지 않고 6을 추가한다.
// myArray = [1, 2, 6, 3, 4, 5]
console.log(myArray);

// 2. 요소 제거
myArray.splice(3, 2); // index 3부터 2개의 요소를 제거합니다.
// myArray는 이제 [1, 2, 6, 5]입니다.
console.log(myArray);

// 3. 요소 대체
myArray.splice(1, 2, 'a', 'b', 'c');// index 1부터 2개의 요소를 제거하고 'a', 'b', 'c'를 추가합니다.
// myArray는 이제 [1, 'a', 'b', 'c', 5]입니다.
console.log(myArray);

실행 결과


slice(): 복제하기 & 범위 선택하기

  • 배열의 일부분을 추출하여 새로운 배열을 생성.
  • 원본 배열을 변경하지 않으며, 추출한 부분의 복사본을 반환한다.

사용법

array.slice(start,end);
  • start(optional): (포함)추출을 시작할 인덱스를 나타냄. 이 인덱스에 해당하는 요소도 포함.
  • end(optional): (불포함)추출을 종료할 인덱스

예시1: 배열 (메모리의 주소)

const testResults = [1, 5.3, 1.5, 10.99, -5, 10]
const storedResults = testResults

testResults.push(5.91);

console.log(storedResults, testResults);

testResults에만 값을 추가해도 두 배열이 동일하다.(같이 영향을 받는다) 이는 두 포인터 storedResultstestResults같은 메모리의 위치(배열의 주소)를 가르키고 있기 때문이다.

예시2: slice() 사용

const testResults = [1, 5.3, 1.5, 10.99, -5, 10]
const storedResults = testResults.slice(0, 2);

testResults.push(5.91);

console.log(storedResults, testResults);

slice를 통해서 배열의 일부분을 추출할 수 있다. 0, 1번 인덱스의 값들을 배열로 가져왔다. 이때, 앞서 언급한 것 처럼 storedResultstestResults는 포인터이므로 testResults.slice(0, 2); 과정을 거쳐도 testResults가 가리키는 배열에는 영향을 주지 않는다.


concat(): 배열에 요소 추가

  • 배열에 요소를 추가하고 새로운 배열을 반환. (배열을 합칠 때 유용하다)
    • 즉, 배열에 요소를 추가한 후에 배열을 복사해 반환. (기존의 배열을 조작하지 않는다.)
  • 배열 끝에 요소를 추가해 연결할 수 있도록 한다.
    • push()와 유사
const letters = ["a", "b", "c"];
const numbers = [1, 2, 3];

const alphaNumeric = letters.concat(numbers);
console.log(alphaNumeric);
// results in ['a', 'b', 'c', 1, 2, 3]
  • testResults.push([]): 중첩 배열 생성

📌 배열의 값 조회

  • find(): 제공된 배열에서 제공된 익명함수를 만족하는 첫 번재 요소를 반환한다.
  • findIndex(): 배열에서 찾은 요소의 인덱스가 필요한 경우
  • indexOf() 값의 인덱스를 찾아야 하는 경우 사용한다.
    • 익명함수 대신 배열의 요소가 값과 동일한지 확인한다. (원시값 비교)
  • includes(): 배열에 값이 존재하는지 찾아야 하는 경우
    • 익명함수 대신 배열의 요소가 값과 동일한지 확인한다. (원시값 비교)
  • some(): 제공된 익명함수를 만족하는 요소가 있는지 찾아야 하는 경우

indexOf() & lastIndexOf(): 인덱스 회수

indexOf()

  • 배열에서 주어진 요소를 찾을 수 있는 첫 번째 인덱스를 반환한다.
  • 배열에서 주어진 요소를 찾지 못한 경우 -1을 반환한다.

사용법

indexOf(searchElement)
indexOf(searchElement, fromIndex)

매개변수

  • searchElement
    • 배열에서 위치를 찾을 요소
  • findIndex
    • 검색을 시작할 0 기반 인덱스. 정수로 변환된다.
      • 음수 인덱스는 배열의 끝부터 거꾸로 센다. 즉, fromIndex < 0이면 fromIndex + array.length가 사용된다. 그러나 이 경우에도 배열은 여전히 앞에서 뒤로 검색된다.
      • fronIndex < -array.length이거나 fromIndex가 생략되면 0이 사용되어 전체 배열이 검색된다.
      • fronIndex >= array.length이면, 배열은 검색되지 않고 -1이 반환된다.

반환값

배열에서 searchElement의 첫 번째 인덱스이고, 찾을 수 없으면 -1을 반환한다.

예시

const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];

console.log(beasts.indexOf('bison'));
// Expected output: 1

// Start from index 2
console.log(beasts.indexOf('bison', 2));
// Expected output: 4

console.log(beasts.indexOf('giraffe'));
// Expected output: -1

lastIndexOf()

  • 주어진 값과 일치하는 부분을 fromIndex로 부터 역순으로 탐색, 최초로 마주치는 인덱스를 반환한다.
  • 일치하는 부분을 찾을 수 없는 경우 -1을 반환한다.
const paragraph = "I think Ruth's dog is cuter than your dog!";

const searchTerm = 'dog';

console.log(
  `Index of the last ${searchTerm} is ${paragraph.lastIndexOf(searchTerm)}`,
);
// Expected output: "Index of the last "dog" is 38"

⛔️ 주의사항!

원시값에는 실행되지만 참조값에 관해서는 실행되지 않는다.

객체는 참조값이므로 새 객체를 생성해 indexOf로 보면 이면에서는 indexOf가 같은 값을 조회할 때 해당 객체의 주소와 일치하는 값을 찾지 못하게 된다.

const personData = [{name: 'Max'}, {name: 'Manuel'}];
console.log(personData.indexOf({name: 'Manuel'}));


find() & findindex(): 탐색

indexOf가 실행되지 않는 배열에서 어떻게 객체를 찾을 수 있을까? 이런 상황에 find()findIndex()를 활용할 수 있다.

find()

  • 객체 뿐만 아니라 숫자, 텍스트로 구성된 배열에서도 사용 가능하다.
  • 만족하는 값이 없으면 undefined가 반환된다.

사용 방법

const manuel = personData.find((배열의 단일 객체, 인덱스, 전체배열이름) => {
  	return 실행할 익명 함수(boolean을 반환한다);
});

예시

const personData = [{name: 'Max'}, {name: 'Manuel'}];
const manuel = personData.find((person, idx, persons) => {
	return person.name === 'Manuel';
});

console.log(manuel);

⛔️ 주의사항!

배열의 객체와 동일한 객체를 반환한다. (복사하지 않는다.)

const personData = [{name: 'Max'}, {name: 'Manuel'}];
const manuel = personData.find((person, idx, persons) => {
	return person.name === 'Manuel';
});

manuel.name = 'Anna';

console.log(manuel, personData);

find해서 찾은 객체의 이름 뿐만 아니라 기존의 personData의 객체의 이름도 변경되어 있다. 이처럼 find는 배열안의 객체를 복사하지 않고 동일한 객체를 반환함으로 이를 주의해야한다.

findIndex()

  • find()와 동일한 원리로 작동한다.
  • 넘겨받은 익명 함수에 해당하는 항목의 인덱스를 반환한다.
const personData = [{name: 'Max'}, {name: 'Manuel'}];
const max = personData.findIndex((person, idx, persons) => {
	return person.name === 'Max';
});

console.log(max); // max의 인덱스인 0을 반환한다.

include(): 포함여부 판별

  • indexOf처럼 원시값에 사용.
  • 배열의 특정 요소 포함 여부를 확인하는데 확인에 사용된다.
    • (indexOf()가 전달받은 값을 포함하지 않는 경우 -1을 반환하므로) indexOf() !== -1과 같은 의미
const testResults = [1, 5.3, 10.99, -5];

console.log(testResults.includes(10.99)); // true
console.log(testResults.indexOf(10.99) !== -1); // true

forEach(): 반복문 대안

조건문, 반복문의 문제점

  • 조건문이나 반복문은 로직의 흐름을 이해하기 어렵게 한다.
    • 특히 for문은 반복을 위한 변수를 선언해야 하며, 조건식과 증감식으로 이루어져 있어서 함수형 프로그래밍이 추구하는 바와 맞지 않다.
  • 함수형 프로그래밍
    • 순수함수와 보조함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거
    • 복잡성을 해결하고 변수의 사용을 억제하여 상태변경을 피하는 프로그래밍 패러다임

forEach: 반복문의 대체제

for문 예시

const numbers = [1, 2, 3];
const pows = [];

// for문으로 배열 순회
for (let i = 0; i < numbers.length; i++) {
  	pows.push(numbers[i] ** 2);
}

console.log(pows); // [1, 4, 9];

forEach문으로 변환

const numbers = [1, 2, 3];
const pows = [];

// forEach 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 호출한다.
numbers.forEach(item => pows.push(item ** 2 ));
console.log(pows); // [1, 4, 9]
  • forEach 메서드는 for문을 대체할 수 있는 고차함수다.
    • forEach메서드는 자신의 내부에서 반복문을 실행한다. 즉, forEach 메서드는 반복문을 추상화한 고차 함수로서 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백함수로 전달받아 반복 호출한다.
  • 각 배열 요소에 대해 제공된 함수를 한 번씩 실행한다.

forEach는 원본배열을 변경하지 않는다.

  • forEach 메서드는 원본 배열(forEach 메서드를 호출한 배열, 즉 this)을 변경하지 않는다. 하지만 콜백 함수를 통해 원본 배열을 변경할수는 있다.
const numbers = [1, 2, 3];

// 콜백 함수의 세 번째 매개변수 arr은 원본 배열 numbers를 가리킨다.
// 따라서 콜백 함수의 세 번째 매개변수 arr을 직접 변경하면 원본 배열 numbers가 변경된다.
numbers.forEach((item, index, arr) => { arr[index] = item ** 2; });
console.log(numbers); // [1, 4, 9];

forEach의 반환값은 언제나 undeifiend이다.

const result = [1, 2, 3].forEach(console.log);
console.log(result); // undefined

forEach 메서드 내부 this 객체

class Numbers {
  	numberArray = [];

	multiply(arr) {
      	arr.forEach(function (item) {
        	//TypeError: Cannot read property 'numberArray' of undeifiend
            this.numberArray.push(item * item);
        });
    }
}
const numbers = new Numbers();
numbers.multiply([1, 2, 3]);
  • function 메서드의 콜백 함수는 일반 함수로 호출되므로 콜백 함수 내부의 this는 undeifined를 가리킨다. this가 전역 객체가 아닌 undefined를 가리키는 이유는 클래스 내부의 코든 코드에는 암묵적으로 strict mode가 적용되기 때문이다.

strict mode 사용시 일반 함수의 this

strict mode에서 함수를 일반 함수로 호출하면 this에 undefined가 바인딩된다. 생성자 함수가 아닌 일반 함수 내부에서는 this를 사용할 필요가 없기 때문이다. 이때 에러는 발생하지 않는다.

(function() {
	'use strict';
  function foo() {
    	console.log(this); // undefined
  }
  foo();
  function Foo() {
    	console.log(this); // Foo
  }
  new Foo();
}());
class Numbers {
  	numberArray = [];

	multiply(arr) {
      	arr.forEach(function (item) {
            this.numberArray.push(item * item);
        }, this); // forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달
    }
}
const numbers = new Numbers();
numbers.multiply([1, 2, 3]); // [1, 4, 9]
  • forEach 메서드의 콜백 함수 내부의 this와 multiply 메서드 내부의 this를 일치시키려면 forEach메서드의 두 번째 인수로 forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달한다.

forEach 메서드의 폴리필

// 만약 Array.prototype에 forEach 메서드가 존재하지 않으면 폴리필을 추가한다.
if (!Array.prototype.forEach) {
  	Array.prototype.forEach = function (callback, thisArg) {
      	// 첫번째 인수가 함수가 아니면 TypeError를 발생시킨다.
        if (typeof callback !== 'function') {
         	throw new TypeError(callback + ' is not a function');
        }
      
      // this로 사용할 두 번째 인수를 전달받지 못하면 전역 객체를 this로 사용한다.
      thisArg = thisArg || window;
      
      // for 문으로 배열을 순회하면서 콜백 함수를 호출한다.
      for (var i = 0; i < this.length; i++) {
          // call 메서드를 통해 thisArg를 전달하면서 콜백 함수를 호출한다.
          // 이때 콜백 함수의 인수로 배열 요소, 인덱스, 배열 자신을 전달한다.
          callback.call(thisArg, this[i], i, this);
      }
    };
}
  • 폴리필(ployfll): 최신 사양의 기능을 지원하지 않는 브라우저를 위해 누락된 최신 사양의 기능을 구현하여 추가하는 것.
  • forEach 메서드도 내부에서는 반복문(for문)을 통해 배열을 순회할 수 밖에 없다. 단, 반복문을 메서드 내부로 은닉하여 로직의 흐름을 이해하기 쉽게하고 복잡성을 해결한다.

forEach문은 for문과 달리 break, continue를 사용할 수 없다.

  • 즉, 배열의 모든 요소를 빠짐없이 모두 순회하며 중간에 순회를 중단할 수 없다.

희소 배열의 경우 존재하지 않는 요소는 순회 대상이 아니다.

// 희소 배열
const arr = [1, , 3];

// for문으로 희소 배열을 순회
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]); // 1, undefined, 3
}

// forEach메서드는 희소 배열의 존재하지 않는 요소를 순회 대상에서 제외한다.
arr.forEach(v => console.log(v)); // 1, 3
  • 앞으로 살펴볼 배열을 순회하는 mpa, filter, reduce메서드 등에서도 동일하다.

정리

  • forEach메서드는 for문에 비해 성능이 좋지는 않지만 가독성은 더 좋다.
    • 따라서 요소가 대단히 많은 배열을 순회하거나 시간이 많이 걸리는 복잡한 코드 또는 높은 성능이 필요한 경우가 아니라면 for문 대산 forEach메서드를 사용할 것을 권장한다.

사용법

void forEach(collbackfn(element, index, array);
  • element: 배열에서 처리 중인 현재 요소.
  • index: 배열에서 처리 중인 현재 요소의 인덱스.
  • array: forEach()를 호출한 배열

반환값

  • 없음(undefined)

예시

가격이 들어있는 배열 전부에 세금을 부여하는 상황을 생각해보자!

반복문 사용 예시

const prices = [10.99, 5.99, 3.99, 6.56];
const tax = 0.19;
const taxAdjustedPrices = [];

for (const price of prices) {
  taxAdjustedPrices.push(price * (1 + tax));
}

console.log(taxAdjustedPrices); // [13.0781, 7.1281, 4.7481, 7.806399999999999]
  • const임에도 taxAdjustedPrices에 값을 추가할 수 있다.
    • 이는 taxAdjustedPrices가 참조 유형이고, 상수에 새 값을 할당하지 않고 메모리의 데이터를 변경하기 때문에 가능하다.

forEach 사용 예시

const prices = [10.99, 5.99, 3.99, 6.56];
const tax = 0.19;
const taxAdjustedPrices = [];

prices.forEach((price, index, prices) => {});

console.log(taxAdjustedPrices); // [13.0781, 7.1281, 4.7481, 7.806399999999999]

map(): 데이터 변환

  • map()

    • 자신을 호출한 배열의 모든 요소를 순회하면서 전달받은 콜백 함수를 반복 호출한다.
    • 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.
    • 원본 배열은 변경하지 않는다.
  • forEach() 와의 비교

    • forEach메서드와 map 메서드의 공통점은 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다는 것이다.
    • 하지만 forEach 메서드는 언제나 undefined를 반환하고, map 메서드는 콜백 함수의 반환값들로 구성된 새로운 배열을 반환하는 차이가 있다.
    • 즉, forEach는 단순히 반복문을 대체하기 위한 고차함수이고, map 메서드는 요소값을 다른 값으로 매핑(mapping)한 새로운 배열을 생성하기 위한 고차함수이다.
  • map 메서드가 생성하여 반환하는 새로운 배열의 length프로퍼티 값은 map 메서드를 호출한 length 프로퍼티 값과 반드시 일치한다. 즉, map메서드를 호출한 배열과 map 메서드가 생성해 반환한 배열은 1:1 매핑한다.

사용법

array.map((currentValue, index, array) => {
  // 반환할 값
});
  • currentValue : 배열의 현재 요소. 매핑 함수가 호출될 때 배열의 각 요소를 순서대로 받는다.
  • index : 현재 요소의 인덱스.
  • array : map() 메서드가 호출된 배열 원본

예제

const numbers = [1, 4, 9];

const roots = number.map(item => Math.sqrt(item));
// 위코드는 다음과 같다.
// const roots = numbers.map(Math.sqrt);

// map 메서드는 새로운 배열을 반환한다.
console.log(roots); // [1, 2, 3]
// map 메서드는 원본 배열을 변경하지 않는다.
console.log(numbers); // [1, 4, 9]

https://school.programmers.co.kr/learn/courses/30/lessons/181875


sort() & reverse()

const fruits = ['Banana', 'Orange', 'Apple'];

// 오름차순(ascending) 정렬
fruits.sort();

// sort 메서드는 원본 배열을 직접 변경한다.
console.log(fruits); // ['Apple', 'Banana', 'Orange']

// 내림차순(descending) 정렬
fruits.reverse();

// reverse 메서드도 원본 배열을 직접 변경한다.
console.log(fruits); // ['Orange', 'Banana', 'Applie'];

sort()

  • 배열의 요소를 정렬하는 데 사용한다. 원본 배열을 변형시키고, 정렬된 배열을 반환한다.
  • 각 요소를 문자열로 변환하고 유니코드 코드 포인트 순서에 따라 정렬한다. (한글도)
    • 따라서, 숫자를 정렬 할 경우 주의가 필요하다. (숫자의 크기가 아닌 자리별 숫자 크기를 비교해 판별하게 된다)

사용법

arr.sort([compareFunction]);
  • compareFunction (optional): 정렬 순서를 정의하는 함수. 생략 시 배열은 각 요소의 문자열 변환에 따라 각 문자의 유니코드 코드 포인트 값에 따라 정렬된다.
  • 반환값 : 정렬한 배열. (원 배열이 정렬된다. 복사본이 만들어지는것이 아니다.)
  • compareFunction이 제공되면 배열 오소는 compare 함수의 반환 값에 따라 정렬된다. a와 b가 비교되는 두 요소라면,
    • compareFunction(a, b)이 0보다 작은 경우, a를 b보다 낮은 색인으로 정렬한다.
      • 즉, a가 먼저온다.
    • compareFunction(a, b)이 0을 반환하면, a와 b를 서로에 대해 변경하지 않고 모든 다른 요소에 대해 정렬한다.
      • 참고: 함수의 반환값이 0인 경우의 정렬은 ECMAScript 사양에 명시되어있지 않다. 따라서 자바스크립트 엔진마다 동작이 다를 수 있다.
    • compareFunction(a, b)이 0보다 큰 경우, b를 a보다 낮은 인덱스로 정렬한다.
    • compareFunction(a, b)은 요소 a와 b의 특정 상이 두 개의 인수로 주어질 때 항상 동일한 값을 반환해야 한다. 일치하지 않는 결과가 반환되면 정렬 순서는 정의되지 않는다.
  • 비교 함수의 반환값이 0보다 작으면 비교 함수의 첫 번재 인수를 우선 정렬, 0이면 정렬하지 않고, 0보다 크면 두 번째 인수를 우선 정렬한다.
function compare(a, b) {
  if (a is less than b by some ordering criterion) {
    return -1;
  }
  if (a is greater than b by the ordering criterion) {
    return 1;
  }
  // a must be equal to b
  return 0;
}

📌 숫자 정렬

sort 메서드의 기본 정렬 순서는 유니코드 코드 포인트의 순서를 따른다. 배열의 요소가 숫자 타입이더라도 배열의 요소를 일시적으로 문자열로 변환한 후 유니코드 코드 포인트의 순서를 기준으로 정렬한다.

  • '10'과 '2'를 비교하는 경우,
    • '10'의 유니코드: U+0031U+0030
    • '2'의 유니코드: U+0032
  • 10의 유니코드가 더 앞서므로 , ['10', '2']로 정렬된다.

따라서, 숫자 요소를 정렬할 때는 sort 메서드에 정렬 순서를 정의하는 비교 함수를 인수로 전달해야 한다.

const points = [40, 100, 1, 5, 2, 25, 10];

// 숫자 배열의 오름차순 정렬. 비교 함수의 반환값이0보다 작으면 a를 우선해서 정렬한다.
points.sort((a, b) => a - b);
console.log(points); // [1, 2, 5, 10, 25, 40, 100]

// 숫자 배열에서 최소/최대값 취득
console.log(points[0], points[points.length - 1]); // 1, 100

// 숫자 배열의 내림차순 정렬. 비교 함수의 반환값이 0보다 작으면 b를 우선하여 정렬한다.
points.sort((a, b) => b - a);
console.log(points); // [100, 40, 25, 10, 5, 2, 1]

// 숫자 배열에서 최소/최댓값 취득
console.log(points[points.length - 1], points[0]); // 1, 100

객체를 요소로 갖는 배열의 정렬

const todos = [
	{ id: 4, content: 'JavaScript' },
	{ id: 1, content: 'HTML' },
	{ id: 2, content: 'CSS' }
];

// 비교함수. 매개변수 key는 프로퍼티 키다.
function compare(key) {
	// 프로퍼티 값이 문자열인 경우 - 산술 연산으로 비교하면 NaN이 나오므로 비교 연산을 사용한다.
	// 비교 함수는 양수/음수/0을 반환하면 되므로 - 산술 연산 대신 비교 연산을 사용할 수 있다.
	return (a, b) => (a[key] > b[key] ? 1 : (a[key] < b[key] ? -1 : 0));
}

// id를 기준으로 오름차순 정렬
todos.sort(compare('id'));
console.log(todos);

/*
[
	{id: 1, content: 'HTML'}
	{id: 2, content: 'CSS'}
	{id: 4, content: 'JavaScript'}
]
*/

// content를 기준으로 오름차순 정렬
todos.sort(compare('content'));
console.log(todos);
/*
[
	{id: 2, content: 'CSS'}
	{id: 1, content: 'HTML'}
	{id: 4, content: 'JavaScript'}
]
*/

reverse()

  • 내림차순으로 요소를 정렬하고 싶은 경우, sort() 메서드를 이용해 오름차순으로 정렬한 후 reverse() 메서드를 사용해 요소의 순서를 뒤집으면 된다.

sort 메서드의 정렬 알고리즘

sort 메서드는 quicksort 알고리즘을 사용했었다. quicksort 알고리즘은 동일한 값의 요소가 있을 때 초기 순서와 변경될 수 있는 불안정한 알고리즘으로 알려져있다. ECMAScript 2019(ES10)에서는 timesort 알고리즘을 사용하도록 바뀌었다.


filter()로 배열 필터링하기

filter() 는 배열의 각 요소를 조건에 따라 필터링 하여 새로운 배열을 생성하는 메서드이다. 원본 배열을 변경하지 않고, 조건을 만족하는 요소들로 이루어진 새로운 배열을 반환한다. 배열의 요소를 하나하나 조회하고, 조건에 일치하는 요소만 걸러서 새로 담아 건내준다고 생각하면 좋을 듯.

사용법

const newArray = array.filter(callback(element, index, array), thisArg);
  • callback : 각 배열 요소에 대해 실행되는 함수. 조건을 검사해 true 혹은 false를 반환한다.
    • element : 현재 처리 중인 배열 오소.
    • index (선택적) : 현재 처리 중인 배열 요소의 인덱스
    • array (선택적) : filter를 호출한 배열
  • thisArg (선택적) : callback 함수 내에서 this로 사용될 값.

예시1.

가격 중 6원보다 큰 가격만 추출해보자.

const prices = [10.99, 5.99, 3.99, 6.59];

const filteredArray = prices.filter((price, index, prices) => {
  return price > 6;
});

console.log(filteredArray); // [10.99, 6.59]

화살표 함수를 이용해 filter안의 call back 함수를 더 짧게 단축시킬 수 있다.

const filteredArray = prices.filter(p => p > 6);

예시2.

공백을 기준으로 문자를 분할하고, 포함된 모든 공백을 제거하는 코드.
ex. i love you => ['i', 'love', 'you']

function solution(my_string) {
    return my_string.split(' ').filter(v => v);
}
  • filter(v => v)
    • 공백 문자로 분할된 문자열 배열에서 "거젓이 되는" 요소를 제거하는 목적으로 사용된다. 즉, 빈문자열''을 제거한다.
    • filter메서드는 주어진 함수를 배열의 각 요소에 적용하고, 함수가 true를 반환하는 요소들만 새로운 배열에 포함시킨다.
      • 공백문자열 ''은 거짓(falsy)이므로 filter메서드는 해당 요소를 제거한다.

예시3. filter((_, i) => ...)

filter()메서드는 배열의 각 요소를 순회하며 조건을 만족하는 요소만 걸러내는데 사용되는데, 람다 함수의 첫 번째 매개변수는 현재 요소를 나타낸다. 그러나 현재 요소를 사용하지 않고 인덱스만 사용하는 경우에, 현재 요소를 나타내는 매개변수 대신에 언더스코어(_)와 같은 이름의 더미 매개변수를 사용할 수 있다.

  • 이 경우 _는 현재 요소를 사용하지 않겠다는 의도를 나타낸다.
const numbers = [1, 2, 3, 4, 5];

// 짝수 인덱스만을 선택하여 반환
const evenIndexedNumbers = numbers.filter((_, i) => i % 2 === 0);
console.log(evenIndexedNumbers); // [1, 3, 5]

reduce()

reduce()는 배열의 각 요소에 대해 주어진 콜백함수를 실행하면서 하나의 결과값을 누적하는 메서드다. reduce는 배열을 단일 값으로 축소하는데 유용하며, 다양한 연산에 활용될 수 있다.

prices의 합을 구해보자.

const prices = [10.99, 5.99, 3.99, 6.56];

const sum = 0;
prices.forEach((price) => {
  	sum += price;
});

이 코드를 reduce를 이용해 더 짧게 작성할 수 있다.

const sum = prices.reduce((prevValue, curValue, curIndex, prices) => {
  return prevValue + curValue;
  // 첫번째 연산 0     + 10.99;
  // 두번째 연산 10.99 + 5.99;
}, 0);

사용법

array.reduce(callback[, initialValue]);
  • callback은 4개의 인자를 받는다.
    • accumulator: 누적된 결과 값. (초기값 또는 콜백 함수의 이전 반환값)
    • currentValue: 현재 처리 중인 배열의 요소. (reduce 메서드를 호출한 배열의 요소값)
    • currentIndex: 현재 처리 중인 배열 요소의 인덱스.
    • array: reduce를 호출한 배열 (즉, this)
  • initialValue (option): 최초의 accumulator 값으로 사용된다.
    • 제공되지 않는 경우 배열의 첫 번째 요소가 초기값이 된다.
    • reduce메서드를 호출할 때는 언제나 초기값을 전달하는 것이 안전하다.

예시1

const numbers = [1, 2, 3, 4, 5];

// 배열의 합을 구하는 예제
const sum = numbers.reduce((accumulator, currentValue) => {
    return accumulator + currentValue;
}, 0);

console.log(sum); // 출력: 15

예시2

[프로그래머스] 배열의 원소만큼 추가하기
배열 arr이 주어졌을 때, arr의 원소가 a일 때, 배열의 맨 뒤에 a를 a번 추가하는 일.

function solution(arr) {
	return arr.reduce((list, num) => [...list, ...new Array(num).fill(num)], []);
}

function solution(arr) {
  	return arr.reduce((list, num) => a.concat(Array(c).fill(c), []));
  • 초기값으로 []을 부여해 누적 변수인 accumulator가 배열로 선정되었다.
  • 초기값인 배열이 list면수에 누적되며 concat작업이 반복된다.

Reference

profile
Good Luck!

0개의 댓글