[javascript] 스터디 2주차 - 자바스크립트 코딩의 기술 4-5장 요약

9 minute read

시간이 부족했다는 피드백을 통해 이번주는 2장만 살펴보기로 했다.

이번주는 조건문과 반복문을 정리하는 스킬을 확인할 수 있을 것 같다.

4. 조건문을 깔끔하게 작성하라.

4.1. 거짓 값이 있는 조건문을 축약하라

거짓 값의 목록

  • false
  • null
  • 0
  • NaN(숫자가 아님)
  • ’’
  • ””

배열과 객체는 비어있더라도 항상 참 값이다. 객체 또는 배열이 비어있는지 확인하려면 [].length, Object.keys({}),length 를 통해 값을 반환받아야한다.

true/false 값을 활용하면 긴 표현식을 축약시킬 수 있다.

const employee  = {
    name: 'Eric',
    equipmentTraining: true,
}

if(!employee.equipmentTraning){
    return '작동 권한이 없습니다.';
}

위 코드는 문제가 없지만, 만약 객체를 조작하게 된다면 의도하지 않은 결과를 얻을 수 있으므로, 조건문 사용 시 엄격한 일치를 이용하는 방법을 추천하고 있다. ===, !== 사용.

if(employee.equipmentTraning !== true){
    return '작동 권한이 없습니다.';
}

4.2. 삼항 연산자로 빠르게 확인하라.

삼항연산자를 사용하면 코드를 줄일 뿐만 아니라, 변수의 재할당을 줄인다.

let permissions;
if(titile === '과장'){
    permissions = ['근로시간', '수당'];
}else{
    permissions = ['근로시간'];
}
// 삼항연산자를 사용한다면...
const permissions = (title === '과장') ? ['근로시간', '수당'] : ['근로시간'];
// const 로 선언할 수 있으며, 예측가능한 값이 된다.

이렇게 편리한 삼항연산자이지만 조건이 여러개일 경우 연결해서 사용하면 코드가 지저분해지니, 독립적인 함수로 만드는 것을 권장한다.

function getTimePermissions({ title }){
    if(title === '과장'){
        return ['근로시간', '초과근무승인', '수당'];
    }
    if(title === '부장'){
        return ['근로시간', '초과근무승인'];
    }
    return ['근로시간'];
}

const permissions = getTimePermissions([{ title: '사원' }]); // ['근로시간']

4.3. 단락 평가를 이용해 효율성을 극대화하라

다음은 아이콘 경로를 생성하고, 해당하는 path가 없을 경우 디폴트 주소를 반환하는 함수다.

function getIconPath(icon){
    const path = icon.path ? icon.path : 'uploads/default.png';
    return `http://assets.foo.com/${path}`;
}

보다시피 icon.path가 반복되고 있다. 비슷한 코드를 짜본 듯한 기시감이 든다… 이 코드는 ||연산자를 이용하여 단락평가를 사용해서 정리할 수 있다.

function getIconPath(icon){
    const path = icon.path || 'uploads/default.png';
    return `http://assets.foo.com/${path}`;
}

OR연산자를 사용하면 첫번째 비교값이 true일 경우 조건을 통과하므로 성립이 된다. 그러나 속성이 정의되지 않은 경우 (객체의 속성이 정의되지 않으면 undefined) 제대로 작동하지 않는 점에 주의하여 적절한 상황에서 단락평가를 사용해야할 것이다!

요약

  1. 조건문에서 사용하는 조건은 true/false 값을 활용하라.
  2. 거짓으로 분류되는 값을 보고 의도한 결과를 얻으려면 엄격한 일치를 사용할 것.
  3. 코드를 간결하게하고, 값의 재할당을 막기위해 삼항연산자를 사용한다.
  4. ||연산자를 사용해 단락평가를 활용할 수 있는 곳은 활용하라.
  5. 코드의 간소화가 어려울 때는 함수로 분리하여 조건을 확인하는 것을 권장한다.

