1. 배열 다루기

1) Javascript의 배열은 객체다

  • 아래 코드에서 for문으로 배열을 출력하면, 정상적으로 출력되는 것 처럼 보인다. 하지만, 변수 i를 통해 생성되는 값(1~3)만을 index값으로 사용하고 이 index 이 외의 다른 값들은 조회가 되지 않고 있어 배열처럼 보이는 것 뿐이다.
const arr = [1, 2, 3];

arr[3] = 'test';
arr['property'] = 'string valuse';
arr['obj'] = {};
arr[{}] = [1, 2, 3];
arr['function'] = function () {
    return "hello";
}

for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]); // 1, 2, 3, test 출력
}

  • 실제로 배열 arr을 찍어보면 위에서 입력한 값이 모두 객체처럼 저장되었음을 확인할 수 있다.
console.log(arr)
/**
[
  1,
  2,
  3,
  'test',
  property: 'string valuse',
  obj: {},
  '[object Object]': [ 1, 2, 3 ],
  function: [Function (anonymous)]
]
 */

  • 심지어, 배열안에 선언한 함수도 실행할 수 있다.
console.log(arr['function']); // 에러, 배열처럼 호출하면 안됨
console.log(arr.function()) // hello, 객체처럼 호출하면 출력이 됨

  • 여기서 주의할 점은 배열은 실제로 객체이기 때문에, 배열을 타입검사할 때 주의해야 한다. 배열 타입 검사 시, 아래와 같은 방법(Array.isArray())이 있으니 참고하도록 하자.
const arr = [1, 2, 3];

// if (typeof arr === "array") {
//     console.log('배열 확인') // 실행 안됨
// }

if (Array.isArray(arr)) {
    console.log('배열 확인') // 좋은 방법!
}

2) Array.length

  • 자바스크립트에서 배열은 배열의 길이를 보장하지 않는다. 이유는 자바스크립트에서 배열은 객체로 동작하기 때문이다.
  • 그래서 이러한 특성을 활용하여 배열의 길이를 0으로 하여, 배열 전체를 초기화하는 방법도 있다.
const arr = [1, 2, 3];
console.log(arr.length); // 3

arr.length = 5;
console.log(arr.length); // 5, 자바스크립트에서 배열의 길이는 할당이 가능

arr[9] = 10;
console.log(arr); // [ 1, 2, 3, <6 empty items>, 10 ], 배열이 객체처럼 동작하기 때문에 사이의 값은 빈값으로 할당됨


// 배열의 길이는 할당이 가능해서 배열을 초기화할 때 아래 방법을 사용할 수 있음. (실무에서 이렇게 초기화했었는데, 이유를 알겠다)
arr.length = 0;
console.log(arr); // []

3) 배열 요소에 접근하기

  • 구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 표현식이다.
// 간단한 예제
const arr = [1, 2, 3, 4, 5];
let first, second, rest;

[first, second] = arr;
console.log(first); // 1
console.log(second); // 2

[first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(rest); // [ 3, 4, 5 ]

  • 구조 분해 할당을 사용하면, 코드도 클린해지지만 어떠한 배열을 입력받을 때, 배열의 각 요소가 무엇인지 변수명으로 의미부여하여 저장, 관리할 수 있다.
// 실전 예제
const arrInput = [180, 75, 37]; // 이렇게 배열의 원소만으로 어떠한 의미인지 모르는 경우가 있음

const profile = (arrInput) => {
    const [height, weight, age] = arrInput; // 구조 분해 할당으로 분해한 요소를 변수에 할당

    return `키: ${height} / 몸무게: ${weight} / 나이: ${age}}`
}

console.log(profile(arrInput));

4) 유사 배열 객체

  • 유사 배열 객체는 배열의 인덱스를 Key로 하는 객체를 말한다.
  • 유사 배열 객체가 가능한 이유는 JS에서 배열이 객체이기 때문에, 인덱스를 Key로하는 객체도 가능하다.
const arrayLikeObject = {
    0: "Hello",
    1: "World",
    length: 2,
}

// 유사 배열 객체를 배열로 변환, 모두 정상적으로 저장됨
const array = Array.from(arrayLikeObject)
console.log(array); // [ 'Hello', 'World' ]
console.log(array.length); // 2

// 배열인지 타입확인
console.log(Array.isArray(arrayLikeObject)); // false
console.log(Array.isArray(array)); // true

  • 유사 배열 객체는 실전에서 ‘어떠한 파라미터가 입력’될지 모르는 상황에서, arguments 메소드와 함께 사용할 수도 있다.
// generatePriceList 함수에 parameters가 정의되지 않았지만, arguments 메소드로 대체할 수 있음
const generatePriceList = () => {
    for (let index = 0; index < arguments.length; index++) {
        const element = arguments[index];
        console.log(element); // 100, 200, 300, 400, 500
    }

    // return arguments.map((arg) => console.log(arg + '원'));
}

generatePriceList(100, 200, 300, 400, 500);

5) 불변성

  • JS에서 배열과 같은 데이터를 다룰 때는 불변성을 지켜야 한다. 원본을 활용할 때는 원본을 복사해서 사용하거나, 새로운 배열을 반환하는 메소드(map 등)를 활용하도록 하자.
// bad case
const originArray = ['123', '456', '789'];

// newArray에 originArray를 복사를 하는 것이 아니다.
// 단지 originArray의 주소를 newArray가 참조할 뿐이다.
const newArray = originArray;

