Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[Javascript] 자바스크립트 코딩의 기술 - 조 모건 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Javascript

[Javascript] 자바스크립트 코딩의 기술 - 조 모건

끈기JK 2022. 12. 26. 09:31

 

목차

 

2장 배열로 데이터 컬렉션을 관리하라

TIP 6  Includes()로 존재 여부를 확인하라

TIP 7  펼침 연산자로 배열을 본떠라

TIP 8  push() 메서드 대신 펼침 연산자로 원본 변경을 피하라

TIP 9  펼침 연산자로 정렬에 의한 혼란을 피하라

 

3장 특수한 컬렉션을 이용해 코드 명료성을 극대화하라

TIP 11  Object.assign()으로 조작 없이 객체를 생성하라

TIP 12  객체 펼침 연산자로 정보를 갱신하라

 

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

TIP 17  거짓 값이 있는 조건문을 축약하라

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

 

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

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

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

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

TIP 24  forEach()로 동일한 동작을 적용하라

TIP 25  체이닝으로 메서드를 연결하라

TIP 26  reduce()로 배열 데이터를 변환하라

 

6장 매개변수와 return 문을 정리하라

TIP 29  해체 할당으로 객체 속성에 접근하라

 

7장 유연한 함수를 만들어라

TIP 33  화살표 함수로 복잡도를 낮춰라

TIP 34 부분 적용 함수로 단일 책임 매개변수를 관리하라

TIP 35 커링과 배열 메서드를 조합한 부분 적용 함수를 사용하라

 

9장 외부 데이터에 접근하라

TIP 44  async/await로 함수를 명료하게 생성하라

 

 

 

 


 

내용

 

TIP 6  Includes()로 존재 여부를 확인하라

자바스크립트 배열에서 존재 여부를 확인하는 것은 언제나 다소 번거로웠습니다.

다행히 ES2016에 추가된 새로운 기능을 이용하면 번거로운 비교 절차를 생략할 수 있습니다. includes() 라는 새로운 배열 메서드를 이용하면 값이 배열에 존재하는지 여부를 확인해서 불(boolean) 값으로 true 또는 false를 반환합니다.

const sections = ['contact', 'shipping'];

function displayShipping(sections) {
  return sections.includes('shipping');
  }

 

 

 

TIP 7  펼침 연산자로 배열을 본떠라

배열에는 수많은 메서드가 있으므로 혼란스럽거나 조작(mutation)과 부수 효과(side effect)로 인한 문제에 맞닥뜨릴 수 있습니다. 다행히 펼침 연산자를 사용하면 최소한의 코드로 배열을 빠르게 생성하고 조작할 수 있습니다.

펼침 연산자의 기능은 단순합니다. 배열에 포함된 항목을 목록으로 바꿔줍니다. 목록은 매개변수 또는 새로운 배열을 생성할 때 사용할 수 있는 일련의 항목입니다.

const cart = ['Naming and Necessity', 'Alice in Wonderland'];
const copyCart = [...cart];
// ['Naming and Necessity', 'Alice in Wonderland'];

 

펼침 연산자를 slice() 메서드와 함께 사용하면 하위 배열을 목록으로 변환해 대괄호 안에 작성할 수 있습니다. 실제로 배열처럼 보이기도 합니다. 더 중요한 것은 원래 배열에 영향을 주지 않고 새로운 배열을 생성해준다는 점입니다.

function removeItem(items, removable) {
  const index = items.indexOf(removable);
  return [...items.slice(0, index), ...items.slice(index + 1)];
}

 

함수의 인수 목록을 생성할 때 펼침 연산자를 사용하는 것은 많이 사용하는 방법 중 하나입니다.

const book = ['Reasons and Persons', 'Derek Parfit', 19.99];
function formatBook(title, author, price) {
  return `${title} by ${author} $${price}`;
}

함수에 어떻게 정보를 전달할 수 있을까요? 한번 해봅시다. 아마도 다음과 같이 작성했을 것입니다.

formatBook(book[0], book[1], book[2]);

