Skip to content
This page has been auto-translated and may contain errors.View in English

배열

docs.scrimba.com

쇼핑 카트, 점수 목록, 단체 채팅의 모든 사람의 이름을 저장해야 한다면 어떻게 할까요? 하나의 변수는 하나의 값을 보유하므로 이런 식으로 목록을 저장하면 price1, price2, price3 같은 별도의 변수들이 쌓이게 되고, 이들을 반복 처리하거나 개수를 세는 깔끔한 방법이 없습니다. 배열이 그 해결책입니다. 배열은 하나의 이름 아래에 순서대로 정렬된 값의 목록을 보유하며, 자신의 길이를 알고 있으므로 항목을 추가하고, 모든 항목을 읽으며, 몇 줄의 코드로 전체 목록을 순회할 수 있습니다.

배열 만들고 읽기

**배열**은 순서대로 정렬된 값의 목록입니다. 대괄호로 쓰며, 값들을 쉼표로 구분합니다:

js
const prices = [4, 9, 2, 7];

이것은 하나의 변수 prices가 네 개의 숫자를 담고 있습니다. 하나의 값을 읽으려면 인덱스라고 부르는 위치를 대괄호로 사용합니다. 처음 배울 때 대부분의 사람들을 헷갈리게 하는 점: 세기는 1이 아닌 0부터 시작합니다.

js
const names = ["서울", "부산", "대구"];
console.log(names[0]); // 서울
console.log(names[1]); // 부산

배열에 몇 개의 항목이 있는지 알아보려면 .length를 사용하세요:

js
console.log(names.length); // 3

세기가 0부터 시작하므로, 마지막 항목은 length - 1이 아닌 length에 있습니다. 따라서 여기서 마지막 이름은 names[2]이고, names[3]은 범위를 벗어납니다.

**배열**은 하나의 이름 아래의 순서대로 정렬된 값 모음입니다. 대괄호 리터럴로 생성하며, 0부터 세기 시작하는 인덱스인 위치로 모든 항목을 읽습니다:

js
const prices = [4, 9, 2, 7];
console.log(prices[0]); // 4
console.log(prices[2]); // 2

0부터 시작하는 인덱싱은 대부분의 초기 경계값 오류의 원인이므로, 이제 머릿속에 확실히 해두는 것이 좋습니다: 첫 번째 항목은 인덱스 0에 있고, 마지막은 length - 1에 있습니다.

js
console.log(prices.length); // 4
console.log(prices[prices.length - 1]); // 7, 마지막 항목

존재하지 않는 인덱스를 읽어도 오류가 발생하지 않습니다. prices[10]undefined를 제공하는데, 이는 JavaScript의 "여기에는 아무것도 없습니다"라는 의미입니다. 이 조용한 결과는 편리하지만 때로는 함정이 될 수 있습니다. 인덱스의 오타는 심각한 실패 대신 undefined를 제공하기 때문입니다.

**배열**은 괄호 리터럴로 생성되는 순서가 있고 인덱스로 주소가 지정되는 모음입니다. 항목은 0부터 세기 시작하는 인덱스로 읽으며, .length는 개수를 보고합니다:

js
const prices = [4, 9, 2, 7];
console.log(prices[0]); // 4
console.log(prices.length); // 4
console.log(prices[prices.length - 1]); // 7, 마지막 항목

두 가지 속성을 정확히 지칭할 가치가 있습니다. 먼저, .length는 미리 선언한 고정 크기가 아니라 사용 중인 가장 높은 인덱스를 추적하고 항목을 추가하거나 제거할 때 업데이트됩니다. 둘째, 범위를 벗어난 읽기는 예외를 던지는 대신 undefined를 반환하므로, 경계값 오류는 실수가 발생하는 시점의 예외가 아니라 나중에 undefined가 논리를 통해 흐를 때 나타납니다. 실패를 확실하게 하려면 인덱스를 직접 length와 대조하세요.

배열은 혼합 값 타입([1, "2", true])을 보유할 수 있지만, 잘 작동하는 코드베이스는 거의 항상 배열 하나에 한 종류의 것을 보유합니다. 배열을 읽는 모든 코드가 무엇을 얻고 있는지 알기 때문입니다. 여기서의 일관성은 언어 규칙이 아닌 가독성 결정입니다.