originArray.push(10);
// 오류, 두 배열의 값이 모두 바뀌어 버림
console.log(originArray); // [ '123', '456', '789', 10 ]
console.log(newArray); // [ '123', '456', '789', 10 ]

6) 배열 고차 함수로 리팩토링하자

  • for문과 같은 반복문보다 고차함수를 사용하면 클린 코드를 작성할 수 있다. (내가 하는 왠만한 프로젝트에서는 for문을 사용하지 않는다. 하지만, for문을 반드시 사용해야 하는 경우도 있으니 for문을 무조건 지양하지는 말자.)
// 일반적인 for문을 사용하는 경우
const price = ['2000', '1000', '3000', '5000', '4000'];

const getWonPrice = (priceList) => {
    let temp = [];

    for (let i = 0; i < priceList.length; i++) {
        temp.push(priceList[i] + '');
    }

    return temp;
}

console.log(getWonPrice(price)) // [ '2000원', '1000원', '3000원', '5000원', '4000원' ]
// map으로 클린코드
const price = ['2000', '1000', '3000', '5000', '4000'];

const getWonPrice = (priceList) => {
    return priceList.map((price) => `${price}원`)
}

console.log(getWonPrice(price)) // [ '2000원', '1000원', '3000원', '5000원', '4000원' ]
  • filter를 사용하여, 특정 조건을 만족하는 배열의 원소만을 추출하여 새로운 배열로 리턴할 수도 있다.
// filter를 추가한 예, 요구사항: 1000원 이상만 배열에 저장하라
const price = ['2000', '1000', '3000', '5000', '4000'];

const suffixWon = (price) => `${price}원`;
const isOVerOneThousand = (price) => Number(price) > 1000;

const getWonPrice = (priceList) => {
    const isOverList = priceList.filter(isOVerOneThousand);

    return isOverList.map(suffixWon);
}

console.log(getWonPrice(price)) // [ '2000원', '3000원', '5000원', '4000원' ]

7) 배열 메서드 체이닝 활용

  • 배열 메서드(filter, map 등)를 사용하여 리팩토링을 하더라도, 추후에 로직이 추가되는 경우에 대비하는 것이 좋다.
  • 이러한 경우에 적합한 방법은 배열 메서드 체이닝인데, 아래 예시에서는 return 하는 값에 배열메서드를 직렬화하여 사용하였다.
// 배열 메서드 체이닝과 로직 추가전의 형태
// 리팩토링은 잘되어있으나 로직을 추가하기에 조금 불편해보임
const price = ['2000', '1000', '3000', '5000', '4000'];

const suffixWon = (price) => `${price}원`;
const isOVerOneThousand = (price) => Number(price) > 1000;

const getWonPrice = (priceList) => {
    const isOverList = priceList.filter(isOVerOneThousand);

    return isOverList.map(suffixWon);
}

console.log(getWonPrice(price)) // [ '2000원', '3000원', '5000원', '4000원' ]
  • 위 코드를 아래와 같이 return에서 바로 체이닝하여 리펙토링하였다.
// 배열 메서드 체이닝 적용 후, 로직 추가 형태
const price = ['2000', '1000', '3000', '5000', '4000'];

const suffixWon = (price) => `${price}원`;
const isOVerOneThousand = (price) => Number(price) > 1000;
const descendingList = (a, b) => b - a; // 로직 추가

const getWonPrice = (priceList) => {
    return priceList.filter(isOVerOneThousand)
        .sort(descendingList) // 로직 추가
        .map(suffixWon);
}

console.log(getWonPrice(price)) // [ '5000원', '4000원', '3000원', '2000원' ]

8) map VS forEach

  • 실무에서 map 아니면, forEach 중 하나만 선택하여 사용하는 경우가 있는데, 이왕 사용할거 차이를 염두하고 사용하는 것이 좋다.
  • map 과 forEach 는 실무에서 많이 사용되는 메서드인데, 둘의 차이점은 아래와 같다.
    • map: map은 돌면서 원소를 추가하여, 새로운 배열을 return
    • forEach: forEach는 돌면서 함수를 실행할뿐
const prices = ['1000', '2000', '3000'];

const newPriceForEach = prices.forEach((price) => {
    return price + '';
})

const newPriceMap = prices.map((price) => {
    return price + '';
})

console.log(newPriceForEach); // undefined
console.log(newPriceMap); // [ '1000원', '2000원', '3000원' ], 새로운 배열을 생성해야하는 경우면 map을 활용

9) continue VS break

  • 만약, 배열의 흐름을 제어해야하는 경우 어떡해 해야할까? 예를 들어, 배열 요소에서 네번째 인덱스부터는 탐색을 종료하는 경우 말이다.
  • (적극추천) 방법 1. 일반적인 for문과 break/continue 문을 활용하는 방법
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

for (let i = 0; i < array.length; i++) {
    if (i === 3) {
        console.log(`${array[i]}, breaking the loop`);
        break;
    }
}

  • (비추천) 방법 2. forEach문과 예외(try/catch)를 사용하는 경우, 그런데 일반적으로 이 방법이 선호되지는 않는다고 함
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

try {
    array.forEach((v, i) => {
        if (i === 3) {
            throw new Error(`${array[i]}, breaking the loop`)
        }
    })
} catch (e) {
    console.log(e.message)
}

  • (추천) 방법 2. Array.prototype.some() 메서드를 사용하는 경우
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

array.some((v, i) => {
    return i === 3 && console.log(`${array[i]}, breaking the loop`)
})

다음에 다루게 될 것

  • 객체

댓글남기기