Development/Etc

[모던 자바스크립트] Array 스마트하게 사용하기

bbubbush 2022. 9. 4. 01:00

들어가며

배열(Array)은 맵과 함께 데이터를 관리하기 위한 가장 효율적인 자료구조다. 이번에는 배열로 무엇을 할 수 있는지 보면서 for 구문의 지옥에서 벗어날 수 있는 것을 목표로 한다.

 

고전적인 배열 사용방법

배열을 사용하면서 자주 만나는 상황을 통해 그동안 어떻게 해결했는지 살펴보자. 우선 예제로 사용할 데이터를 정의하고 출발한다.

const memberList = [
  {id: 0, name: '김철수', deptCd: '01', isManager: true},
  {id: 1, name: '이영희', deptCd: '02', isManager: false},
  {id: 2, name: '고냠이', deptCd: '02', isManager: false},
  {id: 3, name: '강이지', deptCd: '02', isManager: false},
  {id: 4, name: '새의강', deptCd: '03', isManager: false},
  {id: 5, name: '반아나', deptCd: '04', isManager: false},
  {id: 6, name: '한이봉', deptCd: '04', isManager: false},
  {id: 7, name: '고동어', deptCd: '05', isManager: false},
]

memberList를 기초 데이터로 사용하고, 각 상황에 따라 데이터를 핸들링해보려고 한다.

 

1. 배열 복사

가장 직관적인 배열 복사 방법을 먼저 해보자. 새로운 변수에 기존 배열을 그대로 넣는 것이다. 다음은 배열을 복사하고 관리자의 이름을 변경하는 코드다.

const copyMemberList = memberList;
copyMemberList[0]['name'] = '장대표';
console.log(`새로운 관리자 이름: ${copyMemberList[0]['name']}`);  // '새로운 관리자 이름: 장대표'
console.log(`기존 관리자 이름: ${memberList[0]['name']}`);  //  '기존 관리자 이름: 장대표'

이 경우에는 변경사항이 원본 데이터에 반영이 된다. 따라서 배열이 복사된 것이 아니라 새로운 변수 이름으로 메모리 주소가 옮겨 간 것을 알 수 있다.

 

그 다음 사용하는 방법은 for 구문을 통해 하나씩 값을 옮기는 것이다.

const copyMemberList = [];
for (const member of memberList) {
  const copyMember = {
    id: member['id'],
    name: member['name'],
    deptCd: member['deptCd'],
    isManager: member['isManager'],
  } 
  copyMemberList.push(copyMember);
}
copyMemberList[0]['name'] = '장대표';
console.log(`새로운 관리자 이름: ${copyMemberList[0]['name']}`);  // '새로운 관리자 이름: 장대표'
console.log(`기존 관리자 이름: ${memberList[0]['name']}`);  //  '기존 관리자 이름: 김철수'

코드가 제법 길지만 원리는 간단하다. 기존의 값을 하나씩 가져와 새로운 배열과 맵에 옮기는 게 전부다. 원하는 기능을 얻었으므로 기분 좋게 다음 상황으로 넘어가자.

 

2. 배열 필터링

배열을 복사할 때는 0번째 값이 관리자임을 인지하고 이름을 수정했다. 하지만 매번 관리자 정보가 0번째에 있을 순 없다. 따라서 isManager가 true인 값을 찾아 이름을 변경해보자.

let manager = {};
for (const member of memberList) {
  if (member['isManager']) {
    manager = member;
    break;
  }
}
console.log(`관리자 이름: ${manager['name']}`);  // '관리자 이름: 김철수'
console.log(`관리자 부서코드: ${manager['deptCd']}`);  //  '관리자 부서코드: 01'

다행히 큰 어려움 없이 관리자 정보를 구했다. for 구문은 한 번 작성해 놓으면 복사/붙여 넣기 활용하기 좋다. 만약 부서 코드가 ‘02’인 데이터를 필터링해야 한다면 for 구문 안의 if 구문의 조건을 변경해주기만 하면 된다. 매우 심플하고 직관적이다.

 

3. 배열 정렬

현재 memberList는 id를 기준으로 오름차순 정렬이 됐다. 이번에는 부서별로 오름차순을 하고, 같은 부서끼리는 이름으로 오름차순으로 정렬할 것이다. 조금 복잡할 것 같지만 for 구문을 두 번 쓰면 충분히 할 수 있다.

let manager = {};
for (let i = 0; i < memberList.length; i++) {
  let tempForSwap = {};
  for (let j = 0; j < memberList.length - 1 - i; j++) {
    const targetMember = memberList[j];
    const sourceMember = memberList[j + 1];
    if (targetMember['deptCd'] > sourceMember['deptCd']) {
			tempForSwap = targetMember;
      memberList[j] = sourceMember;
      memberList[j + 1] = tempForSwap;
    } else if (targetMember['deptCd'] == sourceMember['deptCd']
              && targetMember['name'] > sourceMember['name']) {
      tempForSwap = targetMember;
      memberList[j] = sourceMember;
      memberList[j + 1] = tempForSwap;
    }
  }
}
console.table(memberList);
| (index) | id | name  | deptCd | isManager |
---------------------------------------------
|    0    | 0  | '김철수' |  '01'  |   true    |
|    1    | 3  | '강이지' |  '02'  |   false   |
|    2    | 2  | '고냠이' |  '02'  |   false   |
|    3    | 1  | '이영희' |  '02'  |   false   |
|    4    | 4  | '새의강' |  '03'  |   false   |
|    5    | 5  | '반아나' |  '04'  |   false   |
|    6    | 6  | '한이봉' |  '04'  |   false   |
|    7    | 7  | '고동어' |  '05'  |   false   |