Juno배열 만들고 읽기 배열은 하나의 이름 아래 대괄호에 있는 값의 목록입니다. names[0]으로 위치로 항목을 읽고, 0부터 세기 시작하므로 첫 번째 항목은 [1]이 아닌 [0]입니다. 그 0 때문에 일주일 동안 헷갈렸으므로 목록이 하나 밀려 있을 때마다 확인하세요. .length로 항목을 세고, 마지막 항목은 length - 1에 있습니다.
Juno배열 만들고 읽기 배열은 하나의 이름 아래에 순서대로 정렬된 목록을 보유하며, 0부터 세기 시작하는 인덱스로 항목을 읽습니다. 마지막 항목은 length - 1에 있으며, 대부분의 초기 경계값 오류가 여기에서 발생합니다. 끝을 넘어 읽으면 오류 대신 undefined를 반환하므로, 오타가 난 인덱스는 크게 실패하지 않고 조용히 진행됩니다. 이 점을 명확한 이유 없이 값이 undefined로 나타날 때 기억하세요.
Juno배열 만들고 읽기 괄호 리터럴, 0부터 시작하는 인덱스, 선언한 크기가 아닌 사용 중인 가장 높은 인덱스를 추적하는 .length입니다. 범위를 벗어난 읽기는 예외를 던지는 대신 undefined를 반환하므로, 경계값 오류는 발생 시점보다 나중에 전파됩니다. 실패를 확실하게 하려면 인덱스를 직접 length와 대조하세요. 그리고 배열 하나를 언어가 강제하지 않기 때문이 아니라 배열의 모든 독자가 무엇을 보유하고 있는지 알도록 하기 위해 한 종류의 것을 유지하세요.

배열 변경하기

배열은 변경되도록 만들어졌습니다. 가장 자주 사용하는 두 개는 에서 추가하거나 제거합니다: push는 항목을 추가하고, pop은 마지막 항목을 제거합니다.

js
const cart = ["apples", "bread"];
cart.push("milk");
console.log(cart); // ["apples", "bread", "milk"]
cart.pop();
console.log(cart); // ["apples", "bread"]

일치하는 쌍은 시작에서 작동합니다: unshift는 맨 앞에 추가하고, shift는 첫 번째 항목을 제거합니다.

js
cart.unshift("eggs");
console.log(cart); // ["eggs", "apples", "bread"]

인덱스에 할당하여 항목을 직접 바꿀 수도 있습니다:

js
cart[0] = "bananas";
console.log(cart); // ["bananas", "apples", "bread"]

무언가를 찾으려면, includes는 예 또는 아니오를 알려주고, indexOf는 그것이 어디에 있는지(또는 없으면 -1) 알려줍니다:

js
console.log(cart.includes("apples")); // true
console.log(cart.indexOf("bread")); // 2

초기에 알 가치가 있는 한 가지: const 배열은 여전히 변경될 수 있습니다. const는 전체 새로운 배열로 이름 cart를 재할당하는 것을 막지만, 그것이 가리키는 목록은 여전히 push, pop, 그리고 인덱스 할당을 위해 열려 있습니다.

배열은 제자리에서 수정되도록 만들어졌습니다. 두 쌍의 메서드가 대부분의 작업을 수행하며, 각 끝에서 하나씩:

js
const cart = ["apples", "bread"];
cart.push("milk"); // 끝에 추가
cart.pop(); // 끝에서 제거
cart.unshift("eggs"); // 시작에 추가
cart.shift(); // 시작에서 제거

pushpop은 저렴하고 일반적인 쌍입니다. 상수로 사용합니다. shiftunshift는 맨 앞에 닿으므로, 모든 다른 항목이 슬롯을 위아래로 이동해야 하므로 큰 배열에서는 더 많은 작업을 합니다. 순서가 당신의 선택일 때 끝을 선호합니다.

인덱스에 할당하여 모든 위치를 덮어쓸 수 있으며, 두 가지 방법으로 항목을 찾을 수 있습니다:

