[javascript] 스터디 3주차 - 자바스크립트 코딩의 기술 6-7장 요약

9 minute read

회사 적응과 스터디로 심신이 지치고있다… 그래도 이번주는 버텨보자는 마음으로 진행하려고 한다.

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

6.1. 매개변수 기본 값을 생성하라

function convertWeight(weight, ounce = 0, roundTo = 2){
    const total = weight + (ounce / 16);
    const conversion = total / 2.2;
    return roundToDecimalPlace(conversion, roundTo); // 반올림 처리를 위한 헬퍼 함수
}

처음에는 무게만 파라미터로 받다가 요구 조건이 늘어난 함수다.

  • 자바스크립트는 함수에 모든 매개변수를 전달할 필요가 없다. 매개변수를 누락하면 값은 undefined가 된다.
  • 누락된 값에 대해서 디폴트 값을 지정할 수 있다.
convertWeight(4,0,2) // 1.82
convertWeight(4,undefined,2) // 1.82

6.2. 해체 할당으로 객체 속성에 접근하라

해체 할당은 객체에 있는 정보를 곧바로 변수에 할당하는 과정을 말한다.

const landscape = {
  title: 'Landscape',
  photographer: 'Nathan',
  equipment: 'Canon',
  format: 'digital',
  src: '/landscape-nm.jpg',
  location: [32.7122222, -103.1405556],
};

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

photographer; // Nathan
// 펼침연산자를 사용하여 해체할당하면 나머지 속성값을 저장한 객체가 생성된다.
addtional; // {title: "Landscape", equipment: "Canon", format: "digital", src: "/landscape-nm.jpg", location: Array(2)}

// 속성명: 이름 으로 할당하면, 해당 이름으로 저장된다.
const { src: url } = landscape;

src; // ReferenceError: src is not defined

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

// 객체 안의 배열을 해체할당 하기.
const { location: [lat, lng] } = landscape;

lat; // 32.7122222

lng; // -103.1405556

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

매개변수를 해체할당으로 받아 개선된 최종코드다. 언뜻 복잡해보일 순 있는데 기존 코드에 비하면 훨씬 깔끔하고 구조만 안다면 어떤일이 일어나고 있는지 금방 확인할 수 있을 것 같다.

해체할당에서 주의해야할 사항은, 키-값 쌍 또는 클래스의 인스턴스인 객체에서만 사용할 수 있고, 맵에는 사용할 수 없다는 점이다.

6.3. 키-값 할당을 단순화 하라

이번에는 역으로 객체에 정보를 빠르게 만드는 축약한 키-값 할당을 배운다.

이전 예시의 landscape 객체를 참조할 때, 위,경도 데이터를 사용하여 지역정보를 조회한다고 가정한다.

const landscape = {
  title: 'Landscape',
  photographer: 'Nathan',
  location: [32.7122222, -103.1405556],
};

function determineCityAndState([latitude, longitude]) {
  // 좌표 탐색을 처리합니다
  const region = {
    city: 'Hobbs',
    county: 'Lea',
    state: {
      name: 'New Mexico',
      abbreviation: 'NM',
    },
  };
  // 로직에 신경쓰지 않고 location정보 기반으로 region을 리턴받았다고 가정.
  return region;    
}

function getCityAndState({ location }) {
  const { city, state } = determineCityAndState(location);
  return {
    city,                       // 위 코드를 통해 받아온 값.
    state: state.abbreviation,  // 전통적인 객체 키-값 선언을 하고 있음에 주의
  };
  // {
  //   city: 'Hobbs',
  //   state: 'NM'
  // }
}

function setRegion({ location, ...details }) { // landscape 객체를 해체할당
  const { city, state } = determineCityAndState(location);
  return {
    city,
    state: state.abbreviation,
    ...details,     // 리턴 값에 주목
  };
}

setRegion(landscape);
// {
//   title: 'Landscape',
//   photographer: 'Nathan',
//   city: 'Hobbs',
//   state: 'NM',
// };

6.4. 나머지 매개변수로 여러개의 인수를 변수로 전달하라

객체와 달리, 전체 개수를 알 수 없는 매개변수들을 처리할 때는 나머지 매개변수를 사용한다.