5. 반복문을 단순하게 만들어라.

5.1. 화살표 함수로 단순하게 만들라.

화살표 함수를 사용하면 다음 정보를 생략할 수 있다.

  • function 키워드
  • 인수를 감싸는 괄호
  • return 키워드
  • 중괄호

화살표 함수 사용 예

function capitalize(name) { // 예시는 기명함수인 점을 참고한다.
    return name[0].toUpperCase() + name.slice(1);
}
// 화살표 함수 사용 
const capitalize = name => name[0].toUpperCase() + name.slice(1);
function applyCustomGreeting(name, callback){
    return callback(capitalize(name));
}
// 콜백함수를 받아 위에서 만든 capitalize를 적용
applyCustomGreeting('mark', function(name){
    return `안녕, ${name}`
});
// 화살표 함수로 변경
applyCustomGreeting('mark', name => `안녕 ${name}`;); // 안녕, Mark!

5.2. 배열 메서드로 반복문을 짧게 작성하라.

배열 메서드를 사용하면 for문의 사용을 최소화 하고 코드를 간결하게 만들 수 있다.

배열에 다음 다섯 개의 객체가 담겨있을 때,

const team = [
    {
        name: 'melinda',
        postion: 'ux designer'
    },
    {
        name: 'katie',
        postion: 'strategist'
    },
    {
        name: 'madhavi',
        postion: 'developer'
    },
    {
        name: 'c',
        postion: 'manager'
    },
    {
        name: 'chiris',
        postion: 'developer'
    }
]

몇가지 배열 메서드와 활용예시를 간략히 설명하면 다음과 같다.

map()

  • 동작 : 형태를 바꿀 수 있다. (길이는 유지)
  • 예시 : 전체 팀원의 이름만을 가져오기
  • 결과 : ['melinda', 'katie', 'madhiv', 'justin', 'chiris']

sort()

  • 동작 : 순서만을 바꾼다. (형태, 길이는 변경되지 않음)
  • 예시 : 팀원 이름을 알파벳 순으로 정렬
  • 결과 : [{name: 'chiris', position: 'developer'}, {name: 'jusutin', position: 'manager'} ...]

filter()

  • 동작 : 길이를 변경하지만 형태는 바꾸지 않음
  • 예시 : 개발자 직무만 선택한다.
  • 결과 : [{name: 'madhavi', position: 'developer'}, {name: 'chiris', position: 'developer'}]

find()

  • 동작 : 배열을 반환하지 않는다. 한 개의 데이터만 반환되며 형태는 바뀌지 않음.
  • 예시 : 팀 매니저를 찾는다.
  • 결과 : [{name: 'madhavi', position: 'manager'}]

forEach()

  • 동작 : 형태를 이용하나 반환하지 않는다.
  • 예시 : 모든 팀원에게 상여를 지급한다.
  • 결과 : name이 상여를 받았습니다! ... 반복출력 (반환값은 없다.)

reduce()

  • 동작 : 길이와 형태를 바꾸는 것을 비롯한 모든 처리
  • 예시 : 개발자와 개발자가 아닌 모든 팀원의 수를 계산
  • 결과 : {developers: 2, non-developers: 3}

예제코드

배열메서드를 활용해 문자열로 저장된 금액을 부동소수점 형태로 변경하는 예제다. (책 앞부분에 for문을 사용한 예시가 있었다.)

const price = ['1.0', '흥정가능','2.15'];

// 반복문을 이용한 기존 방법
//const formattedPrices = [];
//for(let i = 0; i < price.length; i+){
//     const price = parseFloat(prices[i]);
//     if(price){  // NaN일 경우
//         formattedPrices.push(price);
//     }
// }

// 배열메서드를 활용한 방법!!
cosnt formattedPrices = prcice.map(price => parseFloat(price))
    // [1.0, NaN, 2.15]
    .filter(price => price);
    // [1.0, 2.15]