js
const names = ["서울", "부산", "대구"];
names[1] = "인천";
console.log(names.indexOf("대구")); // 2, 또는 없으면 -1
console.log(names.includes("서울")); // true

무언가가 존재하는지만 신경 쓸 때는 includes를 선택하고, 위치도 필요할 때는 indexOf를 선택하세요. 둘 다 ===로 비교하므로, indexOf("2")는 숫자 2를 찾지 못할 것입니다.

const 배열은 여전히 완전히 변경 가능합니다: const는 이름을 잠그지, 내용은 아닙니다. cart를 새 배열로 가리킬 수 없지만, 하루 종일 그것이 보유한 배열로 push할 수 있습니다.

배열은 변경 가능하며, 핵심 메서드는 새로운 배열을 반환하는 대신 제자리에서 배열을 변경합니다. push/pop은 끝에서 작동하고, shift/unshift는 맨 앞에서 작동합니다:

js
const cart = ["apples", "bread"];
cart.push("milk"); // 새 길이를 반환하고, cart를 변경
const removed = cart.pop(); // 제거된 항목을 반환하고, cart를 변경

끝과 앞은 비용이 같지 않습니다. pushpop은 하나의 슬롯에 닿습니다. shiftunshift는 모든 나머지 요소를 다시 인덱싱하므로 배열 길이에 대한 선형 작업입니다. 따라서 큰 배열을 반복적으로 shift하는 핫 루프는 조용히 나타나는 것보다 훨씬 더 많은 작업을 하고 있습니다. 순서가 당신의 제어하에 있을 때, 끝에서 변동을 유지하세요.

인덱스에 할당하면 제자리에서 쓰고, 검색은 필요에 따라 나뉩니다: 존재 여부를 위해 includes, 위치를 위해 indexOf (없으면 -1). 둘 다 엄격한 동등 (===)을 사용하므로, 타입은 중요합니다:

js
const ids = [1, 2, 3];
console.log(ids.indexOf("2")); // -1, 문자열은 숫자와 일치하지 않음
console.log(ids.includes(2)); // true

const 지점은 정확히 명시할 가치가 있습니다. 참조 모델이 처음으로 나타나는 곳이기 때문입니다: const는 이름을 하나의 배열에 바인딩하고 재바인딩을 금지하지만, 배열의 내용에 대해서는 아무것도 말하지 않습니다. cart.push(...)는 이름이 가리키는 객체를 변경하는데, 이는 cart = [...]와 다른 작업입니다. 그 구별이 다음 섹션의 전체 주제입니다.

Juno배열 변경하기pushpop은 끝에서 추가하고 제거합니다. unshiftshift는 시작에서 동일하게 합니다. cart[0] = "bananas"처럼 인덱스에 할당하여 항목을 교체하고, includes 또는 indexOf를 사용하여 하나를 찾으세요. 나를 놀라게 한 부분: const 배열은 여전히 변경될 수 있습니다. const는 전체 배열을 새것으로 바꾸는 것만 막기 때문입니다.
Juno배열 변경하기 두 쌍이 대부분의 편집을 포함합니다: 끝에서 push/pop, 시작에서 shift/unshift. 앞쪽 쌍은 모든 다른 항목을 이동시키므로 순서가 당신의 것일 때 끝을 선호합니다. includes는 존재 여부를 확인하고, indexOf는 위치를 제공하며, 둘 다 ===로 일치하므로 "2"는 숫자 2를 찾지 못할 것입니다. 그리고 const는 이름을 잠그고 내용은 잠그지 않으므로, const 배열로 하루 종일 push할 수 있습니다.
Juno배열 변경하기 이 메서드들은 제자리에서 변경합니다: push/pop은 하나의 슬롯에 닿고, shift/unshift는 전체 배열을 다시 인덱싱하므로, 계속 큰 배열을 shift하는 루프는 매번 선형 작업을 하고 있습니다. includesindexOf는 둘 다 ===를 사용하므로, 문자열 인덱스는 숫자와 일치하지 않을 것입니다. 그리고 const는 이름을 재바인딩하는 것만 금지합니다. cart.push()로 배열을 변경하는 것은 cart = []와는 다른 것입니다. 그 갭이 정확히 다음에 오는 참조 이야기입니다.