어떤 정렬 알고리즘을 썼는지 보다 정렬 조건을 어떻게 주었는지를 자세히 보자. 먼저 if 구문에서 부서 코드로 정렬하고 다음 else if 구문에서 이름으로 정렬한다. 결과를 보니 만족스럽게 잘 나왔다. 다만 시간이 지난 후에 코드를 분석하려면 조금 고생할 것 같다.

 

4. 새로운 형태의 배열

마지막으로 정렬된 memberList의 정보를 “김철수(팀장)” 혹은 “강이지(팀원)” 형태로 보여주려고 한다. 아무래도 원본 데이터를 변경하면 향후 작업에 불편할 수 있으니 이름과 직책 정보를 담을 새로운 배열을 만드려고 한다.

const memberRoleList = [];
for (const member of memberList) {
  const role = member['isManager'] ? '팀장' : '팀원';
  memberRoleList.push(`${member['name']}{${role}}`);
}
console.table(memberRoleList);
| (index) |  Values   |
-----------------------
|    0    | '김철수{팀장}' |
|    1    | '이영희{팀원}' |
|    2    | '고냠이{팀원}' |
|    3    | '강이지{팀원}' |
|    4    | '새의강{팀원}' |
|    5    | '반아나{팀원}' |
|    6    | '한이봉{팀원}' |
|    7    | '고동어{팀원}' |

드디어 개발이 끝났다. 기능은 잘 동작하고, 코드 또한 깔끔하게 작성한 것 같다. 이제 여유를 갖고 다시 위 코드들을 살펴보자. 개발에 급급해서 놓쳤던 공통된 형태가 눈에 들어올 것이다.

 

for & if "

 

이렇게 배열을 다루는 전통적인 방법은 for와 if 구문의 조합으로 이루어진다. 배열의 모든 인덱스를 순차적으로 탐색해야 하고, 각 인덱스에 조건을 주어 원하는 결과를 찾는다.

따라서 ‘공통적으로 자주 사용되는 기능은 함수로 만들어 처리한다면 더욱 가독성 좋고 효율적인 코드를 짤 수 있지 않을까?’ 생각이 든다.

 

 

반응형

 

모던한 배열 사용방법

이번에는 같은 문제를 Cool하게 사용할 수 있는 방법을 제시한다. 바로 확인해보자.

1. 배열 복사

const copyMemberList = memberList.map(member => Object.assign({}, member));
copyMemberList[0]['name'] = '장대표';
console.log(`새로운 관리자 이름: ${copyMemberList[0]['name']}`);  // '새로운 관리자 이름: 장대표'
console.log(`기존 관리자 이름: ${memberList[0]['name']}`);  //  '기존 관리자 이름: 김철수'
const memberNameList = ['김철수', '이영희', '고냠이', '강이지'];
const copyMemberNameList = [...memberNameList];
copyMemberNameList[0] = '장대표';
console.log(`새로운 관리자 이름: ${copyMemberNameList[0]}`);  // '새로운 관리자 이름: 장대표'
console.log(`기존 관리자 이름: ${memberNameList[0]}`);  //  '기존 관리자 이름: 김철수'

Object.assing() 함수를 사용한 방법과 축약 연산자(…array)를 사용한 방법이다. 두 방법 모두 깊은 복사를 지원한다. 다만, 현재 객체의 깊이만 깊게 복사될 뿐, 더 깊은 객체는 복사하지 않는다.

사실 가장 간단하게 사용하는 방법으로 JSON.stringify()와 JSON.parse()의 조합이다. 객체를 직렬화 후 역직렬화 하는 과정에서 모든 객체가 깊은 복사되기 때문이다.

const copyMemberList = JSON.parse(JSON.stringify(memberList));
copyMemberList[0]['name'] = '장대표';
console.log(`새로운 관리자 이름: ${copyMemberList[0]['name']}`);  // '새로운 관리자 이름: 장대표'
console.log(`기존 관리자 이름: ${memberList[0]['name']}`);  //  '기존 관리자 이름: 김철수'

 

2. 배열 필터링

const manager = memberList.filter(member => member['isManager'] == true)[0];
console.log(`관리자 이름: ${manager['name']}`);  // '관리자 이름: 김철수'
console.log(`관리자 부서코드: ${manager['deptCd']}`);  //  '관리자 부서코드: 01'

