[JS] 자료구조 - 배열, 배열 메서드, iterable 객체

PinkTopaz·2023년 5월 6일
0
post-thumbnail

배열

특별한 종류의 객체

  • 대괄호를 사용해 접근하는 방식은 객체 문법에서 왔고, 키가 숫자라는 점만 다르다.
  • 객체와 같이 참조를 통한 복사
let fruits = ["딸기"]
let arr = fruits; // ✅ 참조를 복사함(두 변수가 같은 객체를 참조)
alert( arr === fruits ); // true

arr.push("망고"); // 참조를 이용해 배열 수정
alert( fruits ); // 딸기,망고
  • javascript 배열 요소의 자료형에는 제약이 없다.
// ✅ 요소에 여러 가지 자료형이 섞여 있을 수 있다!
let arr = [ '사과', { name: '이보라' }, true, function() { alert('안녕하세요.'); } ];

unshift, shift

  • 배열 앞, 뒤에 요소를 추가, 제거한다.
//✅ unshift : 배열 앞에 요소를 추가한다. 
let fruits = ["오렌지", "배"];
fruits.unshift('사과');
alert( fruits ); // 사과,오렌지,배

//✅ shift : 배열 앞에 요소를 제거하고 제거한 요소를 반환한다.
const removed = fruits.shift();
alert(removed); // 사과

//✅ push, unshift는 여러개도 한 번에 가능 
let fruits = ["사과"];

fruits.push("레몬", "귤");
fruits.unshift("파인애플", "레몬");

// ["파인애플", "레몬", "사과", "레몬", "귤"]
alert( fruits );
  • push와 pop은 빠르지만 shift와 unshift는 느리다.

💡 이유 - shift, unshift 연산의 동작 원리
1. 인덱스가 0인 요소를 제거, 추가
2. 모든 요소를 왼쪽으로 이동. 이때 인덱스 1은 0, 2는 1로
3. length 프로퍼티 값 갱신

배열 순회

  • for ~ of : 값만 얻을 수 있다.
let fruits = ["귤", "망고", "딸기"];

for (let fruit of fruits) {
  alert( fruit ); // 귤, 망고, 딸기 
}
  • for ~ in : 인덱스를 얻는다. (배열에는 되도록 추천 X)

💡 for~in 사용시 주의 사항
1. for..in 반복문은 모든 프로퍼티를 대상으로 순회히여 키가 숫자가 아닌 프로퍼티도 순회 대상에 포함된다.
2. for..in 반복문은 배열이 아니라 객체와 함께 사용할 때 최적화되어 있어서 배열에 사용하면 객체에 사용하는 것 대비 10~100배 정도 느리다.

length

  • length 값 수동으로 변경 가능 : arr.length = 0 을 사용해 간단하게 배열을 비우기 가능
let fruits = ["귤", "망고", "딸기"];

fruits.length = 2; // ✅ 요소 2개만 남기고 자름
alert( fruits ); // [귤, 망고]

fruits.length = 5; // 본래 길이로 되돌려 봅시다.
alert( fruits[3] ); // undefined

ETC

  • 행렬을 저장할 때 다차원 배열을 사용할 수 있다.
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

배열과 메서드

splice : 요소 제거 시 사용

💡 delete를 사용해 요소를 지운다면?
배열의 길이가 -1 되기를 기대했지만, 그렇지 않다. 따라서 splice를 사용하는 것!

delete arr[1]; // "go"를 삭제합니다.
alert( arr[1] ); // ✅ undefined
alert( arr.length ); // ✅ 여전히 길이가 3
  • 삭제된 요소로 구성된 배열을 반환한다.
let arr = ["I", "study", "JavaScript"];
const newArr = arr.splice(1, 2); // ✅ 인덱스 1부터 요소 2개를 제거
alert(newArr); //["JavaScript"]
  • 요소를 지우고 그 자리를 다른 요소로 교체할 수도 있다.
let arr = ["I", "study", "JavaScript", "right", "now"];
//  ✅  처음 3개의 요소를 지우고 ("I", "study", "JavaScript"), 이 자리를 "Let's", "dance"로 대체
arr.splice(0, 3, "Let's", "dance");

alert( arr ) // ["Let's", "dance", "right", "now"]

💡 음수 인덱스도 사용할 수 있다!
마이너스 부호 앞의 숫자는 배열 끝에서부터 센 요소 위치를 나타낸다.

let arr = [1, 2, 5];

// ✅ 인덱스 -1부터 (배열 끝에서부터 첫 번째 요소) 1개의 요소를 삭제하고 3과 4를 추가합니다.
arr.splice(-1, 1, 3, 4);
alert( arr ); // 1,2,3,4

### slice : 요소 복사 시 사용 

- 인자가 두 개 들어가는 경우 마지막 인자로 들어간 인덱스는 제외된다는 것을 명심하자