배열을 반복하기

일단 목록이 있으면, 보통 모든 항목으로 무언가를 하고 싶습니다. 배열을 읽는 가장 깔끔한 방법은 for...of입니다. 이는 한 번에 하나의 항목을 제공합니다:

js
const prices = [4, 9, 2, 7];
for (const price of prices) {
  console.log(price);
}
// 4
// 9
// 2
// 7

인덱스나 카운터를 관리하지 않습니다. for...of는 각 값을 순서대로 제공하고, 당신이 원하는 대로 이름을 짓습니다 (price). 이미 루프를 만났다면, 이것이 배열에 가장 친화적인 것입니다.

또한 forEach가 있으므로, 각 항목마다 한 번 작은 함수를 실행합니다:

js
const names = ["서울", "부산", "대구"];
names.forEach(function (name) {
  console.log("안녕하세요 " + name);
});

둘 다 전체 배열을 순회합니다. 깔끔하게 읽으려면 for...of를 선택하세요. 배열을 변환할 때 배우게 될 forEach와 그 친척들을 다시 만날 것입니다.

배열을 읽는 현대적 기본값은 for...of입니다. 관리할 인덱스가 없고 경계값 오류의 위험도 없이 각 값을 직접 제공합니다:

js
const prices = [4, 9, 2, 7];
let total = 0;
for (const price of prices) {
  total = total + price;
}
console.log(total); // 22

값을 순서대로 원하고 깔끔한 코드를 원할 때 for...of를 사용하세요. 인덱스도 필요하면 (예를 들어, 목록을 번호 매기려면), 전형적인 카운팅 for 루프 또는 forEach의 두 번째 인수가 이를 제공합니다.

forEach는 콜백, 당신이 제공하는 함수를 항목당 한 번 실행합니다:

js
const names = ["서울", "부산", "대구"];
names.forEach((name) => {
  console.log("안녕하세요 " + name);
});

forEach는 항목당 부작용을 위해 로깅처럼 잘 읽혀집니다. 이것이 할 수 없는 것은 일찍 break하는 것이므로, 중간에 멈춰야 할 때는 for...of를 사용하세요. 이전 배열에서 새 배열을 만드는 것은 배열 메서드에서 다루는 자신의 도구를 가진 다른 작업입니다.

평문 읽기를 위해, for...of는 관용적 선택입니다: 값을 직접 반복하고, 인덱스 산술을 우회하며, breakcontinue를 지원합니다:

js
const prices = [4, 9, 2, 7];
let total = 0;
for (const price of prices) {
  if (price > 8) continue;
  total = total + price;
}
console.log(total); // 13, 9를 건너뜀

forEach는 요소당 콜백을 취하고 부작용을 위해 깔끔하게 읽혀지지만, 이것을 표준화하기 전에 알 가치가 있는 두 가지 제약이 따릅니다. 이것은 break를 할 수 없으므로 항상 끝까지 실행되고, 반환된 값을 무시하므로 결과를 생산하려고 할 때 잘못된 도구입니다. 이를 위해, 변환하는 메서드 (map, filter, reduce)가 존재하고, 배열 메서드는 그들이 속하는 곳입니다.

js
const names = ["서울", "부산", "대구"];
names.forEach((name, index) => {
  console.log(index + ": " + name);
});

콜백의 두 번째 인수는 인덱스이며, 수동 카운터 없이 위치를 복구하는 방법입니다. 경험의 법칙: 제어 흐름이 필요하거나 외부 변수로 축적하고 있을 때는 for...of, 항목당 부작용이 더 명확하게 읽힐 때는 forEach, 변경이나 로그 대신 새 배열을 원할 때는 변환하는 메서드를 선택하세요.

