
순서가 있는 컬렉션이 필요할 때! -> 배열을 사용하자
두 가지 방법이 있다.
// 첫 번째 방법
let arr = new Array();
// 두 번째 방법
let arr = [];
let fruits = ["사과", "오렌지", "자두"]; // 초기 요소를 넣어줄 수도 있다.
let fruits = ["사과", "오렌지", "자두"];
alert( fruits[0] ); // 사과
alert( fruits ); // 사과,오렌지,자두 <- 요렇게 하면 전체 출력!
//수정
fruits[2] = '배'; // 배열이 ["사과", "오렌지", "배"]로 바뀜
//추가
fruits[3] = '레몬'; // 배열이 ["사과", "오렌지", "배", "레몬"]으로 바뀜
length로 배열 요소 개수 알아내기let fruits = ["사과", "오렌지", "자두"];
alert( fruits.length ); // 3
let arr = [ '사과', { name: '이보라' }, true, function() { alert('안녕하세요.'); } ];
배열 끝
배열 앞
let fruits = ["사과", "오렌지", "배"];
alert( fruits.pop() ); // 배열에서 "배"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.
fruits.push("배"); // 맨 끝에 배 추가
alert( fruits.shift() ); // 배열에서 "사과"를 제거하고 제거된 요소를 얼럿창에 띄웁니다.
fruits.unshift('사과'); // 맨 앞에 사과 추가
스택, 큐
이러한 연산들은 자료구조 스택과 큐로부터 나온 연산이다.
먼저 집어넣은 요소가 먼저 나오는 선입선출 자료구조인 큐로부터push와shift,
가장 나중에 집어넣은 요소가 먼저 나오는 후입선출 자료구조, 스택으로부터push와pop연산을 가져와 두 자료구조를 모두 만들 수 있는 배열에서 모두 사용한다.
배열의 본질은 객체이다. arr[0]와 같이 대괄호를 사용해 접근하는 방식은 객체 문법으로부터 온 것이고, 키가 숫자라는 점만 다르다!
👉 객체와 마찬가지로 참조를 통해 복사된다.
let fruits = ["바나나"]
let arr = fruits; // 참조를 복사함(두 변수가 같은 객체를 참조)
alert( arr === fruits ); // true
arr.push("배"); // 참조를 이용해 배열을 수정합니다.
alert( fruits ); // 바나나,배 - 요소가 두 개가 되었습니다.
자바스크립트 엔진은 배열의 요소를 인접한 메모리 공간에 차례로 저장해 연산 속도를 높인다. 이 방법 이외에도 배열 관련 연산을 더 빠르게 해주는 최적화 기법은 다양하다!
그런데 개발자가 배열을 '순서가 있는 자료의 컬렉션’처럼 다루지 않고 일반 객체처럼 다루면 이런 기법들이 제대로 동작하지 않는다
let fruits = []; // 빈 배열을 하나 만듭니다.
fruits[99999] = 5; // 배열의 길이보다 훨씬 큰 숫자를 사용해 프로퍼티를 만듭니다.
fruits.age = 25; // 임의의 이름을 사용해 프로퍼티를 만듭니다.
차례대로 배열을 저장해 성능을 높여주는 이점을 전혀 활용하지 못하고 있다.
👉 이렇게 임의의 키를 사용해야 한다면 배열보단 일반 객체 {}가 적합한 자료구조일 확률이 높다.
push와 pop은 빠르지만 shift와 unshift는 느리다?
shift 연산은 아래 3가지 동작을 모두 수행해줘야 한다
1. 인덱스가 0인 요소 제거
2. 모든 요소를 왼쪽으로 이동. 인덱스 1은 0, 2는 1, ....로 변환
3. length 프로퍼티 값 갱신
👉 배열에 요소가 많으면 요소가 이동하는데 걸리는 시간이 길고 메모리 관련 연산도 많아진다..!
forfor..oflet fruits = ["사과", "오렌지", "자두"];
// 배열 요소를 대상으로 반복 작업을 수행합니다.
for (let fruit of fruits) {
alert( fruit );
}
for..inlet arr = ["사과", "오렌지", "배"];
for (let key in arr) {
alert( arr[key] ); // 사과, 오렌지, 배
}
❗ 그런데 for..in은 다음과 같은 특징 때문에 배열에서 문제가 발생하므로 지양해야 한다.
1. for..in 반복문은 모든 프로퍼티를 대상으로 순회 => 키가 숫자가 아닌 프로퍼티도 순회 대상에 포함된다.
브라우저나 기타 호스트 환경에서 쓰이는 객체 중, 배열과 유사한 형태를 보이는 ‘유사 배열(array-like)’ 객체가 있다. 유사 배열 객체엔 배열처럼 length 프로퍼티도 있고 요소마다 인덱스도 붙어 있는데 여기에 더하여 유사 배열 객체엔 키가 숫자형이 아닌 프로퍼티와 메서드가 있을 수 있다. 따라서 유사 배열 객체와 for..in을 함께 사용하면 이 모든 것을 대상으로 순회가 이뤄지므로 ‘필요 없는’ 프로퍼티들이 문제를 일으킬 가능성이 생긴다.
for..in 반복문은 배열이 아닌 객체와 함께 사용할 때 최적화되어 있어서 배열에 사용하면 객체에 사용하는 것 대비 10~100배 정도 느리다.length 프로퍼티length : 배열 내 요소 개수가 아니라 가장 큰 인덱스 + 1
✨ length 프로퍼티의 독특한 특징
수동으로 증가시키면 아무 일도 일어나지 않지만, 감소시키면 배열이 잘린다!
let arr = [1, 2, 3, 4, 5];
arr.length = 2; // 요소 2개만 남기고 잘라봅시다.
alert( arr ); // [1, 2]
arr.length = 5; // 본래 길이로 되돌려 봅시다.
alert( arr[3] ); // undefined: 삭제된 기존 요소들이 복구되지 않습니다.
arr.length = 0;으로 배열 비우기도 가능
❗ 배열에 요소가 하나 있고, 이 요소의 인덱스가 아주 큰 정수면 length 프로퍼티도 아주 커진다..! 이런식으로 무의미하게 큰 length가 나오도록 사용하지 말자.
let fruits = [];
fruits[123] = "사과";
alert( fruits.length ); // 124```js
맨 처음에 언급한 new Array에 숫자형 인수 n 하나를 넣어서 호출하면 요소가 없지만 길이가 n인 배열이 만들어진다.
let arr = new Array(2);
alert( arr[0] ); // undefined가 출력됩니다. 요소가 하나도 없는 배열이 만들어졌네요.
alert( arr.length ); // 길이는 2입니다.
❗ new Array(number) 을 이용해 만든 배열의 요소는 모두 undefined이기 때문에이런 특징이 실수를 유발할 수 있다!
👉 new Array의 기능을 잘 알지 않는 한 대부분 대괄호를 써서 배열을 만들자.
배열도 배열의 요소가 될 수 있다.
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
alert( matrix[1][1] ); // 5, 중심에 있는 요소
toString 메서드를 쓰면 요소를 쉼표로 구분한 문자열이 반환된다.
let arr = [1, 2, 3];
alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true
배열엔 Symbol.toPrimitive나 valueOf 메서드가 없다.
따라서 아래처럼 문자열로의 형 변환이 일어난다.
alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"
arr.push(...items) – 맨 끝에 요소 추가arr.pop() – 맨 끝 요소 제거arr.shift() – 맨 앞 요소 제거arr.unshift(...items) – 맨 앞에 요소 추가배열에서 요소를 하나만 지우고 싶을 때
🔎 배열도 객체형이니까 delete 연산자를 사용할 수 있지 않을까?
let arr = ["I", "go", "home"];
delete arr[1]; // "go"를 삭제합니다.
alert( arr[1] ); // undefined
// delete를 써서 요소를 지우고 난 후 배열 --> arr = ["I", , "home"];
alert( arr.length ); // 3
delete obj.key는 key를 이용해 해당 키에 상응하는 값을 지우기 때문에 그 빈 공간을 shift처럼 다른 요소가 채워 들어오는 일은 벌어지지 않는다.
👉 splice사용하기 => 요소 추가, 삭제, 교체가 모두 가능하다
arr.splice(index[, deleteCount, elem1, ..., elemN])
index : 조작을 가할 첫 번째 요소deleteCount : 제거하고자 하는 요소의 개수 ( 0이고 elem만 있으면 추가만 가능! )elem : 추가할 요소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 ) // now ["Let's", "dance", "right", "now"]
✨ splice는 삭제된 요소로 구성된 배열을 반환한다!
let arr = ["I", "study", "JavaScript", "right", "now"];
// 처음 두 개의 요소를 삭제함
let removed = arr.splice(0, 2);
alert( removed ); // "I", "study" <-- 삭제된 요소로 구성된 배열
음수 인덱스
배열 관련 메서드엔 음수 인덱스를 사용할 수 있다. 마이너스 부호 앞의 숫자는 배열 끝에서부터 센 요소 위치를 나타낸다.
"start" 인덱스부터 "end"인덱스 직전까지의 요소를 복사한 새로운 배열을 반환
splice와 차이점?
splice는 원본 배열을 건드리지만,slice는 아니다!
arr.slice([start], [end])
let arr = ["t", "e", "s", "t"];
alert( arr.slice(1, 3) ); // e,s (인덱스가 1인 요소부터 인덱스가 3인 요소까지를 복사(인덱스가 3인 요소는 제외))
alert( arr.slice(-2) ); // s,t (인덱스가 -2인 요소부터 제일 끝 요소까지를 복사)
✨ 인수를 하나도 넘기지 않고 호출하면 배열의 복사본을 만들 수 있고, 기존의 배열을 건드리지 않으면서 배열을 조작해 새로운 배열을 만들고자 할 때 사용된다!!
기존 배열의 요소를 사용해 새로운 배열을 만들거나 기존 배열에 요소를 추가하고자 할 때
arr.concat(arg1, arg2...)
메서드를 호출하면 arr에 속한 모든 요소와 arg1, arg2 등에 속한 모든 요소를 한데 모은 새로운 배열이 반환
let arr = [1, 2];
// arr의 요소 모두와 [3,4]의 요소 모두를 한데 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4]) ); // 1,2,3,4
// arr의 요소 모두와 [3,4]의 요소 모두, [5,6]의 요소 모두를 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6
// arr의 요소 모두와 [3,4]의 요소 모두, 5와 6을 한데 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
객체가 인자로 넘어오면 객체는 분해되지 않고 통으로 복사되는데,
let arr = [1, 2];
let arrayLike = {
0: "something",
length: 1
};
alert( arr.concat(arrayLike) ); // 1,2,[object Object]
특수한 프로퍼티 Symbol.isConcatSpreadable이 있으면 객체를 배열처럼 취급한다. 따라서 객체 전체가 아닌 객체 프로퍼티의 값이 더해진다!
let arr = [1, 2];
let arrayLike = {
0: "something",
1: "else",
[Symbol.isConcatSpreadable]: true,
length: 2
};
alert( arr.concat(arrayLike) ); // 1,2,something,else
주어진 함수를 배열 요소 각각에 대해 실행
arr.forEach(function(item, index, array) {
// 요소에 무언가를 할 수 있습니다.
});
["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
alert(`${item} is at index ${index} in ${array}`);
});
➕ 인수로 넘겨준 함수의 반환값은 무시된다.
arr.indexOf(item, from) : 인덱스 from부터 시작해 item(요소)을 찾기arr.lastIndexOf(item, from) : indexOf 동일한 기능을 하는데, 검색을 끝에서부터 시작한다는 점만 다릅니다.arr.includes(item, from) : 인덱스 from부터 시작해 item이 있는지를 검색➕ 이 메소드들은 완전 항등 연산자 ===을 사용하므로 false를 검색하면 정확히 false만을 검색하지, 0을 검색하지 않는다는 것을 알고 있자.
➕ 요소의 위치를 정확히 알고 싶은게 아니고, 요소의 존재 여부만 확인하고 싶으면 arr.includes를 사용하자. 또, arr.includes는 NaN도 제대로 처리한다는 점에서 위 메소드들과 다르다.
const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (완전 항등 비교 === 는 NaN엔 동작하지 않으므로 0이 출력되지 않습니다.)
alert( arr.includes(NaN) );// true (NaN의 여부를 확인하였습니다.)
findundefined 반환let result = arr.find(function(item, index, array) {
// true가 반환되면 반복이 멈추고 해당 요소를 반환합니다.
// 조건에 해당하는 요소가 없으면 undefined를 반환합니다.
});
item – 함수를 호출할 요소index – 요소의 인덱스array – 배열 자기 자신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
findIndex: find와 동일하나, 조건에 맞는 요소가 있으면 인덱스 반환, 없으면 -1 반환함수의 반환 값을 true로 만드는 단 하나의 요소를 찾는다.
find와 차이점
조건에 맞는 요소 전체를 담은 배열을 반환한다는 점에서 차이가 있습니다.let results = arr.filter(function(item, index, array) { // 조건을 충족하는 요소는 results에 순차적으로 더해집니다. // 조건을 충족하는 요소가 하나도 없으면 빈 배열이 반환됩니다. });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
배열 요소 전체를 대상으로 함수를 호출하고, 함수 호출 결과를 배열로 반환
```js
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6
배열의 요소를 정렬해준다. 원본 배열이 변경된다 => 반환값은 잘 사용하지 않음.
(기본 정렬 순서는 문자열의 유니코드 코드 포인트를 따릅니다.sort)
let arr = [ 1, 2, 15 ];
// arr 내부가 재 정렬됩니다.
arr.sort();
alert( arr ); // 1, 15, 2
요소는 문자열로 취급되어 재 정렬된다. 따라서 사전편집 순으로 진행되어 2는 15보다 큰 값으로 취급되므로("2" > "15") 1, 15, 2와 같은 순서가 나온다.
그래서 이렇게 오름차순 기준으로 정렬할 수 있도록 정렬 기준을 정의해주는 함수를 인자로 넘겨주면
function compareNumeric(a, b) {
if (a > b) return 1;
if (a == b) return 0;
if (a < b) return -1;
}
let arr = [ 1, 2, 15 ];
arr.sort(compareNumeric);
alert(arr); // 1, 2, 15
정렬 과정에서 어떤 요소끼리 비교가 일어났는지 확인하고 싶다면?
[1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); return a - b; });이 코드를 사용하자
정렬 함수는 어떤 숫자든 반환할 수 있다.
양수를 반환하는 경우 첫 번째 인수가 두 번째 인수보다 '크다’를 나타내고, 음수를 반환하는 경우 첫 번째 인수가 두 번째 인수보다 '작다’를 나타내기만 하면 된다!!
이 점을 이용하면 정렬 함수를 더 간결하게 만들 수 있다.
let arr = [ 1, 2, 15 ];
arr.sort(function(a, b) { return a - b; });
alert(arr); // 1, 2, 15
문자열엔
localeCompare를 사용하자!
유니코드를 기준으로 글자를 비교한다. 또,Ö같은 문자가 있는 언어에도 대응하려면 이 메서드를 사용하는게 좋다. (참고 : localeCompare)let countries = ['Österreich', 'Andorra', 'Vietnam'];
alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (제대로 정렬이 되지 않았습니다.)
alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (제대로 정렬되었네요!)
---
#### reserve
요소를 역순으로 정렬. 원본 배열을 변경한다.
```js
let arr = [1, 2, 3, 4, 5];
arr.reverse();
alert( arr ); // 5,4,3,2,1
split : 구분자를 기준으로 문자열을 쪼개서 배열로 바꿔준다.
(구분자가 빈 문자열이면 문자열을 글자 단위로 분리)
let names = 'Bilbo, Gandalf, Nazgul';
let arr = names.split(', ');
for (let name of arr) {
alert( `${name}에게 보내는 메시지` ); // Bilbo에게 보내는 메시지
}
let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); // 두번째 인자는 배열이 길이를 제한해주므로 길이를 넘어서는 요소를 무시한다. 그러나 실무에서 자주 사용하는 기능은 아님.
alert(arr); // Bilbo, Gandalf
join : split과 반대로, 배열 요소를 모두 합친 후 하나의 문자열을 만들어 준다.
배열을 기반으로 값 하나를 도출할 때 사용
let value = arr.reduce(function(accumulator, item, index, array) {
// ...
}, [initial]);
accumulator – 이전 함수 호출의 결과. initial(옵션) - 함수 최초 호출 시 사용되는 초깃값을 나타냄 ( 그러나 초기값이 없을 때 배열이 비어 있는 상태면 에러가 발생하므로 항상 명시해줄 것을 권장. )item – 현재 배열 요소index – 요소의 위치array – 배열let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current) => sum + current, 0);
alert(result); // 1+2+3+4+5 = 15
reduceRight : reduce와 동일한 기능을 하지만 배열의 오른쪽부터 연산을 수행한다는 점이 다른 메서드배열은 독립된 자료형으로 취급하지 않고 객체형에 속한다!
alert(typeof {}); // object
alert(typeof []); // object
따라서 배열 여부를 알아내기 위해서는 isArray를 사용하면 된다.
alert(Array.isArray({})); // false
alert(Array.isArray([])); // true
thisArg함수를 호출하는 대부분의 배열 메서드(find, filter, map 등. sort는 제외)는 thisArg라는 매개변수를 옵션으로 받을 수 있다.
thisArg는 func의 this가 된다.
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
위 예제에서 thisArgs에 army를 지정하지 않고 단순히 users.filter(army.canJoin)만 사용했다면, army.canJoin은 단독 함수처럼 취급되고, 함수 본문 내 this는 undefined가 되어 에러가 발생한다.
정말 볼 때마다 감탄을 하고 갑니다... 자스 스터디 스장으로서 아주 뿌듯하여요 ㅎㅎ