5.3. map() 메서드로 비슷한 길이의 배열을 생성하라

map()메서드를 이용하면 원본 배열에서 필요한 정보를 꺼내 새로운 배열을 생성할 수 있다. (컬렉션인 map과 혼동하지 말 것)

예시를 확인하자.

// band 배열의 각 객체의 instrument 요소만 모은 배열을 만드려고한다.
const band = [{
        name: 'corbett',
        instrument: 'guitar',
    },
    {
        name: 'evan',
        instrument: 'guitar',
    },
    {
        name: 'sean',
        instrument: 'bass',
    },
    {
        name: 'brett',
        instrument: 'drums',
    }
];

// const instruments = [];

// function getInstrument(member){
//     return member.instrument;
// }

// 기존 방법을 사용하면 저장할 배열을 따로 선언하고 for문을 통해 할당함
// for(let i = 0; i < band.length; i++){
//     instruments.push(getInstrument(band[i]));
// }

// map 메서드를 사용하면 한줄로 축약가능.
// const instruments = band.map(getInstrument);
const intruments = band.map(member => member.instrument);
  • 실행 결과 배열을 얻게되는 사실을 인지하며, 미리 선언할 필요가 없다.
  • 원본 배열과 같은 길이로 생성된다는 것을 알 수 있다.
  • 악기만 담기고 다른 정보는 담기지 않는 점을 알 수 있다.

map 메서드를 사용하면 예측가능하면서 단순해진다.

5.4. filter()와 find()로 데이터의 부분집합을 생성하라.

배열에 담긴 항목의 형태는 유지하면서 길이를 변경하려면 filter()find()를 사용한다. filter()map()과 다르게 배열의 정보를 변경하지는 않고, 반환되는 배열의 길이를 줄인다.

다음 예제에서 예시를 확인하자. 다음은 Dav를 포함하는 문자열을 필터링 하는 예제이다.

filter() 예제

const team = ['Michelle B', 'Dave L', 'Dave C', 'Courtney B', 'Davina M'];

// for문 사용
// const daves = [];
// for(let i = 0; i < team.length; i++){
//     if(team[i].match(/Dav/)){
//         daves.push(team[i]);
//     }
// }

// filter를 사용
const daves = team.filter(team => team.match(/Dav/));

[참고] 예제에서 쓰이는 match()메서드는 문자열이 정규식 표현과 일치하면 참 값인 배열로 반환하고, 일치하지 않으면 null을 반환한다. 'Dave'.match(/Dav/) //['Dav', index[0], input: 'Dave']

filter() 메서드는 map()과 다르게 전달되는 함수가 반드시 true값을 반환하도록 해야한다. 그리고 filter 메서드는 항상 배열을 반환하며, 조건에 일치하는 값이 없는 경우도 빈 배열을 반환한다.

find() 예제

find() 메서드는 일치하는 항목이 최대 하나일 경우 사용한다. 예를 들면 특정 ID를 찾는 경우 매우 유용하다. 또는 특정 항목의 첫 번째 인스턴스가 필요한 경우에도 유용하다. 반복문에서 break를 사용하는 경우가 적합한 경우라고 생각하면된다.

다음은 도서관 사서가 사용할 일정 앱을 만드는 예제이다. 사서가 둘 이상인 곳은 없다고 가정한다.

const intructiors = [
    {
        name: 'Jim'
        libraries: ['미디어교육정보 도서관'],
    },
    {
        name: 'Sera'
        libraries: ['기념 도서관', '문헌정보학 도서관'],
    },
    {
        name: 'Eliot'
        libraries: ['중앙 도서관'],
    }
]

// for문 사용
// let memorialIntructor;
// for(int i = 0; i < instructors.length; i++){
//     if(instructors[i].libraries.includes('기념 도서관')){
//         memorialIntructor = instructors[i];
//     }
// }