Juno배열을 반복하기for...of는 배열을 깔끔하게 순회하는 방법입니다: 각 항목을 순서대로 제공하고 인덱스에는 절대 닿지 않습니다. forEach는 항목마다 작은 함수를 한 번 실행하는데, 이것도 편합니다. 둘 다 전체 목록을 거치므로, 직진하며 읽으려면 for...of를 선택하세요. 배열을 변환할 때 더 많은 배열 도구를 보게 될 것입니다.
Juno배열을 반복하기for...of는 배열을 읽는 기본값입니다: 순서대로 값, 관리할 인덱스 없음, 그리고 일찍 멈춰야 할 때 break가 작동합니다. forEach는 항목당 콜백을 실행하고 로깅처럼 부작용을 위해 잘 읽혀지지만, break를 할 수 없으므로 항상 끝까지 실행됩니다. 이전 배열에서 새 배열을 만드는 것은 별도의 작업이며 자신의 메서드를 가집니다. 의심스러울 때는 for...of를 선택하세요.
Juno배열을 반복하기for...of는 값을 직접 반복하고 breakcontinue를 지원하므로, 제어 흐름이 필요하거나 외부 변수로 축적하고 있을 때마다 이깁니다. forEach는 항목당 부작용을 위해 깔끔하게 읽혀지지만 break를 할 수 없고 반환 값을 무시하므로, 결과를 생산하려고 할 때 잘못된 도구입니다. 변경이나 루프 대신 새 배열을 원할 때, 변환하는 메서드를 선택하세요. 그 두 번째 콜백 인수는 위치가 필요하면 인덱스를 제공합니다.

배열은 참조로 보유됩니다

여기 사람들을 놀라게 하는 행동이 있으므로, 초기에 볼 가치가 있습니다. 배열 변수를 다른 이름에 복사하면, 두 번째 배열을 얻지 못합니다. 두 이름이 같은 것을 가리킵니다.

js
const original = [1, 2, 3];
const copy = original;
copy.push(4);
console.log(original); // [1, 2, 3, 4]

copy로 push하면 original이 변경되었습니다. 왜냐하면 거기에는 항상 하나의 배열만 있었기 때문입니다. =는 두 번째 이름에 같은 목록을 주었지, 그것의 신선한 사본이 아닙니다. 이것이 사람들이 배열이 참조로 보유된다고 말하는 의미입니다: 변수는 목록의 포인터를 보유하지, 목록 자체를 보유하지 않습니다.

실제의 별도 사본을 얻으려면, ...를 사용하여 배열을 새로운 배열로 펼쳐 놓으세요:

js
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3], 건드리지 않음

숫자 또는 문자열은 으로 보유됩니다: 변수를 복사하면 값을 복사합니다. 배열은 다릅니다. 이것은 참조로 보유되므로, 변수를 복사하면 포인터를 복사하고, 두 이름이 같은 배열을 목표로 합니다.

js
const original = [1, 2, 3];
const shared = original;
shared.push(4);
console.log(original); // [1, 2, 3, 4], 같은 배열

이것은 앞서 const 주석 뒤의 같은 사실입니다: 배열은 객체이고, 변수는 그 객체의 참조를 보유하지, 객체 자체를 보유하지 않습니다. 어느 쪽 이름을 통해 변경하면 그들이 공유하는 하나의 배열을 변경합니다.

독립적인 사본을 원할 때, 배열을 새 배열로 펼쳐 놓으세요. ...는 각 항목을 가로질러 복사합니다:

js
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3], 별도의

이것은 얕은 사본입니다: 새 배열은 자신의 목록이지만, 항목 자체가 배열이었다면, 두 복사본은 여전히 그 내부 배열을 공유합니다. 평문 숫자와 문자열에서, 얕은 것이 필요한 모든 것입니다.

원시값 (숫자, 문자열, 부울)은 값으로 보유됩니다. 배열은 객체이고 참조로 보유됩니다. 배열을 "포함"하는 변수는 실제로 그에 대한 참조를 보유하므로, 할당은 참조를 복사하고, 그 배열을 가리키는 모든 이름은 모든 변경을 봅니다:

js
const original = [1, 2, 3];
const alias = original;
alias.push(4);
console.log(original); // [1, 2, 3, 4]
console.log(original === alias); // true, 같은 배열