배열의 필터링도 직관적인 방법으로 변경되었다. filter()를 통해 각 인덱스 별로 조건을 만족하는 데이터만 남겨서 새로운 배열로 만들어 주기 때문에 for 구문도, if 구문도 필요가 없어졌다.

 

3. 배열 정렬

memberList.sort((m1, m2) => m2['deptCd'] > m1['deptCd'])
  .sort((m1, m2) => {
    if(m2['deptCd'] == m1['deptCd']) {
      return m2['name'] > m1['name'];
    } 
    return 0;
  });
console.table(memberList);
| (index) | id | name  | deptCd | isManager |
---------------------------------------------
|    0    | 0  | '김철수' |  '01'  |   true    |
|    1    | 1  | '이영희' |  '02'  |   false   |
|    2    | 2  | '고냠이' |  '02'  |   false   |
|    3    | 3  | '강이지' |  '02'  |   false   |
|    4    | 4  | '새의강' |  '03'  |   false   |
|    5    | 5  | '반아나' |  '04'  |   false   |
|    6    | 6  | '한이봉' |  '04'  |   false   |
|    7    | 7  | '고동어' |  '05'  |   false   |

어떤 것을 정렬하고 싶은지 명확하게 보인다. sort()는 인덱스 앞 뒤로 붙어있는 객체끼리 어떤 값으로 비교할지 쉽게 표현하고, 이해할 수 있게 돕는다.

단, sort는 리스트 자체를 변경하므로 원본 데이터의 변경을 원하지 않는다면 배열을 복사한 후에 사용해야 한다.

 

4. 새로운 형태의 배열

const memberRoleList = memberList.map(member => {
  const role = member['isManager'] ? '팀장' : '팀원';
  return `${member['name']}{${role}}`;
})
console.table(memberRoleList);
| (index) |  Values   |
-----------------------
|    0    | '김철수{팀장}' |
|    1    | '이영희{팀원}' |
|    2    | '고냠이{팀원}' |
|    3    | '강이지{팀원}' |
|    4    | '새의강{팀원}' |
|    5    | '반아나{팀원}' |
|    6    | '한이봉{팀원}' |
|    7    | '고동어{팀원}' |

마지막으로 map()을 통해 손쉽게 원하는 형태의 데이터를 만들었다. map()은 배열의 길이는 유지하면서 인덱스의 데이터 형태를 동일하게 변경하는 기능을 제공한다. 구체적으로는 Array<Object> → Array<String>의 형태가 됐다.

 

마치며

모던하고 쿨하게, 혹은 스마트하게 배열을 다루는 방법을 살펴봤다. 각 기능의 자세한 설명은 Mozilla의 Array prototype을 참고하면 된다.

추가로 여기서 설명한 함수들은 깊은 복사를 제공하지만, Object.assing()과 마찬가지로 더 깊은 수준까지는 복사하지 않는다. 따라서 상황에 따라 의도하지 않는 버그를 발생한다. 이 부분을 언제나 생각해야 한다.

프로젝트에서 아직도 for와 if로 도배된 코드를 작성하고 있다면, 이번 기회에 하나씩 변경해보자. 한 달 정도 지난 후에 문득 읽기 쉽게 작성된 코드가 눈에 들어오면서 뿌듯함 느껴질 것이다.

 

[모던 자바스크립트 관련 글]

 

[모던 자바스크립트] var를 사용하지 않아야 하는 이유

2022.09.03 - [Development/Etc] - [모던 자바스크립트] var를 사용하지 않아야 하는 이유 들어가며 ES6에서는 변수를 사용하기 위해 새로운 문법인 let과 const를 지원하면서 동시에, var의 사용을 지양하라고

bbubbush.tistory.com

 

[모던 자바스크립트] Object 기깔나게 사용하기

들어가며 자바스크립트에서 맵(Map)은 ES6가 되어서야 등장했다. 다른 언어에 비하면 상당히 늦은 편이다. 왜일까? 바로 객체(Object)라는 대안이 있었기 때문이다. 따라서 맵을 어떻게 사용하는지

bbubbush.tistory.com

 

[모던 자바스크립트] 어썸한 Funtion 변경사항

들어가며 자바스크립트는 함수로 대표된다고 해도 과언이 아니다. 이제는 객체지향적인 방식으로 작업하는 개발자도 많지만 과거부터 함수를 정의하고 사용해왔기에 아직까지 함수 지향적인

bbubbush.tistory.com

 

[모던 자바스크립트] Promise 한 방에 뿌수기

들어가며 아마 ES6의 내용 중 이해하기 가장 어려운 내용이 프로미스가 아닐까 생각한다. 다른 변경사항은 기능에 충실한 반면, 프로미스는 특정한 상황을 해결하기 위해 등장했기 때문이라 생

bbubbush.tistory.com

 

[모던 자바스크립트] 이름은 Optional, 적용은 Required

들어가며 ES6부터 객체의 값을 안정적으로 가져오는 옵셔널이 도입됐다. 개념도 쉽고 적용하기도 쉽기 때문에 활용도가 높다. 더 이야기할 게 없으니 바로 알아보자 🙂 전통적인 객체 프로퍼티

bbubbush.tistory.com