const arr = ['apple', 'banana', 'orange'];
console.log(arr[0]); // apple
console.log(arr[2]); // orange
console.log(arr.length); // 3
// 순회가 가능
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
console.log(typeof arr); // object 타입
// 배열의 생성자 함수는 'Array'
console.log(arr.constructor === Array); // true
// 배열의 프로토타입 객체는 Array.prototype
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
| 구분 | 객체 | 배열 |
|---|---|---|
| 구조 | 프로퍼티 키와 프로퍼티 값 | 인덱스와 요소 |
| 값의 참조 | 프로퍼티 키 | 인덱스 |
| 값의 순서 | X | O |
| length 프로퍼티 | X | O |
const arr = [
'string',
10,
true,
null,
undefined,
NaN,
Infinity,
[],
{},
function (){}
];
// 뭐든 올 수 있다
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
| 종류 | 밀집배열 | 희소배열 |
|---|---|---|
| 장점 | 익덱스로 요소에 빠르게 접근 가능 | 밀집배열보다 인덱스로 접근하는 것이 느리다 |
| 단점 | 요소를 삽입 또는 삭제하는 효율이 좋지 않다 | 요소를 삽입, 삭제하는 경우 밀집배열보다 빠르다 |
[].length // 0
const arr = [1,2,3,4,5]
arr.length // 5
arr.push(6); // 6
arr.pop(); // 5
// 길이를 줄여서 배열의 요소를 삭제할 수 있다.
arr.length = 3;
console.log(arr.length); // 3
console.log(arr); // 1,2,3
// 길이를 늘리면, 길이는 늘어나지만, 실제 요소가 할당되지도 않고, 메모리 공간이 확보되지도 않는다.
arr.length = 5;
console.log(arr.length); // 5
console.log(arr); // 1,2,3, undefined, undefined
// 배열의 중간이 비어 있을 수도 있다
arr.push(6); // 6
console.log(arr.length); // 6
console.log(arr); // 1,2,3, undefined, undefined, 6
const arr = [1,2,3];
const arr2 = [1,,3];
console.log(arr2.length); // 3
console.log(arr2[1]); // undefined
const arr = new Array(10);
console.log(arr); // undefined * 10
console.log(arr.length); // 10
// 배열의 최대 길이는 2^32 -1 이다
new Array(4294967295);
// 넘어서면 에러 발생
new Array(4294967296); // RangeError: Invalid array length
// 음수여도 에러 발생
new Array(-1); // RangeError: Invalid array length
// 전달된 인수가 없는 경우 빈 배열 생성
new Array(); // []
// 인수가 2개 이상이거나, 숫자가 아니면? 인수를 요소로 갖는 배열 생성
new Array(1,2,3); // [1,2,3]
new Array({}); // [{}]
// new 연산자가 없어도 일반 함수가 아닌 생성자 함수로 동작한다. new.target을 확인해서 동작하기 때문(17.2.7절)
Array(1,2,3);
Array.of(1); // [1]
Array.of(1,2,3); // [1,2,3]
Array.of('string'); // ['string']
// 유사 배열 객체를 변환하여 배열 생성
Array.from({ length: 2, 0: 'a', 1:'b'}); // ['a','b']
// 이터러블은 변환하여 배열을 생성
Array.from('Hello'); // ['H','e','l','l','o']
// 길이만 있는 유사 배열 객체 전달 시, undefined 요소로 채운다
Array.from({ length: 3}); // ['undefined', 'undefined', 'undefined']
// 두 번째 인수로 콜백 함수를 전달하면, 그 반환값으로 구성된 배열을 만든다
Array.from({ length: 3}, (_, i) => i); // [0, 1, 2]
// 위 화살표 함수의 동작 과정
// 인자1에는 첫번째 요소 전달, 인자2에는 index 전달
// (undefined, 0) => return 0
// (undefined, 1) => return 1
// (undefined, 2) => return 2
Array.from({ length: 3, 1:'a'}, (i, _) => i); // [undefined, a, undefined]
const arr = [0,,2];
console.log(arr[2]); // 2
console.log(arr[1]); // undefined
console.log(Object.getOwnPropertyDescriptors(arr));
/*
0: Object
configurable: true
enumerable: true
value: 0
writable: true
__proto__: Object
2: Object
configurable: true
enumerable: true
value: 2
writable: true
__proto__: Object
length: Object
configurable: false
enumerable: false
value: 3
writable: true
__proto__: Object
*/
배열은 인덱스를 나타내는 문자열을 프로퍼티 키로 갖는 객체다. 따라서 배열이 undefined를 반환하는 것은 존재하지 않는 프로퍼티 키로 객체의 프로퍼티에 접근했을 때 undefined를 반환하는 것과 완전히 동일.
const arr = [];
// 배열 요소의 추가
arr[0] = 1;
arr['1'] = 2; // 숫자는 문자라도 괜찮다
// 프로퍼티 추가
arr['foo'] = 3;
arr.bar = 4;
arr[1.1] = 5;
arr[-1] = 6;
console.log(arr); // [1,2,foo:3, bar:4, '1.1':5, '-1': 6]
console.log(arr.length); // 2
console.log(arr['foo']); // 3, 키값 던지면 잘 가져오긴 한다
const arr = [1,2,3,4];
arr.splice(1,1); // index 1 부터 1개 제거
console.log(arr); // [1,3,4]
console.log(arr.length); // 3
delete arr[1];
console.log(arr); // [1,undefined, 4]
console.log(arr.length); // 3
배열 메서드
1. 원본 배열을 직접 변경하는 메서드
2. 원본 배열을 직접 변경하지 않고 새로운 배열을 생성하여 반환하는 메서드
const arr = [1];
// push는 원본 배열을 직접 변경
arr.push(2);
console.log(arr); // [1,2]
// concat은 새로운 배열을 생성해 반환
const result = arr.concar(3);
console.log(arr); // [1,2]
console.log(result); // [1,2,3]
// true
Array.isArray([]);
Array.isArray([1,2]);
Array.isArray(new Array());
// false
Array.isArray();
Array.isArray(1);
Array.isArray({});
Array.isArray(null);
Array.isArray(true);
Array.isArray({0:1, length:1});
const foods = ['apple', 'banana', 'orange'];
if(foods.indexOf('orange') === -1) {
foods.push('orange');
}
if(foods.includes('orange')) {
foods.push('orange');
}
console.log(foods); // ['apple', 'banana', 'orange']
const arr = [1, 2];
let result = arr.push(3,4);
console.log(result);// 4, 배열의 길이 리턴
console.log(arr); // 1,2,3,4, 원본 배열이 변경됨
const arr = [1,2];
arr[arr.length] = 3;
console.log(arr); // [1,2,3]
const arr = [1,2];
const newArr = [...arr, 3];
console.log(newArr); // [1,2,3]
const arr = [1,2];
let result = arr.pop();
console.log(result); // 2, 마지막 제거한 요소 반환
console.log(arr); // [1], 원본 배열 직접 변경
// 생성자 함수로 Stack 구현
const Stack = (function () {
function Stack(array = []) {
if (!Array.isArray(array)) {
throw new TypeError(`${array} is not an array.`);
}
this.array = array;
}
Stack.prototype = {
// 인스턴스의 프로토타입이 Object가 아닌 Stack을 가리키도록.. 이게 없으면 자동 생성된 Object의 constructor가 암묵적으로 생성되기 때문
constructor: Stack,
// 스택의 마지막에 데이터 삽입
push(value) {
return this.array.push(value);
},
// 스택의 최신 데이터를 꺼낸다
pop() {
return this.array.pop();
},
// 스택의 사본 배열 반환
entries() {
return [...this.array];
}
};
return Stack;
}());
const stack = new Stack([1,2]);
console.log(stack.entries()); // [1, 2]
stack.push(3);
console.log(stack.entries()); // [1,2,3]
stack.pop();
console.log(stack.entries()); // [1,2]
// 클래스로 Stack 구현
class Stack {
#array; // private class member
constructor(array = []) {
if(!Array.isArray(array)) {
throw new TypeError(`${array} is not an array.`);
}
this.#array = array;
}
push(value) {
return this.#array.push(value);
}
pop() {
return this.#array.pop();
}
entries() {
return [...this.#array];
}
}
const stack = new Stack([1, 2]);
console.log(stack.entries()); // [1,2]
stack.push(3);
console.log(stack.entries()); // [1,2,3]
stack.pop();
console.log(stack.entries()); // [1,2]
const arr = [1,2];
let result = arr.unshift(3, 4);
console.log(result); // 4
console.log(arr); // [3, 4, 1, 2]
const arr = [1, 2];
const newArr = [3, ...arr];
console.log(newArr); // [3,1,2]
const arr = [1,2];
let result = arr.shift();
console.log(result); // 1
console.log(arr); // [2]
// 생성자 함수로 Queue 구현
const Queue = (function () {
function Queue(array = []) {
if (!Array.isArray(array)) {
throw new TypeError(`${array} is not an array.`);
}
this.array = array;
}
Queue.prototype = {
constructor : Queue,
enqueue(value) {
return this.array.push(value);
},
dequeue() {
return this.array.shift();
},
entries() {
return [...this.array];
}
};
return Queue;
}());
const queue = new Queue([1,2]);
console.log(queue.entries()); // [1,2]
queue.enqueue(3);
console.log(queue.entries()); // [1,2]
queue.dequeue();
console.log(queue.entries()); // [2,3]
// 클래스로 Queue 구현
class Queue {
#array;
constructor(array = []) {
if(!Array.isArray(array)) {
throw new TypeError(`${array} is not an array.`);
}
this.#array = array;
}
enqueue(value) {
return this.#array.push(value);
}
dequeue() {
return this.#array.shift();
}
entries() {
return [...this.#array];
}
}
const queue = new Queue([1,2]);
console.log(queue.entries()); // [1,2]
queue.enqueue(3);
console.log(queue.entries()); // [1,2,3]
queue.dequeue();
console.log(queue.entries()); // [2,3]
const arr1 = [1,2];
const arr2 = [3,4];
let result = arr1.concat(arr2);
console.log(result); // [1,2,3,4]
result = arr1.concat(3);
console.log(result); // [1,2,3]
result = arr1.concat(arr2, 5) ;
console.log(result); // [1,2,3,4,5]
// 원본 배열은 그대로
console.log(arr1); // [1,2]
let result = [1,2].concat([3,4]);
console.log(result); // [1,2,3,4]
// 위와 아래 스프레드 문법은 동일 결과
result = [...[1,2], ...[3,4]];
console.log(result); // [1,2,3,4]
const arr = [1,2,3,4];
// index 1에서 2개(2,3) 제거하고, 대신 20, 30을 집어 넣는다.
const result = arr.splice(1,2,20,30);
// 제거한 요소
console.log(result); // [2,3]
// 원본 배열이 변경됨
console.log(arr); // [1,20,30,4]
const arr = [1,2,3,1,2];
function remove(array, item) {
const index = array.indexOf(item);
if(index !== -1) {
array.splice(index, 1);
}
return array;
}
console.log(remove(arr, 2)); // [1,3,1,2], 중복되면 앞의 것 하나만 제거
console.log(remove(arr, 10)); // [1,3,1,2], 없다면 당연히 제거X
const arr = [1,2,3,1,2];
function removeAll(array, item) {
return array.filter(v => v !== item);
}
console.log(removeAll(arr, 2)); // [1,3,1]
const arr = [1,2,3];
arr.slice(0,1); // [1]
arr.slice(1,2); // [2]
arr.slice(1); // [2,3]
arr.slice(-1); // [3]
arr.slice(-2); // [2,3]
console.log(arr); // [1,2,3]
const copy = arr.slice(); // [1,2,3]
console.log(copy === arr); // false, 얕은 복사
slice 메서드, 스프레드 문법, Object.assign 메서드 모두 얕은 복사 수행
=> 깊은 복사를 위해 Lodash 라이브러리의 cloneDeep 메서드 사용 권장
slice 메서드를 이용하면 유사 배열 객체를 배열로 변환 가능 (ES5)
function sum() {
var arr = Array.prototype.slice.call(arguments);
console.log(arr); // [1,2,3]
return arr.reduce(function (pre, cur) {
return pre + cur;
}, 0);
}
console.log(sum(1,2,3)); // 6
function sum() {
var arr = Array.from(arguments);
console.log(arr); // [1,2,3]
return arr.reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1,2,3)); // 6
function sum() {
const arr = [...arguments];
console.log(arr); // [1,2,3]
return arr.reduce((pre, cur) => pre + cur, 0);
}
console.log(sum(1,2,3)); // 6
const arr = [1,2,3,4];
// 기본 구분자는 콤마
arr.join(); // '1,2,3,4'
// 구분자 없음
arr.join(''); // '1234'
// 구분자 :
arr.join(':')// '1:2:3:4'
// 기본적으로 모든 리턴은 문자열
const arr = [1, 2, 3];
const result = arr.reverse();
console.log(arr); // [3,2,1]
console.log(result); // [3,2,1], 반환값도 변경된 배열
// arr과 result는 동일 인스턴스.. 참 쓸데없다🤔
arr[0] = 4;
console.log(arr); // [4,2,1]
console.log(result); // [4,2,1]
const arr = [1,2,3];
arr.fill(0);
console.log(arr); // [0,0,0]
// 두번째 인수는 시작 인덱스
arr.fill(1, 1);
console.log(arr); // [0,1,1]
// 세번째 인수는 종료 인덱스(종료 미포함)
arr.fill(0,1,2);
console.log(arr); // [0,0,1]
const arr = [1,2,3];
arr.includes(2); // true
arr.includes(4); // false
// 두번째 인수는 검사 시작지점
arr.includes(1,1); // false
// 음수도 받는다
arr.includes(3,-1); // true
// indexOf도 비슷
[NaN].indexOf(NaN) !== -1; // false
[NaN].includes(NaN); // true
const arr = [1,[2,3,4,5]];
arr.flat(); // [1,2,3,4,5]
console.log(arr.flat()); // [1,2,3,4,5]
console.log(arr); // [1,[2,3,4,5]]
// 인수로 평탄화 레벨을 받는다. 기본값는 1, Infinity도 받는다
[1,[2,[3,[4]]]].flat(2); // [1,2,3,[4]]
배열 고차 함수
함수를 인자로 전달받거나 함수를 반환하는 함수
const arr = [2,1];
arr.sort(); // [1,2]
const points =[40,100,1,5,2,25,10];
// 오름차순 정렬
points.sort((a,b) => a - b); // a와 b를 비교해서 음수가 나오면 a가 작다는 의미. 그렇다면 a를 우선 정렬한다.
- 객체를 요소로 갖는 배열 정렬
const todos = [
{ id: 4, content: "Javascript" },
{ id: 1, content: "HTML" },
{ id: 2, content: "CSS" }
];
function campare(key) {
// a보다 b가 크면 1, 같거나 작으면 다시 비교
// a보다 b가 작으면 -1, 같으면 0
return (a,b) => (a[key] > b[key] ? 1 : (a[key] < b[key] ? -1 : 0));
}
todos.sort(compare('content'));
console.log(todos); // content를 기준으로 오름차순 정렬
/*
[
{ id: 2, content: "CSS" }
{ id: 1, content: "HTML" },
{ id: 4, content: "Javascript" },
]
*/
9.2 Array.prototype.forEach
- for문을 대체하는 함수
- forEach 메서드의 반환값은 언제나 undefined
- 희소배열에서 존재하지 않는 요소는 아예 순회에서 제외
const arr = [1,2,3];
arr.forEach((item, index, arr) => {
console.log(`요소값: ${item}, 인덱스: ${index}, this: ${arr}`)
});
/*
요소값: 1, 인덱스: 0, this: 1,2,3
요소값: 2, 인덱스: 1, this: 1,2,3
요소값: 3, 인덱스: 2, this: 1,2,3
*/
console.log(arr.forEach(() => 1)); // undefiend
- 첫번째 인자로 함수를 받고, 두번째 인자로 this로 사용할 객체를 받는다. 하지만 위 예제처럼 화살표함수 쓰는게 편리
- 이렇게 좋은 forEach문은 break, continue를 못쓴다😨
- 미련하게도 무조건 처음부터 끝까지 동작한다🤬
[1,2,3].forEach(item => {
console.log(item);
if(item > 1) break; // SyntaxError
}
[1,2,3].forEach(item => {
console.log(item);
if(item > 1) continue; // SyntaxError
}
어차피 forEach도 내부적으로는 for문 쓰니 성능차이도 없을 것 같고.. 참 계륵같은 녀석
9.3 Array.prototype.map
- 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백 함수를 반복 호출
- 그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환
- 원본 배열은 변경하지 않는다
const numbers = [1,4,9];
// forEach 때처럼, 화살표 함수의 두번째 인자로 index와 세번째 인자로 arr를 쓸 수 있다.
const roots = numbers.map((item) => Math.sqrt(item));
console.log(roots); // [1,2,3]
console.log(numbers); // [1,4,9]
- 첫번째 인자로 함수를, 두번째 인자로 내부에서 this로 사용할 객체를 받을 수 있다...forEach처럼.
- 역시 굳이 두번째 인자 쓸 필요없는 화살표 함수 쓰자
9.4 Array.prototype.filter
- 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
- 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환
- 원본 배열은 변경하지 않는다
const numbers = [1,2,3,4,5];
const odd = numbers.filter(item => item % 2);
console.log(odds); // [1, 3, 5]
- forEach, map 처럼 첫번째 인자로 함수, 두번째 인자로 this로 쓸 객체를 받을 수 있다
- 그리고 계속 말하지만 화살표 함수 쓰자
9.5 Array.prototype.reduce
- 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
- 콜백 함수의 반환값을 다음 콜백 함수의 첫번째 인자로 전달하는 것을 반복
- 최종적으로 하나의 값을 반환
- 원본 배열은 보존
// 누적 구하기
const sum = [1,2,3,4].reduce((accumulator, currentValue, idx, arr) => accumulator + currentValue, 0); // 0은 초기값
console.log(sum); // 10
reduce 메서드의 인수
첫번째 인수 : 콜백 함수
두번째 인수 : 초기값(생략하지 않는게 안전)
reduce 메서드의 콜백함수의 인수
첫 번째 인수 : 초기값 또는 콜백 함수의 이전 반환값
두 번째 인수 : reduce 메서드를 호출한 배열의 요소값
세 번째 인수 : reduce 메서드를 호출한 배열의 인덱스
네 번째 인수 : reduce 메서드를 호출한 배열 this
// 평균 구하기
const values = [1, 2, 3, 4, 5, 6];
const average = values.reduce((acc, cur, i, { length }) => {
// 마지막 순회라면 누적값으로 평균값을 구해 반환한다.
return i === length - 1 ? (acc + cur) / length : acc + cur;
}, 0);
console.log(average); // 3.5
// 최대값 구하기, 하지만 Math.max() 쓰는게 낫다.
const values = [1, 2, 3, 4, 5];
const max = values.reduce((acc, cur) => (acc > cur ? acc : cur), 0);
console.log(max); // 5
const fruits = ["banana", "apple", "orange", "orange", "apple"];
// 중복 횟수 구하기
const count = fruits.reduce((acc, cur) => {
// 첫 번째 순회 시 acc는 초기값인 {} 이고 cur 은 첫 번째 요소인 'banana'이다.
// 초기값으로 전달받은 빈 객체에 요소값인 cur을 프로퍼티 키로, 요소의 개수를 프로퍼티 값으로 할당한다.
// 만약 프로퍼티 값이 undefined(처음 등장하는 요소)이면 프로퍼티 값을 1 로 초기화 한다.
acc[cur] = (acc[cur] ?? 0) + 1;
return acc;
}, {});
console.log(count);
// 중첩 배열 평탄화, 그냥 flat 쓸 것
const values = [1, [2, 3], 4, [5, 6]];
const flatten = values.reduce((acc, cur) => acc.concat(cur), []);
// [1] -> [1, 2, 3] -> [1, 2, 3, 4] -> [1, 2, 3, 4, 5, 6]
console.log(flatten); // [1, 2, 3, 4, 5, 6]
// 중복 요소 제거
const values = [1, 2, 1, 3, 5, 4, 3, 4, 4];
const result = values.reduce((unique, val, i, _values) =>
_values.indexOf(val === i ? [...unique, val] : unique, []);
console.log(result); // [1, 2, 3, 5, 4]
9.6 Array.prototype.some
- 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
- 하나라도 true면 true 반환, 아니면 false 반환
const arr = [5, 10, 15];
arr.some((item) => item > 10); // true
arr.some((item) => item < 0); // false
9.7 Array.prototype.every
- 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
- 모두 true면, true, 아니면 false
const arr = [5, 10, 15];
arr.every((item) => item > 0); // true
arr.every((item) => item > 10); // false
9.8 Array.prototype.find
- 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
- true인 첫번째 요소 반환
const arr = ['banana', 'apple', 'orange','banana'];
arr.find((item) => item === 'banana'); // banana
9.9 Array.prototype.findIndex
- 자신을 호출한 배열의 모든 요소를 순회하면서, 인자로 전달받은 콜백함수를 반복호출
- find와 유사하지만, index를 반환
const arr = ['banana', 'apple', 'orange','banana'];
arr.findIndex((item) => item === 'banana'); // 0
9.10 Array.prototype.flatMap
- ES10
- map 메서드를 통해 생성된 새로운 배열을 평탄화
const arr = ['banana', 'orange'];
arr.map(x => x.split('')); // [['b', 'a', 'n', 'a', 'n', 'a'],['o', 'r', 'a', 'n', 'g', 'e']]
arr.map(x => x.split('')).flat(); // ['b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e']
arr.flatMap(x => x.split('')); // ['b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e']