// items 배열의 각 요소의 길이가 max를 초과하지 않는지 체크하는 함수
function validateCharacterCount(max, items){
  return items.every(item => item.length < max);
}
validateCharacterCount(10, ['Hobbs', 'Eagles']); // true
// 파라미터가 배열이 아닐 시
validateCharacterCount(10, 'wvoquine');
// TypeError: items.every is not a function 

// items 를 나머지 매개변수로 전달 (...)
function validateCharacterCount(max, ...items){
  return items.every(item => item.length < max);
}
validateCharacterCount(10, 'wvoquine')  // true

나머지 매개변수를 사용하는 이유

(1) 인수를 배열로 다루는 것을 명시

(2) 코드 디버깅에 도움 (길게 나열된 인수 확인)

['Spirited Away', 'Princess Mononoke'].map((film, ...other) => {
  console.log(other);
  // [0, ['Spirited Away', 'Princess Mononoke']]
  // [1, ['Spirited Away', 'Princess Mononoke']]
  return film.toLowerCase();
  // ["spirited away", "princess mononoke"]
});

(3) 함수간에 속성을 전달하면서 해당 속성을 조작할 필요가 없을 때 사용

// 모달창에서 다른 함수로 정보를 갱신하면서 창을 닫아야하는 경우
function applyChanges(...args){
  updateAccount(...args);
  closeModal();
}

인수에 나머지 매개변수를 사용하는 경우, 반드시 마지막 매개변수에 사용해야 한다는 점에 유의해야한다.

예를 들어 첫 번째 항목을 반환하는 shift()메서드는 만들 수 있지만, 마지막 항목을 반환하는 pop()메서드는 다시 만들 수 없다. (params/rest/rest.js 예제 참조)

요약

  • 매개 변수에 = 기호를 사용하여 디폴트 값을 지정할 수 있다.
  • 해체 할당을 사용하면 객체의 속성값을 바로 변수에 할당할 수 있다.
  • 키-값 할당을 단순화하여 필요한 정보를 갖는 객체를 빠르게 생성할 수 있다. ??
  • 나머지 매개변수 ... 는 매개변수를 배열로 다루는 것을 명시하고, 디버깅 시 나열된 인수를 확인할 수 있으며 함수 간에 속성을 전달하면서 해당 속성을 조작할 필요가 없을 때 사용된다.

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

7.1. 테스트 하기 쉬운 함수를 만들어라

7.1.1. 대표적인 테스트 프레임워크

책에서는 모카를 사용한다.

describe(), it()함수의 기본을 이해하고 기댓값을 알고있어야 한다.

추후 추가 및 수정 예정…

7.2. 화살표 함수로 복잡도를 낮춰라

화살표 함수를 사용해 인수를 해체 할당, 객체를 반환, 고차 함수를 만드는 방법을 알아본다.

인수를 해체 할당

const name = {
  first: 'Lemmy',
  last: 'Kilmister',
}

function getName({first, last}){
  return `${first} ${last}`;
}

// 화살표 함수로 변경
//const getName = {first, last} => { 오류!
const getName = ({first, last}) => {
  return `${first} ${last}`;
}
 

[주의] 반드시 매개변수를 괄호()로 묶어주어야한다.


return를 생략하여 객체 반환

const comic = {
  first: 'Peter',
  last: 'Bagge',
  city: 'Seattle',
  state: 'Washington',
}

const getNameAndLocation = ({ first, last, city, state}) => ({
  fullName: `${first} ${last}`,
  location: `${city}, ${state}`,
});
getNameAndLocation(comic);
// {
//  fullName: 'Peter Bagge',
//  location: 'Seattle, Washington'
// }

고차함수 만들기

고차 함수는 다른 함수를 반환하는 함수로, 여기서는 만드는 방법만 살펴본다.

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

const tenPercentOff = discounter(0.1);  // 함수를 반환
tenPercentOff(100); // 90
// 화살표 기능을 활용하여 함수 정리
const discounter = discount => price => price * (1 - discount);
// 매개변수를 연결하여 바로 호출
discounter(0.1)(100); // 90

고차 함수는 매개 변수를 가두는데 사용할 수 있으며, 배열 메서드와 나머지 매개변수에 도움을 줄 수 있다 한다.


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