// find 활용
const librarian = instructors.find(instructor => instructor.libraries.includes('기념 도서관')); );

5.5. forEach()로 동일한 동작을 활용하라.

forEach()를 이용하면 배열의 각 항목에 동작을 적용할 수 있다. forEach()는 입력 배열을 변경하지 않고 모든 항목에 동일한 동작을 수행한다.

예제코드

전체 회원에게 메일을 보내는 스크립트를 작성한다. (메일을 전송하는 함수는 따로 작성되어있다고 가정.)

const sailingClub = ['yi hong', 'andy', 'darcy', 'jessi', 'alex', 'nathan'];

// for문을 사용한 예
for(let i = 0 ; i < sailingClub.length; i++){
    sendEmail(sailingClub[i]);  // sendEmail 함수가 작성됨을 가정
}

// forEach를 사용한 예
sailingClub.forEach(member => sendEmail(member) );

forEach 메서드는 코드를 단순화 시킨다기보다는, 함수의 유효범위를 벗어나는 작업, 부수효과가 필요한 경우에 사용한다. 가장 중요한 사용이유는 체이닝 과정에서 다른 배열 메서드와 결합할 수 있기때문이다.

5.6. 체이닝으로 메서드 연결하기

체이닝(chaining)를 정의하면, 값을 다시 할당하지 않고 반환된 객체에 메서드를 즉시 호출하는 것을 의마한다. 쉽게 말하면 배열 메서드를 연이어 호출할 수 있는 것을 의미한다.

이전에 정리했던 예제에서도 체이닝은 계속 사용해왔으니 금방 활용할 수 있을 것 같다.

예제코드

다음은 활동중인 클럽회원들에게 메일을 보내는 예제다. active가 true인 회원에게 메일을 보내되, 개인이메일 정보가 있는 회원에게는 개인 이메일로, 없으면 클럽 기본 이메일로 송신하고자한다.

const sailors = [
    {
        name: 'yi hong',
        active: true,
        email: 'yh@yhproduction.io'
    },
    {
        name: 'alex',
        active: true,
        email: ''
    },
    {
        name: 'nathan',
        active: false,
        email: ''
    }
]

// 체이닝 사용
sailors.filter( sailor => sailor.active )
    .map(sailor => sailor.email || `${sailor.name}@wiscsail.io`)
    .forEach(sailor => sendEmail(member));

위 처럼 체이닝을 사용하면 코드가 단순화 될 뿐만아니라 한눈에 이해하는데 도움이 된다. 유일한 단점이 있다면, 메서드를 호출할 때마다 반환된 배열 전체를 다시 반복한다는 점인데, 대규모 데이터를 다루는 것이 아니라면 성능향상 보다는 가독성을 우선하는 것이 좋다. 반복이 많다는 점만 기억하면 될 것 같다.

체이닝을 사용할때 주의해야할 점

  • 세미콜론(;)이 들어가있는지 확인할 것
  • 실행순서를 지킬 것

5.7. reduce()로 배열 데이터를 변환하라

reduce()는 원본배열과 크기가 형태가 다른 새로운 배열을 생성한다.

const callback = function(collectedValues, item){
    return [...collectedValues, item];
};

const saying = ['veni', 'vedi', 'veci'];
const initialValue = [];
const copy = saying.reduce(callback, initialValue);

reduce() 메서드는 콜백 함수에서 항상 누적된 값을 반환한다. 한 번에 이해가 되진 않는다… MDN을 참고해보자

🔗 MDN - Array.prototype.reduce()

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환한다. 이 리듀서라는 것이 더 많은 값을 쉽게 다룰 수 있도록 유연성을 제공한다.

reduce()는 데이터의 크기와 형태를 바꿀 수 있어서 사용방법이 무궁무진하다. map()이나 filter(),find() 메서드도 reduce()로 구현할 수 있다~

예시코드

