본문 바로가기
모던 자바스크립트 튜토리얼

[SOPT] JS 스터디 3주차

by 치우치지않는 2023. 5. 6.

배열

객체 vs 배열

배열은 객체의 특수한 종류(배열의 키 == 인덱스)이다. 객체는 태생이 순서를 고려하지 않고 만들어졌기 때문에, 순서 메서드가 없다. 따라서 여러 가지 자료형을 한 데 모은 자료구조가 필요한데다가 순서 메서드가 필요한 일이 많다면 배열을 사용하는 편이 좋다. 

ex) 객체는 새 프로퍼티를 기존 프로퍼티 사이에 끼워 넣는 것 불가

더 공유하고 싶은 내용1_객체에서도 length 메서드가 있을까?

더보기

결론: 없지만, Object.keys()를 통해 키 값을 요소로 하는 배열을 만들 수 있고, 그 배열에 length 를 적용하면 된다.

const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj).length); // 3

배열과 push, pop, shift, unshift, stack, queue

배열에서는 push, pop 을 이용해 스택과 큐를 구현할 수 있다. 

push(): 배열 말미에 추가

pop(): 배열 말미에서 제거

let fruits = ["사과", "오렌지", "배"];

alert( fruits.pop() ); // 결과는?

alert( fruits ); // 결과는?

정답은 "배", "사과","오렌지" 이다. pop() 메서드는 pop 한 요소를 반환까지 해준다!!

shift(): 배열 앞에서 제거, 제거한 요소 반환 

unshift(): 배열 앞에서 추가

push, shift 의 경우 복수 개의 인자 추가 가능, 콤마(,)로 구분

let fruits = ["바나나"]

let arr = fruits; // 참조를 복사함(두 변수가 같은 객체를 참조)

alert( arr === fruits ); // 결과는??

arr.push("배"); // 

alert( fruits ); // 결과는??

배열도 객체다. 그렇다면 배열에는 프로퍼티를 추가할 수 있을까?

let fruits = [];
fruits[99999] = 5;
fruits.age = 25;

console.log(fruits); // [ <99999 empty items>, 5, age: 25 ]

할 수 있다. 그러나!! length 에 포함되지 않는다. 100개 1000개여도!!!

for of vs for in

배열도 객체인지라, for in 을 사용할 수도 있으나, fruits.age 같이 length 에 들어가지 않는 프로퍼티를 세지 않고 객체에 초점이 맞추어 있어 느리다. 

let fruits = ["사과", "오렌지", "자두"];

// 배열 요소를 대상으로 반복 작업을 수행합니다.
for (let fruit of fruits) {
  alert( fruit );
}

arr.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 를 수동으로 줄이면 배열이 잘라진다. 그러나 복구는 불가능 하다. arr.length = 0 을 이용하면 쉽게 배열 초기화 가능

배열과 메서드

배열의 맥가이버 칼, splice

let arr = ["I", "study", "JavaScript", "right", "now"];

arr.splice(0, 3, "Let's", "dance");

alert( arr ) // 결과는?

splice 도 삭제한 요소들로 구성된 새로운 배열을 반환한다.

자르는 것뿐만 아니라, 추가만 하는 것도 가능(두 번째 인자 = 0)하다! 자름과 동시에 추가도 가능!

splice(1,2) 인덱스 1부터 요소 2 개를 제거한다는 뜻

똑같이 생긴 슬라이스 치즈들처럼, 배열도 똑같이 복사해 새 배열을 주는 slice 

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

alert( arr.slice(1, 3) ); // e,s (인덱스가 1인 요소부터 인덱스가 3인 요소까지를 복사(인덱스가 3인 요소는 제외))

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

두 개 인자를 갖는 경우, 시작과 끝을 의미해서 시작 인덱스부터 끝 인덱스까지 다 복사한다는 뜻! 주의할 것은 인덱스라서 0번부터 시작한다는 것!!!

append 해서 새로운 배열을 리턴해 주는 concat

let arr = [1, 2];

// arr의 요소 모두와 [3,4]의 요소 모두를 한데 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4]) ); 

// arr의 요소 모두와 [3,4]의 요소 모두, [5,6]의 요소 모두를 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4], [5, 6]) ); 

// arr의 요소 모두와 [3,4]의 요소 모두, 5와 6을 한데 모은 새로운 배열이 만들어집니다.
alert( arr.concat([3, 4], 5, 6) );

객체는 얕은 복사여서, concat 의 인자로 들어오면??? 

let arr = [1, 2];

let arrayLike = {
  0: "something",
  length: 1
};

alert( arr.concat(arrayLike) ); // 1,2,[object Object]

이렇게 찍힌다..! 그럼 만약 객체의 값을 append 하고 싶다면?

let arr = [1, 2];

let arrayLike = {
  0: "something",
  1: "else",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};

alert( arr.concat(arrayLike) ); // 1,2,something,else
isConcatSpreadable 사용!!

배열 탐색 - indexOf, includes

let arr = [1, 0, false];

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

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

includes 와 indexOf 의 가장 큰 차이점은, 전자는 값의 존재 여부만 판단하는 반면 후자는 정확한 위치까지 리턴!

객체 배열 탐색 - find, findIndex

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

배열에서 특정한 조건의 요소만 고르고 싶다면 filter!

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!

배열을 문자열을 이용해 분리하고 합치는 split, join!

배열의 typeof 는 object 이다. 배열 타입을 확인하고 싶다면 Array.isArray(판단하고 싶은 것) 을 이용!

iterable 객체