```js
let arr = ["t", "e", "s", "t"];

alert( arr.slice(1, 3) ); // e,s 
alert( arr.slice(-2) ); // s,t (인덱스가 -2인 요소부터 제일 끝 요소까지를 복사)

concat

  • 기존 배열 요소 활용해 새로운 배열 or 요소 추가
  • 인수가 배열일 경우 배열의 모든 요소가 복사됩니다. 그렇지 않은경우(단순 값인 경우)는 인수가 그대로 복사됩니다.
let arr = [1, 2];

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

indexOf,lastIndexOf,includes

  • arr.indexOf(item, from) : from부터 시작해 item을 찾는다. 요소를 발견하면 해당 요소의 인덱스를 반환하고, 발견하지 못했으면 -1을 반환한다.
  • arr.lastIndexOf(item, from) : indexOf와 같은 기능을 하지만, 배열 끝에서부터 검색한다.
  • arr.includes(item, from) : indexOf와 같은 기능을 하지만, 해당 요소를 발견하면 true를 반환한다.
let arr = [1, 0, false];

alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2

alert( arr.lastIndexOf(false) ); // 2

alert( arr.includes(1) ); // true

find, findIndex, filter : 조건에 맞는 요소 찾기

  • 배열에서 특정조건에 부합하는 요소를 찾을 때 find, findIndex를 이용할 수 있다.
    - find : 특정조건에 부합하는 요소 자체를 리턴
    • findIndex : 조건에 부합하는 요소의 인덱스를 리턴한다.
let users = [
  {id: 1, name: "Heather"},
  {id: 2, name: "Matthew"},
  {id: 3, name: "Ricky"}
];

let user = users.find(item => item.id == 1);
let userIndex = users.findIndex(item => item.name == "Heather");

alert(user.name); // Heather
alert(userIndex); //0
  • 조건에 부합하는 요소가 단 하나가 아니라면, filter를 사용한다.
    - filter : 조건에 맞는 요소 전체를 담은 배열을 반환
let user = users.filter(item => item.id > 1);


alert(user.length); // 2
alert(user); //[object Object], [object Object]

map, sort, reverse

  • map : 배열 요소 전체를 대상으로 함수를 호출하고, 함수 호출 결과를 배열로 반환
// ✅ 각 요소마다의 length를 리턴한 배열을 반환 
let lengths = ["Heather", "Matthew", "Ricky"].map(item => item.length);
alert(lengths); // 7,7,5
  • sort : 배열 요소 전체를 정렬한다. (quick sort 이용하여 정렬한다고 한다!)

💡 sort에서 요소는 문자열로 취급되어 재정렬된다.

let arr = [ 1, 2, 15 ];
arr.sort();
// ✅ 문자열 비교는 사전편집 순으로 진행되어 15가 2보다 큰 값으로 취급된다. 
alert( arr );  // 1, 15, 2

따라서 기본 정렬 기준 대신 새로운 정렬 기준을 만드려면 arr.sort()에 새로운 함수를 넘겨줘야한다!

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 ✅ 기대했던 대로 요소가 정렬됨

보통은 화살표 함수를 사용해서 깔끔하게 나타낸다!

let arr = [ 1, 2, 15 ];

arr.sort( (a, b) => a - b );

alert(arr); // 1, 2, 15


[sort의 내부 동작은 해당 링크를 참고하면 더 쉽게 이해가 된다!](https://noirstar.tistory.com/359)

> 💡 문자열을 언어 상관없이 사전 순으로 정렬하고 싶다면? ** localCompare ** 를 사용하자!
`str.localeCompare` 메서드는 유니코드를 기준으로 글자를 비교하기 때문에 독일어와 같은 언어도 사전 순서대로 정렬해준다!
```js
let countries = ['Österreich', 'Andorra', 'Vietnam'];

alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (제대로 정렬 X)

alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (제대로 정렬)

split, join : 문자열 ↔️ 배열

  • split : 문자열을 배열로 쪼개준다. (메일 발신자를 ,로 구분해서 여러명 작성했을 경우 유용!)
  • join : 배열요소를 문자열로 합쳐준다
let names = 'Heather, Mary, Eunice';
let arr = names.split(', '); // ✅ 인자 안에 들어간 구분자를 기준으로 나눈다
for (let name of arr) {
  alert( `${name}에게 보내는 메시지` ); // Heather에게 보내는 메시지
}

let nameStr = arr.join(',');// ✅ 인자 안에 들어간 구분자를 사용해 합친다. 
alert(nameStr); //Heather,Mary,Eunice

reduce, reduceRight : 배열을 기반으로 하나의 값 도출

  • reduce, reduceRight 모두 배열의 각 요소에 대해 주어진 리듀서 (reducer) 함수를 실행하고, 하나의 결과값을 반환하는데, reduceRight는 연산을 배열 끝에서부터 사용한다는 점이 다르다.
