Dandy Now!
  • [JavaScript] 클린코드 자바스크립트 : 배열
    2023년 10월 14일 12시 30분 37초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    이 글은 유데미의 "클린코드 자바스크립트" 강의 내용(섹션 6: 배열 다루기)을 정리한 것이다.


    1. 배열은 객체이다.

    배열은 인덱스로 값에 접근한다. 하지만 [ ]에 키를 넣을 수도 있다. obj라는 키에 빈 객체를 할당해 보겠다.

    const arr = [1, 2, 3]
    arr["obj"] = {}
    
    console.log(arr) // [ 1, 2, 3, obj: {} ]
    console.log(arr[3]) // undefined
    console.log(arr["obj"]) // {}

     

    arr의 요소에 obj요소가 있는 것을 확인할 수 있다. 시각적으로 볼 때 인덱스 3번에 위치해 있는 것처럼 보이지만 arr[3]으로 접근하면 요소가 없는 것을 확인할 수 있다. 하지만 "obj"라는 키로 접근하면 {}라는 값에 접근이 가능하다. 물론 함수도 할당 가능하다.

    arr["func"] = function () { return "array? object?" }
    
    console.log(arr) // [ 1, 2, 3, obj: {}, func: [Function (anonymous)] ]
    console.log(arr.func()) // 'array? object?'

     

    2. Array.length는 인덱스가 있는 요소의 개수만 알려준다!

    배열 요소의 개수를 확인할 때 Array.length를 사용한다. 나 역시 딱 그 정도로만 활용했었다. 그런데 재미난 기능을 숨기고 있었다.

    const arr = [1, 2, 3]
    arr.length = 10
    
    // 빈 요소 7개가 생겨난 것을 확인할 수 있다.
    console.log(arr) // [1, 2, 3, <7 empty items>]

     

    위의 코드와 같이 arr.length에 정수 값을 할당하면 배열의 길이를 직접 지정할 수 있고 해당 길이만큼 비어있는 요소가 생성되는 것을 알 수 있다. 이번에는 키와 값을 가진 요소도 포함된 상태에서 진행해 보겠다.

    // 아래와 같은 요소를 가지고 있는 배열 arr이 있다.
    console.log(arr) // [ 1, 2, 3, obj: {}, func: [Function (anonymous)] ]
    
    // 배열의 길이를 확인해 보면 3인 것을 확인할 수 있다.
    console.log(arr.length) // 3
    
    arr.length = 0
    
    // 인덱스를 가진 값은 모두 사라졌다.
    console.log(arr) // [ obj: {}, func: [Function (anonymous)] ]
    
    // 키와 값을 가진 요소 외에 비어있는 요소 10개가 생겼다.
    arr.length = 10
    console.log(arr) // [ <10 empty items>, obj: {}, func: [Function (anonymous)] ]

     

    이상의 코드를 보면 키와 값으로 되어있는 요소는 Array.length와 무관한 것을 알 수 있다. 정리하자면 Array.length에는 정수 값을 할당할 수 있고 할당된 정수 값에 따라 배열의 길이가 결정되기 때문에 인덱스를 가진 요소를 제거할 수도 있고 요소의 공간을 확보할 수 도 있다.

     

    3. 배열 요소(element)에 접근할 때 [ ] 쓰지 않기

    [ ]에 인덱스를 넣어 해당 요소에 접근할 수 있는데 이 같은 방식은 명시적이지 못하여 가독성을 떨어트릴 수 있다. 따라서 다음과 같은 방식을 사용한다면 코드를 읽는 이가 더 잘 이해할 수 있도록 도울 수 있다.

    3.1. 구조분해 할당 활용

    const phoneNumber = "010-1111-1111";
    
    /**
    * before : 인덱스로 접근
    * 가독성이 좋지 못하다!
    */
    const numArr = phoneNumber.split('-');
    console.log(`이동 통신 번호 : ${numArr[0]}, 국번 : ${numArr[1]}, 개별 번호 : ${numArr[2]}`);
    
    /**
    * after : 구조 분해 할당
    */
    const [firstNum, middleNum, lastNum] = phoneNumber.split('-');
    console.log(`이동 통신 번호 : ${firstNum}, 국번 : ${middleNum}, 개별 번호 : ${lastNum}`);

     

    3.2. 사용자 정의 함수 활용 

    아래의 코드는 배열의 첫 번째 요소만 사용하는 경우에 대한 예시이다.

    // 첫 번째 요소만 사용하는 경우
    /**
    * before : 인덱스로 할당
    * 배열 요소가 없을 경우 undefined 반환
    */
    const firstNum = phoneNumber.split('-')[0];
    
    /**
    * after : 사용자 정의 함수 활용
    * 배열 요소가 없을 경우 원하는 값 반환 가능
    */
    const head = (arr) => arr[0] ?? '';
    const firstNum = head(phoneNumber.split('-'));

     

    4. 유사 배열 객체

    4.1. 배열을 흉내낸 객체

    객체를 배열 처럼 만들 수 있다. 하지만 배열이 아닌 객체이기 때문에 배열로 만들기 위해서는 Array.from()을 이용해 변환해야 한다. 

    const likeArr = {
      0: 1,
      1: 2,
      2: 3,
      length: 3,
    };
    
    console.log(likeArr[0]) // 1
    console.log(likeArr.length) // 3
    console.log(Array.isArray(likeArr)) // false
    
    const Arr = Array.from(likeArr))
    console.log(Array.isArray(Arr)) // true

     

    4.2. 대표적인 유사 배열 객체 arguments

    대표적인 유사 배열 객체로 arguments가 있다. arguments는 배열처럼 보이지만 배열이 아니다. 따라서 고차함수를 사용할 수 없다.

    function func() {
      return arguments
    }
    
    const args = func(1, 2, 3)
    console.log(args[0]) // 1
    console.log(args.length) // 3
    console.log(Array.isArray(args)) // false
    
    const argsArr = Array.from(args)
    console.log(Array.isArray(argsArr)) // true

     

    5. 불변성

    5.1. 불변성이 유지되지 않는 경우

    다음 코드는 불변성이 유지되지 않는 경우이다. 원본 배열의 값이 변경되면 복사본 배열의 값도 변경이된다.

    const arr1 = [1, 2, 3]
    const arr2 = arr1
    
    console.log(arr2) // [1, 2, 3]
    
    const arr1.push(4)
    
    console.log(arr2) // [1, 2, 3, 4]

     

    5.2. 불변성 유지 방법

    • 배열을 복사한다(concat() 메서드, 전개 연산자 활용).
    • 새로운 배열을 반환하는 메서드(고차 함수)를 활용한다.

     

    6. for문 배열 고차 함수로 리팩터링

    아래 코드는 for문을 map 메서드를 사용해 리팩터링한 예제이다.

    const arrNums = [1, 3, 2];
    
    // for문
    function func1(arr) {
      let temp = [];
    
      for (let i = 0; i < arr.length; i++) {
        temp.push(`${arr[i]}명`);
      }
    
      return temp;
    }
    
    const forResult = func1(arrNums);
    console.log(forResult); // [ '1명', '3명', '2명' ]
    
    // 고차 함수
    function func2(arr) {
      return arr.map((num) => `${num}명`);
    }
    
    const mapResult = func2(arrNums);
    console.log(mapResult); // [ '1명', '3명', '2명' ]

     

    7. 배열 메서드 체이닝 활용하기

    배열 고차함수를 파이프라인 처럼 이어서 사용할 수 있다. 이 경우 for문을 이용할 때 보다 명시적이다.

    const arrNums = [1, 3, 2];
    
    const isOverFucn = (num) => num > 1;
    const ascFunc = (a, b) => a - b;
    const strFunc = (num) => `${num}명`;
    
    function func(arr) {
      // 1보다 큰 값을 필터, 오름차순 정렬, "명"을 붙여 문자열로 변환
      return arr.filter(isOverFucn).sort(ascFunc).map(strFunc);
    }
    
    const rst = func(arrNums);
    console.log(rst); // [ '2명', '3명' ]

     

    8. map vs forEach

    map과  forEach는 다음과 같은 차이가 있다.

    • Array.prototype.map() : 매 요소마다 함수를 실행, 결과 값 반환, 새로운 배열 생성
    • Array.prototype.forEach(): 매 요소마다 함수를 실행

    아래 코드는 두 배열 메서드의 차이를 보여주는 간단한 예제이다. 코드 아래에 주석 처리된 부분은 실행 결과이다. 언뜻 보면 map과 forEach가 별 차이없게 보이지만 반환값에서 큰 차이를 보인다. map은 콜백 함수의 결과 값으로 새로운 배열을 반환하고, forEach는 콜백 함수의 리턴 값을 반환한다. 위 예제의 경우 콜백 함수는 리턴 값이 없으므로 undefined이다.

    const arr = [1, 2, 3]
    
    console.log(arr.map((el) => console.log(el)));
    console.log(arr.forEach((el) => console.log(el)));
    
    // 1
    // 2
    // 3
    // [ undefined, undefined, undefined ]
    // 1
    // 2
    // 3
    // undefined

     

    9. Continue & Break

    for문에서는 if문과 함께 사용할 수 있는 제어문이다. 하지만 forEach 메서드에서는 사용할 수 없다. 배열 메서드에서 이 제어문을 사용할 수 있는 방법이 있는데 some 메서드를 이용하면 된다.

    some 메서드는 순회하는 요소의 값이 하나라도 true이면 순회를 멈추고 true를 반환한다. 반대로 true가 나오지 않는 다면 모든 요소를 순회하고 결국 false를 반환한다. 이 점을 이용해 break하고 싶은 조건에서 true를 반환하면 순회를 중도에 멈출 수 있다. 아래는 예제 코드인데, 예제 1은 for of 문을 이용한 경우이고 예제 2는 some을 이용한 경우이다.

    const arr = [1, 3, 4, 5];
    
    // 예제 1
    for (n of arr) {
      if (n % 2 !== 0) {
        continue;
      }
      console.log(n);
      break;
    }
    
    // 예제 2
    arr.some((n) => {
      if (n % 2 === 0) {
        console.log(n);
        return true;
      }
      return false;
    });
    
    // 4
    // 4

     

    forEach에서는 Continue, Break를 문법적으로 지원하지 않는다. 이와 같은 효과를 구현하기 위해서는 다음의 방법을 사용할 수 있다.

    • try catch를 이용하고 Continue, Break 대신 throw로 에러를 던지는 방법
    • every(), some(), find(), findIndex()와 같은 메서드로 조기에 반복을 종료

    😉 every와 some은 불리언 값을 반환한다. every는 &&연산자와 비슷해서 모든 요소가 true이면 true를 리턴하고, some은 ||연산자와 비슷해서 요소 중 하나라도 true이면 true를 반환한다. find와 findIndex는 특정 인덱스 요소를 찾으면 종료한다.

    728x90
    반응형
    댓글