다음 코드는 개발자에 대한 정보(이름과 다루는 언어)를 담은 목록을 보고, 언어별로 몇명인지 세는 기능을 만들고 있다.

// 개발자 목록(객체배열)
const developers = [
  {
    name: 'Jeff',
    language: 'php',
  },
  {
    name: 'Ashley',
    language: 'python',
  },
  {
    name: 'Sara',
    language: 'python',
  },
  {
    name: 'Joe',
    language: 'javascript',
  },
];

// reduce()를 사용하여 기능 구현
const aggregated = developers.reduce((specialities, developer) => {
    const count = specialities[developer.language] || 0;
    return {
        ...specialities,
        [developer.language]: count + 1,
    };
}, {});

// 결과
// aggregated {php: 1, python: 2, javascript: 1}

결론적으로 reduce()는 위 예시처럼 배열을 이용해서 다른 형태의 데이터를 만들 필요가 있을 경우 사용하게 된다.

5.8 for…in, for…of로 반복문을 정리하라.

다음과 같은 함수가 있다고 가정하자.

const entries = [...firms];
for(let i = 0; i < entries.length; i++){
    const [id, name] = entries[i];
    if(!isAvailable(id)){
        return `${name}는 사용할 수 없습니다.`;
    }
}
return '모든 회사를 사용할 수 있습니다.';

firms는 회사의 ID를 키, 회사 이름을 값으로 갖는 map이다. 예시 함수는 id를 체크하여 회사가 존재여부를 체크하여 메시지를 출력하는 기능을 한다.

for…of

컬렉션의 이터레이터를 사용할 수 있다. index를 반복하는 대신 컬렉션 멤버를 직접 순회한다고 보면 된다.

for(const firm of firms){
    const [id, name] = firm;
    if(!isAvailable(id)){
        return `${name}는 사용할 수 없습니다.`;
    }
}
return '모든 회사를 사용할 수 있습니다.';

장점 : 이터러블을 순회하기 위해 배열로 변환할 필요가 없다. (최적화를 위해 고려할만함)

문제점 : 예측가능성이 줄어든다.

일반적으로 배열메서드를 우선해서 사용하기 때문에 반드시 필요할 경우 사용하는 것을 권함.

for…in

for…of 와 매우 유사하지만 키-값 객체에서만 작동하는 반복문. 객체의 속성을 순회한다.

for(const id in firms){
    if(!isAvailable(parseInt(id, 10))){
        return `${firms[id]}는 사용할 수 없습니다.`;
    }
}
return '모든 회사를 사용할 수 있습니다.';

for...of와 다른점을 확인해보면, 반복자에 속성명이 들어가는 것이 먼저 눈에 보인다. 이와 같이 for...in을 사용하면 키-값 쌍이 아니라 속성을 가져오기 때문에 이름과 값을 따로 추출할 필요가 없다.

예시코드의 경우 id가 정수이므로 parseInt()를 사용해서 변환한 것을 참고하자.

요약

  • 화살표 함수를 사용하면 익명함수를 더욱 직관적이고 간결하게 만들 수 있다.
  • 배열 메서드를 활용하면 반복문 사용을 최소화하고 직관적이며 체이닝을 할 수 있으니 적절하게 활용하라.
  • map()은 길이를 유지하며 형태를 바꿀 수 있다. (ex: 객체배열의 일부 속성만 배열로 리턴받기)
  • filter()는 특정 조건에 맞는 데이터를 선택한다. 길이는 바꾸지만 형태는 바뀌지않는다. find()도 동일한데, 단 하나의 데이터만 반환한다. (ex: id검색)
  • forEach()는 형태를 이용하면서 반복되는 작업을 할 수 있다.
  • reduce()는 길이와 형태를 바꿀 필요가 있을 때 사용할 수 있다.
  • for…of 는 컬렉션의 멤버를 순회할 수 있다.
  • for…in 은 키-값 객체의 속성을 순회한다.

Categories:

Updated:

Comments