let arr = [1, 2, 3, 4, 5];
let result = arr.reduce((sum, current,0 ) => sum + current);
alert( result ); // 15

💡 reduce, reduceRight에서 초깃값 생략이 가능하다.
초깃값이 없으면 배열 첫번쨰 요소를 초깃값으로 사용한다.
초깃값이 없이 사용할 때 만약 배열이 비어있는 상태면 reduce 호출 시 에러가 발생하기 때문에 주의해야한다.
따라서 초깃값을 항상 지정해주는 것을 추천한다!

isArray : 배열 여부 알아내기

  • 배열은 객체형에 속하기 때문에 typeof []의 결과는 object이다.
  • 따라서 isArray를 사용해서 배열인지 아닌지를 판단할 수 있다.
alert(Array.isArray({})); // false
alert(Array.isArray([])); // true

배열 메서드와 thisArg

  • 함수를 호출하는 대부분의 배열 메서드(find, filter, map 등. sort는 제외)는 thisArg라는 매개변수를 옵션으로 받을 수 있다.
  • thisArgfunc의 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의 this
// ✅ 두번째 인자를 넣어주지 않으면 army.canJoin가 단독함수로 처리돼서 this를 찾을 수 없어서 this가 undefined가 되고, 에러를 뱉을 것!
let soldiers = users.filter(army.canJoin, army);

iterable 객체 : 배열을 일반화한 객체

💡 배열이 아닌 객체가 있는데, 이 객체가 어떤 것들의 컬렉션(목록, 집합 등)을 나타내고 있는 경우, for..of 문법을 적용할 수만 있다면 컬렉션을 순회하는데 유용하지 않을까?

  • 일반 객체를 반복 가능한 iterable 객체로 만들어보자!
let range = {
  from: 1,
  to: 5
};

// 1. ✅ for..of 최초 호출 시, Symbol.iterator가 호출됨
range[Symbol.iterator] = function() {

  // ✅ Symbol.iterator는 이터레이터 객체를 반환
  // 2. 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데, 이때 다음 값도 정해진다.
  return {
    current: this.from,
    last: this.to,

    // 3. ✅ for..of 반복문에 의해 반복마다 next()가 호출
    next() {
      // 4. ✅ next()는 값을 객체 {done:.., value :...}형태로 반환
      // ✅ done : true - 반복이 종료되었음
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

for (let num of range) {
  alert(num); // ✅ 1, then 2, 3, 4, 5
}

\rightarrow range에는 next()가 없지만, range[Symbol.iterator]()를 호출해서 만든 ‘이터레이터’ 객체와 이 객체의 메서드 next()에서 반복에 사용될 값을 만들어낸다.

더 간단하게 만들 수도 있다.

let range = {
  from: 1,
  to: 5,

  // ✅ 이제 range[Symbol.iterator]가 객체 range 자체를 반환
  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },

  // ✅ 반환된 객체엔 필수 메서드인 next()
  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let num of range) {
  alert(num); // 1, then 2, 3, 4, 5
}

iterable 예시 - 배열 , 문자열

for (let char of "test") {
  // ✅ 글자 하나당 한 번 실행
  alert( char ); // t, e, s, t가 차례대로 출력됨
}

iterator 명시적으로 호출하기

  • iterator를 명시적으로 호출하면 for..of 없이도 for..of를 사용한 것과 동일한 작업을 한다.
let str = "Hello";

// ✅ for..of를 사용한 것과 동일한 작업을 합니다.
// for (let char of str) alert(char);
let iterator = str[Symbol.iterator]();

while (true) {
  let result = iterator.next(); // ✅ 반복 과정을 더 명시적으로 통제할 수 있다. 
  if (result.done) break;
  alert(result.value);
}

iterable vs 유사 배열

  • 이터러블(iterable) : 메서드 Symbol.iterator가 구현된 객체
  • 유사 배열(array-like) : 인덱스와 length 프로퍼티가 있어서 배열처럼 보이는 객체
let arrayLike = { // ✅ 인덱스와 length프로퍼티가 있음 => 유사 배열
  0: "Hello",
  1: "World",
  length: 2
};

둘 다 대개 배열이 아니기 때문에 배열에 사용할 수 있는 메서드를 지원하지 않는다.

💡 이터러블과 유사배열을 받아 진짜 배열을 만드는 Array.from() 을 사용하자!

let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
};

let arr = Array.from(arrayLike);
alert(arr.pop()); // ✅ World (메서드가 제대로 동작)


#### Array.from의 매핑 함수 

- 새로운 배열에 요소를 추가하기 전에 각 요소를 대상으로 mapFn을 적용할 수 있다. 새로운 배열엔 mapFn을 적용하고 반환된 값이 추가된다. 

```js
// 각 숫자를 제곱
let arr = Array.from(range, num => num * num);

alert(arr); // 1,4,9,16,25
profile
🌱Connecting the dots🌱

0개의 댓글