부분 적용 함수(partially applied function)를 사용하면, 독립적인 매개변수 집합을 만들 수 있다. 다음은 서로 다른 출처에서 얻은 정보를 결합해 완전한 정보를 반환하는 예제다.

[ 입력받을 데이터 ]

  • building : 건물의 주소와 개방시간
  • manager : 행사 담당자의 이름과 전화번호
  • program : 행사의 형태, 행사 시간이 개방 시간보다 짧은 유형
  • exhibit : 행사의 형태, 건물이 열려있는 동안 계속 진행되며, 큐레이터에 대한 정보가 있음.
const building = {
  hours: '8 a.m. - 8 p.m.',
  address: 'Jayhawk Blvd',
};

const manager = {
  name: 'Augusto',
  phone: '555-555-5555',
};

const program = {
  name: 'Presenting Research',
  room: '415',
  hours: '3 - 6',
};

const exhibit = {
  name: 'Emerging Scholarship',
  contact: 'Dyan',
};

// 건물, 행사 장소 담당자, 프로그램 or 전시회 의 세가지 인수를 받아서 결합하는 함수
function mergeProgramInformation(building, manager, event){
  const { hours, address } = building;
  const { name, phone } = manager;
  const defaults = {
    hours,
    address,
    contact: name,
    phone,
  }
  return { ...default, ...event };
}
// 함수 반복 호출
const programInfo = mergeProgramInformation(building, manager, program);
const exhibitInfo = mergeProgramInformation(building, manager, exhibit);

고차함수를 이용하여 단일 책임 매개변수를 만들면 앞에 위치한 두 개의 인수를 재사용할 수 있다. 첫 번째 매개변수 집합 (건물, 담당자)은 기초 데이터를 수집하고, 두 번째 매개변수 집합은 기초 데이터를 덮어 쓰는 사용자 지정 정보다.

그러므로 외부함수의 매개변수는 building과 manager만 갖고, 이 함수를 실행하면 매개변수로 program(또는 exhibit, event) 하나만 사용하는 함수를 반환하도록 만든다.

// 부분 적용
function mergeProgramInformation(building, manager){
  const { hours, address } = building;
  const { name, phone } = manager;
  const defaults = {
    hours,
    address,
    contact: name,
    phone,
  }
  return program => {
    return {...defaults, ...program};
  }
}
const programInfo = mergeProgramInformation(building, manager)(program);

단일 책임을 부여하는 또 하나의 이유는 나머지 매개변수를 재사용할 수 있다는 것이다.

배열 데이터가 있거나 원본 데이터에 1:1로 대응되는 추가 데이터가 있는 경우. (예제에서는 지역 이름이 담긴 배열을 받아서, 지역을 상징하는 새 이름을 반환하는 함수) 원본 - 결과값을 배열 쌍으로 연결해야함. 이 작업을 하는 함수를 zip함수라고 부른다.

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']
// ]

자주쓰이지는 않으나 인터페이스를 간결하게 유지할 때 유용하다.

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

이전 팁 예제에서 building과 manager를 재사용하려면, 첫 번째 호출의 반환 값을 변수에 할당하면 된다.

const setStrongHallProgram = mergeProgramInformation(building, manager);
const programInfo = setStrongHallProgram(promgram);
const exhibitInfo = setStrongHallProgram(exhibit);

함수를 한 번 호출해 저장해둔 매개변수를 다시 사용하는 것은 다음 예제 처럼 내부 변수를 미리 알고 있는 함수를 선언하는 것과 같다.

//커링 적용
const setStrongHallProgram = program => {
  const defaults = {
    hours: '8 a.m. - 8 p.m.',
    address: 'Jayhawk Blvd',
    name: 'Augusto',
    phone: '555-555-5555',
  }
  return { ...defaults, ...program }
}
const programs = setStrongHallProgram(program);
const exhibit = setStrongHallProgram(exhibit);

첫 번째 함수는 고차 함수의 부분 적용을 활용하여 작성했고, 두 번째는 정보를 하드 코딩했다.

첫 번째 같은 경우 유연하기는 하지만, 두 번째 방법을 쓰면 함수에 필요한 인수의 수를 줄인다. 이렇게 한 번에 인수를 하나만 받는 함수를 커링(currying)이라 한다.