이것이 const와 변경 가능성이 별도의 우려인 이유입니다: const는 참조를 고정하고, 변경은 참조가 가리키는 것을 변경합니다. 또한 불변성을 선호하기 위한 논증입니다. 제자리에서 배열을 변경하는 대신 새 배열을 생성하세요. 공유 변경 가능한 상태는 받은 배열을 변경하는 함수가 조용히 그 참조를 보유하는 모든 다른 홀더를 위해 그것을 변경한다는 의미이며, 추적하기 어려운 버그 클래스입니다. 새 배열을 반환하면 각 호출자의 데이터는 자신의 것을 유지합니다.

펼치는 연산자는 얕은 사본을 만듭니다: 같은 항목이 복사된 새로운 외부 배열입니다.

js
const original = [1, 2, 3];
const copy = [...original];
console.log(copy === original); // false, 다른 배열

얕은이 중요한 단어입니다. 새 배열은 독립적이지만, 내부에 중첩된 배열은 여전히 참조에 의해 공유되므로, 깊은 구조는 펼침보다 더 많이 필요합니다. 원시값의 배열로, 지금까지 당신이 가진 모든 것으로, 펼침은 완전한 사본입니다.

구조를 선택하는 것과 관련해서: 배열은 비슷한 것들의 순서대로 정렬된 목록이 있고 위치나 개수가 중요할 때의 올바른 도구입니다. 가격의 큐, 이름의 수열입니다. 대신 위치 대신 의미 있는 키로 값을 찾으려고 원한다면, 그것이 객체이고, 함수는 이 배열 작업의 많은 부분이 끝나는 곳입니다.

Juno배열은 참조로 보유됩니다=로 배열 변수를 복사하면 두 번째 배열을 만들지 못합니다. 두 이름이 같은 것을 가리키므로, 어느 쪽을 통한 변경이든 둘 다에 나타납니다. 이것이 나를 한 번 이상 물렸습니다. 실제 별도 사본을 원할 때, [...original]로 펼쳐 놓으세요. 당신이 자신의 것을 변경할 수 있는 신선한 배열을 만듭니다.
Juno배열은 참조로 보유됩니다 숫자와 문자열은 값으로 복사됩니다. 배열은 다릅니다. 이것은 **참조**로 보유되므로, const b = a는 하나의 배열을 위한 두 이름을 주고, 어느 쪽을 통한 변경이든 둘 다를 친다는 의미입니다. 이것이 const 배열이 여전히 변경 가능한 이유와 같습니다. 독립적인 사본을 위해 [...original]을 사용하세요. 그것이 얕다는 것을 명심하세요. 이것은 정확히 숫자와 문자열의 배열을 위해 원하는 것입니다.
Juno배열은 참조로 보유됩니다 배열은 참조에 의해 보유되는 객체이므로, 할당은 포인터를 복사하고 모든 이름은 모든 변경을 봅니다. 이것이 const (참조를 고정)가 변경 가능성 (대상을 변경)과 별도인 이유이고, 제자리에 변경하는 대신 새 배열을 선호하기 위한 경우입니다: 받은 배열을 변경하는 함수는 그 참조를 보유하는 모든 사람을 위해 그것을 변경합니다. 펼침은 얕은 사본을 만듭니다. 최상위 수준에서는 독립적이지만 중첩된 배열을 공유합니다. 순서와 개수가 중요할 때 배열을 선택하고, 키로 값을 찾으려고 원할 때 객체를 선택하세요.

배열이 여기서 어디로 가는가

배열은 하나의 이름 아래의 순서대로 정렬된 목록을 제공합니다: []로 만들고, 0부터 인덱스로 읽고, pushpop으로 변경하고, for...of로 순회합니다. 이 장을 넘어 전달할 하나의 아이디어는 배열이 참조로 보유되므로, 변수를 복사하면 목록을 중복하는 대신 공유한다는 것입니다.

자연스러운 다음 단계는 하나의 배열을 다른 것으로 변환하는 것입니다. 목록을 필터링하고, 모든 항목을 변환하고, 수동으로 루프를 작성하지 않고 모두 더합니다. 그것이 배열 메서드입니다. 위치 대신 이름으로 값을 찾아야 할 때, 객체가 그 구조입니다.