그렇지만 책에 대한 정보의 양이 바뀌었을 때도 코드를 고치지 않아도 되는 더 간결한 방법이 있습니다. 예를 들어 출판 연도를 추가하는 경우를 생각해 봅시다.

다음과 같은 코드를 작성했다면 훌륭합니다. 매개변수는 인수의 목록이므로 펼침 연산자를 이용하면 배열을 인수 목록으로 빠르고 쉽게 변환할 수 있습니다.

formatBook(...book);

 

 

 

TIP 8  push() 메서드 대신 펼침 연산자로 원본 변경을 피하라

조작은 예상치 못한 결과를 낳을 수 있습니다. 코드의 앞부분에서 컬렉션의 무언가를 수정하면 훨씬 더 찾기 어려운 버그를 만들 수 있지요. 가능하면 조작을 피하는 것이 좋습니다.

모던 자바스크립트의 상당수가 함수형 프로그래밍 형식을 취하기 때문에 부수 효과와 조작이 없는 코드를 작성해야 합니다.

 

결론적으로 내용을 목록으로 다시 쓰기만 하면 됩니다. 이렇게 하면 새로운 배열을 생성하기 때문에 원본 배열을 변경할 가능성은 전혀 없습니다.

const titles = ['Moby Dick', 'White Teeth'];
const moreTitles = [...titles, 'The Conscious Mind'];
// ['Moby Dick', 'White Teeth', 'The Conscious Mind'];

 

 

 

TIP 9  펼침 연산자로 정렬에 의한 혼란을 피하라

사용해야 할 메서드가 원본을 조작할 때, 어떻게 하면 조작을 막을 수 있을까요? 답은 간단합니다. 원본 데이터를 조작하지 않으면 됩니다. 그 대신에 사본을 만들고, 사본을 조작하세요.

function sortByYears(a, b) {
  if (a.years === b.years) {
    return 0;
  }
  return a.years - b.years;
}

[...staff].sort(sortByYears);

// [
//   {
//     name: 'Theo',
//     years: 5
//   },
//   {
//     name: 'Joe',
//     years: 10
//   },
//   {
//     name: 'Dyan',
//     years: 10
//   },
// ];

 

 

 

TIP 11  Object.assign()으로 조작 없이 객체를 생성하라

Object.assign() 은 일련의 객체를 전달받고 가장 먼저 인수로 받은 객체를 뒤이어 인수로 넘긴 객체의 키-값을 이용해서 갱신합니다. 그러고 나서 갱신된 첫 번째 객체를 반환합니다. 호출 시 인수 순서대로 적용되므로, 먼저 전달한 객체부터 적용되고 가장 나중에 전달한 객체가 맨 마지막으로 적용됩니다.

const defaults = {
  author: '',
  title: '',
  year: 2017,
  rating: null,
};

const book = {
  author: 'Joe Morgan',
  title: 'Simplifying JavaScript',
};
Object.assign(defaults, book);

// {
//   author: 'Joe Morgan',
//   title: 'Simplifying JavaScript',
//   year: 2017,
//   rating: null,
// }

첫 번재 객체에 빈 객체를 사용하면 조작이 발생하지 않습니다.

const updated = Object.assign({}, defaults, book);

중첩된 객체가 있는 경우에 Object.assign()을 이용해서 복사하도록 하면 모든 것을 갱신할 수 있습니다.

const employee2 = Object.assign(
  {},
  defaultEmployee,
  {
    name: Object.assign({}, defaultEmployee.name),
  },
);

 

 

 

TIP 12  객체 펼침 연산자로 정보를 갱신하라

객체 펼침 연산자는 키-값 쌍을 목록에 있는 것처럼 반환합니다. 배열 펼침 연산자와 마찬가지로 독립적으로 사용할 수는 없고 객체에 펼쳐지게 해야 합니다.

  const book = {
    title: 'Reasons and Persons',
    author: 'Derek Parfit',
  };

  const update = { ...book, year: 1984 };

  // { title: 'Reasons and Persons', author: 'Derek Parfit', year: 1984}