부분 적용 함수와 커링

[공통점]

  • 원래보다 필요한 인수의 수가 적은 함수를 반환하여 인수의 수를 줄인다.

[차이점]

  • 부분 적용 함수는 원래의 함수보다 항수(인수의 수)가 적은 함수를 반환한다.
  • 커링 함수는 정확히 인수 하나만 받는 일련의 함수를 반환할 때 사용한다.

예제 https://github.com/gilbutITbook/007030/blob/master/functions/curry/curry.js

7.5. 화살표 함수로 문맥 혼동을 피하라

저자는 자바스크립트에서 가장 어렵게 느껴지는 개념 두 가지를 유효범위와 문맥으로 꼽고있다. 유효범위는 let과 const 키워드를 배우면서 살펴보았다. 유효범위는 함수와 연관되어있고, 문맥은 객체와 연관되어있다고 보면 된단다.

문맥은 함수 또는 클래스에서 this 키워드가 참조하는 것이라고 생각하면 된다.

다음 코드를 보자.

const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessage(field){
    return `${field}${this.message}`;
  },
}
validator.setInvalidMessage('도시');
// 도시는 유효하지 않습니다.

this.message는 해당 객체의 속성을 참조한다. 객체에서 setInvalidMessage()가 호출될 때 함수에서 this 바인딩을 생성하면서 해당 함수가 담긴 객체도 문맥에 포함시키기 떄문이다.

this는 일반적인 객체에서 다룰 때는 문제가 없지만, 객체에 담긴 함수를 다른 함수의 콜백으로 사용하는 경우 주의해야 한다. setTimeout(), setInterval()이나 배열 메서드 등을 사용할 때 문제가 발생할 가능성이 있다. 이 함수들은 콜백 함수를 받으면서 콜백 함수의 문맥도 변경하기 때문이다.

다음은 위 객체의 메서드를 여러 개의 입력폼에 대한 메시지를 처리하도록 한다.

const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessages(...fields){
    return fields.map(function(field){
        return `${field}${this.message}`;
    });
  },
}

validator.setInvalidMessages('도시');
// undefined

setInvalidMessages()가 호출될 때 this의 문맥은 해당 객체. map() 메서드에 콜백으로 전달할 때는 map()메서드의 문맥에서 호출되므로 문맥은 전역객체가 된다. (브라우저의 경우 window, Node.js REPL환경은 global) 따라서 콜백 함수로 전달되면 message속성에 접근할 수 없게 된다.

이 때 화살표 함수를 사용하면, this 바인딩을 새로 만들지 않아서 의도 대로 작동된다.

화살표 함수를 사용

const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessages(...fields){
    return fields.map(field => {  // 화살표로 변경
        return `${field}${this.message}`;
    });
  }
}
validator.setInvalidMessages('도시');
// 도시는 유효하지 않습니다.

this 문맥을 직접 설정해야할 경우도 있다. 다음 코드는 속성에 할당한 화살표 함수로 작성되어 있다.

const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessages: field => `${field}${this.message}`,
}
validator.setInvalidMessages('도시');
// undefined

호출하면 undefined가 나오는데 화살표 함수는 이미 문맥이 존재할 경우에 유용하며, 위 코드 처럼 새로운 this 바인딩을 설정할 필요가 있을 때는 문제가 있다.

이 바인딩에 관련해서는 bind()로 문맥 문제를 해결하라. 에서 다루기로 한다.

요약

  • 테스팅 (추후 정리)
  • 화살표 함수를 이용해 인수를 해체 할당 하거나, 객체를 반환하거나, 고차함수를 만들면 복잡도를 줄일 수 있다.
  • 부분 적용 함수를 만들면 독립적인 매개 변수의 집합을 사용할 수 있는데, 매개변수의 재사용을 할 수 있기 때문이다?
  • 커링도 부분 적용 함수처럼 인수의 수를 줄이되 단 하나의 인수만을 받는다.
  • 화살표 함수를 사용하면 이미 존재하는 this의 문맥을 그대로 사용할 수 있다. (단, 새로 설정해야할 때는 문제가 있음.)

Categories:

Updated:

Comments