배열은 대표적인 이터러블 객체! 말 그대로 반복 가능한 객체. for of 사용 가능.

더보기

유사 배열
length 프로퍼티와 인덱스를 가진 객체로서 배열과 유사한 동작을 하는 객체. 유사 배열은 배열처럼 인덱스로 요소에 접근할 수 있으며, length 프로퍼티로 요소의 개수를 구할 수 있지만 유사 배열은 Array 메소드를 사용할 수 없다.

Array.from 을 이용해 유사 배열이나 이터러블을 진짜 Array 로 만들 수 있다. 

맵과 셋

맵 - 키에 문자열 외에 다양한 자료형을 허용하는 2차원 배열, 다양한 메서드와 프로퍼티를 제공한다.

맵의 키로는 객체가 올 수 있다!

let john = { name: "John" };

let visitsCountObj = {}; // 객체를 하나 만듭니다.

visitsCountObj[john] = 123; // 객체(john)를 키로 해서 객체에 값(123)을 저장해봅시다.

// 원하는 값(123)을 얻으려면 아래와 같이 키가 들어갈 자리에 `object Object`를 써줘야합니다.
alert( visitsCountObj["[object Object]"] ); // 123

맵의 메서드/프로퍼티

let map = new Map();

map.set('1', 'str1');   // 문자형 키
map.set(1, 'num1');     // 숫자형 키
map.set(true, 'bool1'); // 불린형 키

// 객체는 키를 문자형으로 변환한다는 걸 기억하고 계신가요?
// 맵은 키의 타입을 변환시키지 않고 그대로 유지합니다. 따라서 아래의 코드는 출력되는 값이 다릅니다.
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'

alert( map.size ); // 3

맵은 총 세 가지의 반복문 인자를 제공한다. - map.keys(), map.values(), map.entries()

let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// 키(vegetable)를 대상으로 순회합니다.
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// 값(amount)을 대상으로 순회합니다.
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// [키, 값] 쌍을 대상으로 순회합니다.
for (let entry of recipeMap) { // recipeMap.entries()와 동일합니다.
  alert(entry); // cucumber,500 ... 
}
["cucumber", 500]
["tomatoes", 350]
["onion", 50]
//키, 값을 쌍으로하는 배열을 반환한다.
혹은 forEach 를 사용할 수도 있다. 
// 각 (키, 값) 쌍을 대상으로 함수를 실행
recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // cucumber: 500 ...
});

평범한 객체를 맵으로 바꿀 때, Object.entries 를 사용하자!

let obj = {
  name: "John",
  age: 30
};

let map = new Map(Object.entries(obj));

alert( map.get('name') ); // John

맵을 객체로 바꿀 땐 Object.fromEntries 를 사용한다

let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);

let obj = Object.fromEntries(map.entries()); // entries() 는 생략 가능

// 맵이 객체가 되었습니다!
// obj = { banana: 1, orange: 2, meat: 4 }

alert(obj.orange); // 2

중복이 없는 객체, Set!

let set = new Set();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

// 어떤 고객(john, mary)은 여러 번 방문할 수 있습니다.
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

// 셋에는 유일무이한 값만 저장됩니다.
alert( set.size ); // 3

for (let user of set) {
  alert(user.name); // // John, Pete, Mary 순으로 출력됩니다.
}

forEach, for of 를 사용해서 반복할 수 있다.

let set = new Set(["oranges", "apples", "bananas"]);

for (let value of set) alert(value);

// forEach를 사용해도 동일하게 동작합니다.
set.forEach((value, valueAgain, set) => {
  alert(value);
});

forEach의 인자가 세 개인 이유는 map 과 호환할 때 호환성을 좋게 하기 위해서 value 를 한번 더 씀으로써 자리를 맞추어준 것이다.

반복 인자로는 set 과 마찬가지로 keys, values, entries 를 가진다. 

Object.keys, values, entries

일반 객체의 obejct.keys(object) 와의 차이점은, iterable 객체를 반환한다는 점! 반면 객체의 경우 진짜 배열을 반환한다. 

객체에도 map, filter 등의 배열 전용 메서드를 사용하고 싶다면 entries(obj) 와 fromEntries(obj)를 사용하자!

let prices = {
  banana: 1,
  orange: 2,
  meat: 4,
};

let doublePrices = Object.fromEntries(
  // 객체를 배열로 변환해서 배열 전용 메서드인 map을 적용하고 fromEntries를 사용해 배열을 다시 객체로 되돌립니다.
  Object.entries(prices).map(([key, value]) => [key, value * 2])
);

alert(doublePrices.meat); // 8

구조 분해 할당

나머지 요소를 뜻하는 '...'

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar

// `rest`는 배열입니다.
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

name1, name2 는 따로, ...rest 는 나머지를 모은 새로운 배열!

기본값을 지정하고 싶다면, = 사용하기!

// 기본값
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (배열에서 받아온 값)
alert(surname); // Anonymous (기본값)

함수에 전달해야하는 매개변수가 많을 경우 순서를 틀릴 확률이 높으므로 구조 분해 할당을 사용하면 좋다. 

let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

function showMenu({
  title = "Untitled",
  width: w = 100,  // width는 w에,
  height: h = 200, // height는 h에,
  items: [item1, item2] // items의 첫 번째 요소는 item1에, 두 번째 요소는 item2에 할당함
}) {
  alert( `${title} ${w} ${h}` ); // My Menu 100 200
  alert( item1 ); // Item1
  alert( item2 ); // Item2
}

showMenu(options);

댓글