배열 펼침 연산자와 다른 점은 동이한 키에 서로 다른 값을 추가하면 어떤 값이든 가장 마지막에 선언된 값을 사용한다는 것입니다.

  const book = {
    title: 'Reasons and Persons',
    author: 'Derek Parfit',
  };

  const update = { ...book, title: 'Reasons & Persons' };

  // { title: 'Reasons & Persons', author: 'Derek Parfit' }

깊은 병합 문제는 객체 펼침 연산자를 사용해도 여전히 발생합니다. 다행히 객체 펼침 연산자로 좀 더 보기 좋게 문제를 해결할 수 있습니다.

function deepMerge() {
  const defaultEmployee = {
    name: {
      first: '',
      last: '',
    },
    years: 0,
  };

  const employee = {
    ...defaultEmployee,
    name: {
      ...defaultEmployee.name,
    },
  };

 

 

 

TIP 17  거짓 값이 있는 조건문을 축약하라

undefined는 false로 인식되기 때문에 동등(==)의 경우 문제가 발생할 수 있다.

엄격한 일치(===)를 이용해서 값이 있는지, 원하는 형식인지 확인한다.

또한 boolean 값을 확인할때도 엄격한 일치를 이용해 오류 발생을 방지한다.

function checkAuthorization() {
  if(employee.equipmenttraining !== true) {
    return '기계를 작동할 권한이 없습니다.';
  }
  return `반갑습니다, ${employee.name} 님`;
}
checkAuthorization(employee);
// '기계를 작동할 권한이 없습니다.'

 

 

 

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

icon.path 를 두번이나 확인하는 코드입니다.

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

코드를 개선하기 전에 논리 연산자가 어떻게 작동하는지 잠시 생각해봅시다. || 으로 작성하는 OR 연산자는 선택 가능한 값 중 하나라도 true 이면 true를 반환합니다. 즉 어떤 값이든 true를 반환하면 다른 값은 확인할 필요가 없습니다. 더 재미있는 부분은, 불 표현식 true 또는 false를 검사할 때 참이면 구태여 true로 변경하지 않고 검사를 통과한 참 값이 반환됩니다.

위 삼항 연산자는 아래와 같이 간결하게 바꿀 수 있습니다.

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

단락 평가의 가장 좋은 부분은 표현식의 끝에 기본값을 추가할 수 있다는 것입니다.

 

false가 있을 때 표현식을 중단하는 방법입니다.

조건문과 && 연산자를 조합하면 userConfig = undefined 로 인해 발생하는 TypeError를 피할 수 있습니다.

function getImage(userConfig) {
  if (userConfig.images && userConfig.images.length > 0) {
    return userConfig.images[0];
  }
  return 'default.png';
}

위 단락 평가를 삼항 연산자와 조합해서 확인 과정을 한 줄로 줄일 수도 있습니다.

function getImage(userConfig) {
  const images = userConfig.images;
  return images && images.length ? images[0] : 'default.png';
}

 

 

 

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

거의 대부분의 배열 메서드는 반환되는 배열의 길이나 형태를 변경할 수 있습니다. 길이를 변경할 것인지, 아니면 형태를 변경할 것인지 결정하기만 하면 됩니다.

  • map()
    • 동작: 형태를 바꿀 수 있지만 길이는 유지됩니다.
    • 예시: 전체 팀원의 이름을 가져옵니다.
  • sort()
    • 동작: 형태나 길이는 변경되지 않고 순서만 바꿉니다.
    • 예시: 팀원 이름을 알파벳순으로 정렬합니다.
  • filter()
    • 동작: 길이를 변경하지만 형태는 바꾸지 않습니다.
    • 예시: 개발자만 선택합니다.
  • find()
    • 동작: 배열을 반환하지 않습니다. 한 개의 데이터가 반환되고 형태는 바뀌지 않습니다.
    • 예시: 팀의 관리자를 찾습니다.
  • forEach()
    • 동작: 형태를 이용하지만 아무것도 반환하지 않습니다.
    • 예시: 모든 팀원에게 상여를 지급합니다.
  • reduce()
    • 동작: 길이와 형태를 바꾸는 것을 비롯해 무엇이든 처리할 수 있습니다.
    • 예시: 개발자와 개발자가 아닌 모든 팀원의 수를 계산합니다.

 

 

 

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

맵 함수는 입력한 배열에서 차례대로 하나씩 값을 가져와서 함수를 적용해 새로운 값을 반환합니다.

const band = [
  {
    name: '코벳',
    instrument: '기타',
  },
  {
    name: '에반',
    instrument: '기타',
  },
  {
    name: '션',
    instrument: '베이스',
  },
  {
    name: '브렛',
    instrument: '드럼',
  },
];
  const instruments = band.map(member => member.instrument);
  // ['기타', '기타', '베이스', '드럼']

 

 

 

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

먼저 match() 메서드를 알아보면, 정규 표현식에 일치하는 항목이 있으면 참 값인 배열을 반환하고, 그렇지 않은 경우에는 거짓 값인 null을 반환합니다.

'Dave'.match(/Dav/);
// ['Dav', index: 0, input: 'Dave']
'Michelle'.match(/Dav/);
// null

 

filter() 메서드는 반한되는 배열의 길이를 줄입니다.

  const daves = team.filter(member => member.match(/Da/));

 

find() 메서드는 배열에서 값을 하나씩 함수에 적용해 참 값을 반환하는 첫 번째 항목만 반환합니다.

const instructors = [
  {
    name: '짐',
    libraries: ['미디어교육정보 도서관'],
  },
  {
    name: '새라',
    libraries: ['기념 도서관', '문헌정보학 도서관'],
  },
  {
    name: '엘리엇',
    libraries: ['중앙 도서관'],
  },
];

function findMemorialInstructor(instructors) {
  const librarian = instructors.find(instructor => {
    return instructor.libraries.includes('기념 도서관');
  });

 

 

 

TIP 24  forEach()로 동일한 동작을 적용하라

forEach() 메서드 사용의 가장 좋은 경우는 함수의 유효 범위를 벗어나는 작업이 필요한 경우입니다. 즉, 반드시 부수 효과가 필요한 경우에 forEach()를 사용해야 합니다.

function sendInvitation(sailingClub, sendEmail) {
  sailingClub.forEach(member => sendEmail(member));
}

 

 

 

TIP 25  체이닝으로 메서드를 연결하라

체이닝은 값을 다시 할당하지 않고 반환된 객체에 메서드를 즉시 호출하는 것을 의미합니다.

function sendActiveMemberEmail(sailors, sendEmail) {
  sailors
    .filter(sailor => sailor.active)
    .map(sailor => sailor.email || `${sailor.name}@wiscsail.io`)
    .forEach(sailor => sendEmail(sailor));
}

 

 

 

TIP 26  reduce()로 배열 데이터를 변환하라

reduce() 메서드의 특징은 배열의 길이와 데이터 형태를 모두 변경할 수 있다는 점입니다.

 

map() 메서드를 reduce() 메서드로 표현시 다음과 같습니다.

const colors = dogs.map(dog => dog['색상']);

// reduce 사용시
const colors = dogs.reduce((colors, dog) => {
  return [...colors, dog['색상']];
}, []);

 

강아지 객체의 모든 키에 대해 고윳값을 분류하는 방법입니다. 빈 세트가 있는 객체로 시작합니다. reduce() 메서드에 넘겨주는 콜백 함수에서 각 항목을 세트에 추가합니다.

  const filters = dogs.reduce((filters, item) => {
    filters.breed.add(item['견종']);
    filters.size.add(item['크기']);
    filters.color.add(item['색상']);
    return filters;
  },
  {
    breed: new Set(),
    size: new Set(),
    color: new Set(),
  });

 

언어별로 몇 명인지 확인하려고 합니다. 개발자들이 사용하는 언어를 예측하기 어렵기 때문에 동적으로 추가해야 합니다.

  const aggregated = developers.reduce((specialities, developer) => {
    const count = specialities[developer.language] || 0;
    return {
      ...specialities,
      [developer.language]: count + 1,
    };
  }, {});

 

 

 

TIP 29  해체 할당으로 객체 속성에 접근하라

자바스크립트에서는 해체 할당이라는 과정을 통해 객체에 있는 정보를 곧바로 변수에 할당할 수 있습니다. 객체에 있는 키와 같은 이름의 변수를 생성하고, 객체에 있는 키에 연결된 값을 생성한 변수의 값으로 할당합니다.

const landscape = {
  photographer: 'Nathan',
};
const { photographer } = landscape;

photographer;
// Nathan

 

객체에 키가 존재하지 않으면 해체 할당을 하면서 동시에 기본값을 설정할 수도 있습니다.

const landscape = {};
const { photographer = 'Anonymous', title } = landscape;

photographer;
// Anonymous

title;
// undefined

 

키 이름을 모르면 마침표 세개와 변수 이름으로 어떤 추가 정보라도 담을 수 있습니다.

const landscape = {
  photographer: 'Nathan',
  equipment: 'Canon',
  format: 'digital',
};

const {
  photographer,
  ...additional
} = landscape;

additional;
// { equipment: 'Canon', format: 'digital' }

 

변수 이름으로 원래 키와 다른 이름을 지정할 수도 있습니다.

const landscape = {
  src: '/landscape-nm.jpg',
};
const { src: url } = landscape;

src;
// ReferenceError: src is not defined

url;
// '/landscape-nm.jpg'

 

우리가 원래 작성했던 기능을 해체 할당을 이용해서 다시 작성해보면 다음과 같습니다.

function displayPhoto(photo) {
  const {
    title,
    photographer = 'Anonymous',
    location: [latitude, longitude],
    src: url,
    ...other
  } = photo;
  const additional = Object.keys(other).map(key => `${key}: ${other[key]}`);
  return (`
    <img alt="${title} 사진 ${photographer} 촬영" src="${url}" />
    <div>${title}</div>
    <div>${photographer}</div>
    <div>위도: ${latitude} </div>
    <div>경도: ${longitude} </div>
    <div>${additional.join(' <br/> ')}</div>
  `);
}

 

 

TIP 33  화살표 함수로 복잡도를 낮춰라

고차 함수는 그저 다른 함수를 반환하는 함수입니다.

const discounter = discount => price => price * (1 - discount);

const tenPercentOff = discounter(0.1);
console.log(tenPercentOff(100));
// 90

 

 

TIP 34 부분 적용 함수로 단일 책임 매개변수를 관리하라

고차 함수는 매개변수를 가두는 방법을 통해 특별한 값을 제공하므로, 나중에 원래의 인쉥 접근할 수 있게 해두고 함수 실행을 마칠 수 있습니다. 또한, 매개변수를 분리해 함수의 의도를 명확하게 유지할 수 있습니다.

 

지역 이름이 담긴 배열을 받아서 지역을 상징하는 새(bird) 이름을 반환하는 함수가 있는 경우, 결과 배열은 괜찮아 보이지만 결과적으로는 원본과 결괏값을 배열 쌍으로 연결해야 합니다.

const birds = getBirds('kansas', 'wisconsin', 'new mexico');
// ['meadowlark', 'robin', 'roadrunner']

const zip = (...left) => (...right) => {
  return left.map((item, i) => [item, right[i]]);
};
zip('kansas', 'wisconsin', 'new mexico')(...birds);
// [
//   ['kansas', 'meadowlark'],
//   ['wisconsin', 'robin'],
//   ['new mexico', 'roadrunner']
// ]

 

 

TIP 35 커링과 배열 메서드를 조합한 부분 적용 함수를 사용하라

함수를 완전히 분리하기 전에 함수에 필요한 인수의 수를 줄일 수 있도록 인수를 분리하는 것이 훨씬 더 중요합니다. 한 번에 인수를 하나만 받는 함수를 '커링(currying)'이라고 합니다.

const dogs = [
  {
    이름: '맥스',
    무게: 10,
    견종: '보스턴 테리어',
    지역: '위스콘신',
    색상: '검정색',
  },
  {
    이름: '도니',
    무게: 90,
    견종: '래브라도레트리버',
    지역: '캔자스',
    색상: '검정색',
  },
  {
    이름: '섀도',
    무게: 40,
    견종: '래브라도레트리버',
    지역: '위스콘신',
    색상: '갈색',
  },
];

정해진 체중보다 무게가 적게 나가는 강아지를 찾을 수 있는 함수를 작성해봅시다. 비교 함수를 하드 코딩하지 않고 필터 함수에 콜백 함수로 전달할 수 있게 만들어봅시다.

function getDogNames(dogs, filterFunc) {
  return dogs
    .filter(filterFunc)
    .map(dog => dog['이름'])
}

getDogNames(dogs, dog => dog['무게'] < 20);

이 경우에도 숫자 20과 같은 값을 하드 코딩하고 있습니다.

우리의 목표는 부분 적용 함수를 이용해서 필요한 값을 미리 담아두는 것입니다.

우리가 해야 할 일은 비교 함수를 다시 작성해서 비교를 위한 값을 매번 하드 코딩할 필요가 없도록 만드는 것뿐입니다.

const weightCheck = weight => dog => dog['무게'] < weight;

getDogNames(dogs, weightCheck(20));

getDogNames(dogs, weightCheck(50));

두 개의 함수와 두 개의 인수 집합으로 제한할 필요가 없다는 점입니다.

먼저 첫 번째 함수에서 색상과 같은 비교 대상을 지정합니다. 다음 함수에서 '검정색'과 같은 비교할 값을 전달합니다. 마지막 함수는 개별 강아지에 대한 정보를 받습니다.

const identity = field => value => dog => dog[field] === value;
const colorCheck = identity('색상');
const stateCheck = identity('지역');

getDogNames(dogs, colorCheck('갈색'));
// ['섀도']

getDogNames(dogs, stateCheck('캔자스'));
// ['섀도']

모든 조건을 충족하거나, 하나라도 충족하는 강아지를 찾을때 다음과 같이 작성할 수도 있습니다.

function allFilters(dogs, ...checks) {
  return dogs
  .filter(dog => checks.every(check => check(dog)))
  .map(dog => dog['이름']);
}
allFilters(dogs, colorCheck('검정색'), stateCheck('캔자스'));
// ['도니']

function anyFilters(dogs, ...checks) {
  return dogs
  .filter(dog => checks.some(check => check(dog)))
  .map(dog => dog['이름']);
}

anyFilters(dogs, weightCheck(20), colorCheck('갈색'));
// ['맥스', '섀도']

 

 

TIP 44  async/await로 함수를 명료하게 생성하라

async 키워드를 이용해서 선언한 함수는 비동기 데이터를 사용한다는 것을 의미합니다. 비동기 함수의 내부에서 await 키워드를 사용하면 값이 반한될 때까지 함수의 실행을 중지시킬 수 있습니다.

function getUserPreferences() {
  const preferences = new Promise((resolve, reject) => {
    resolve({
      theme: 'dusk',
    });
  });
  return preferences;
}

 

async/await가 어떻게 사용되는지 살펴보겠습니다.

getUserPreferences()
  .then(preferences => {
    console.log(preferences.theme);
  });
// 'dusk'

 

먼저 getUserPreferences()를 호출하는 부분을 다른 함수에서 감싸도록 해야합니다. 이를 위해 getTheme()라는 새로운 함수를 작성하세요. 이 함수는 모든 비동기 함수 호출을 담당할 것입니다. function 키워드 앞에 async 키워드를 추가해 비동기 함수를 호출한다는 점을 표시합니다.

getTheme() 함수를 내부에서 getuserPreferences()를 호출하기 전에 await 키워드를 추가해서 getUserPreferences()가 프라미스를 반환한다는 것을 알려줘야 합니다.

async function getTheme() {
  const { theme } = await getUserPreferences();
  return theme;
}

비동기 함수의 재미있는 점은 프라미스로 변환된다는 것입니다.

getTheme()
  .then(theme => {
    console.log(theme);
  });