ES6 +를 사용하는 JavaScript 팩토리 함수

스모크 아트 큐브 스모크 — MattysFlicks — (CC BY 2.0)
참고 :이 글은 JavaScript ES6 +의 기능 프로그래밍 및 컴포지션 소프트웨어 기술을 처음부터 배우는 "소프트웨어 구성"시리즈 (현재 책!)의 일부입니다. 계속 지켜봐 주시기 바랍니다. 앞으로 더 많은 것들이 있습니다!
도서 구매 | 색인 | <이전 | 다음>

팩토리 함수는 (아마도 새로운) 객체를 반환하는 클래스 또는 생성자가 아닌 함수입니다. JavaScript에서 모든 함수는 객체를 반환 할 수 있습니다. 새 키워드가 없으면 공장 기능입니다.

팩토리 함수는 클래스와 새 키워드의 복잡성으로 뛰어 들지 않고 객체 인스턴스를 쉽게 생성 할 수있는 기능을 제공하므로 JavaScript에서 항상 매력적이었습니다.

JavaScript는 매우 편리한 객체 리터럴 구문을 제공합니다. 다음과 같이 보입니다 :

const 사용자 = {
  사용자 이름 : 'echo',
  아바타 : 'echo.png'
};

JavaScript (JavaScript의 객체 리터럴 표기법을 기반으로 함)와 마찬가지로 JSON의 왼쪽은 속성 이름이고 오른쪽은 값입니다. 점 표기법으로 소품에 액세스 할 수 있습니다.

console.log (user.userName); // "에코"

대괄호 표기법을 사용하여 계산 된 속성 이름에 액세스 할 수 있습니다.

const 키 = '아바타';
console.log (user [key]); // "echo.png"

의도 한 속성 이름과 이름이 같은 변수가 범위 내에 있으면 개체 리터럴 생성에서 콜론과 값을 생략 할 수 있습니다.

const userName = 'echo';
const avatar = 'echo.png';
const 사용자 = {
  사용자 이름,
  화신
};
console.log (사용자);
// { "avatar": "echo.png", "userName": "echo"}

객체 리터럴은 간결한 메소드 구문을 지원합니다. .setUserName () 메소드를 추가 할 수 있습니다 :

const userName = 'echo';
const avatar = 'echo.png';
const 사용자 = {
  사용자 이름,
  화신,
  setUserName (사용자 이름) {
    this.userName = userName;
    이것을 돌려줍니다;
  }
};
console.log (user.setUserName ( 'Foo'). userName); // "푸"

간결한 메소드에서 이는 메소드가 호출되는 오브젝트를 나타냅니다. 오브젝트에서 메소드를 호출하려면 오브젝트 점 표기법을 사용하여 메소드에 액세스하고 괄호를 사용하여 메소드를 호출하십시오 (예 : game.play ()는 .play ()를 게임 오브젝트에 적용 함). 점 표기법을 사용하여 메소드를 적용하려면 해당 메소드가 해당 오브젝트의 특성이어야합니다. 함수 프로토 타입 메소드 인 .call (), .apply () 또는 .bind ()를 사용하여 다른 임의의 객체에 메소드를 적용 할 수도 있습니다.

이 경우 user.setUserName ( 'Foo')은 .setUserName ()을 사용자에게 적용하므로 === user입니다. .setUserName () 메소드에서이 바인딩을 통해 사용자 오브젝트의 .userName 특성을 변경하고 메소드 체인에 대해 동일한 오브젝트 인스턴스를 리턴합니다.

하나에 대한 리터럴, 다수에 대한 팩토리

많은 객체를 생성해야하는 경우 객체 리터럴과 팩토리 기능의 강력한 기능을 결합해야합니다.

팩토리 기능을 사용하면 원하는 수의 사용자 개체를 만들 수 있습니다. 예를 들어 채팅 앱을 구축하는 경우 현재 사용자를 나타내는 사용자 개체와 현재 로그인 및 채팅중인 다른 모든 사용자를 나타내는 다른 많은 사용자 개체를 가질 수 있으므로 이름을 표시 할 수 있습니다 아바타도.

사용자 객체를 createUser () 팩토리로 바꾸겠습니다.

const createUser = ({userName, avatar}) => ({
  사용자 이름,
  화신,
  setUserName (사용자 이름) {
    this.userName = userName;
    이것을 돌려줍니다;
  }
});
console.log (createUser ({userName : 'echo', 아바타 : 'echo.png'}));
/ *
{
  "avatar": "echo.png",
  "userName": "echo",
  "setUserName": [함수 setUserName]
}
* /

객체 반환

화살표 함수 (=>)에는 암시 적 반환 기능이 있습니다. 함수 본문이 단일 표현식으로 구성된 경우 반환 키워드를 생략 할 수 있습니다. () => 'foo'는 매개 변수를 사용하지 않고 문자열 " foo ".

객체 리터럴을 반환 할 때는주의하십시오. 기본적으로 JavaScript는 중괄호 (예 : {broken : true})를 사용할 때 함수 본문을 작성한다고 가정합니다. 객체 리터럴에 암시 적 반환을 사용하려면 객체 리터럴을 괄호로 묶어 명확하게해야합니다.

const noop = () => {foo : 'bar'};
console.log (noop ()); // 정의되지 않음
const createFoo = () => ({foo : 'bar'});
console.log (createFoo ()); // {foo : "bar"}

첫 번째 예에서 foo :는 레이블로 해석되고 bar는 지정되거나 리턴되지 않는 표현식으로 해석됩니다. 이 함수는 undefined를 반환합니다.

createFoo () 예제에서 괄호는 중괄호를 함수 본문 블록이 아니라 평가할 표현식으로 해석하도록합니다.

파괴

함수 서명에 특별한주의를 기울이십시오.

const createUser = ({userName, avatar}) => ({

이 줄에서 중괄호 ({,})는 객체 파괴를 나타냅니다. 이 함수는 하나의 인수 (객체)를 취하지 만, 단일 인수 인 userName 및 avatar에서 두 개의 형식 매개 변수를 구성 해제합니다. 그런 다음 해당 매개 변수를 함수 본문 범위에서 변수로 사용할 수 있습니다. 배열을 재구성 할 수도 있습니다.

const swap = ([첫번째, 두 번째]) => [두번째, 첫 번째];
console.log (스왑 ([1, 2])); // [2, 1]

rest 및 spread 구문 (... varName)을 사용하여 배열 (또는 인수 목록)에서 나머지 값을 수집 한 다음 해당 배열 요소를 개별 요소로 다시 펼칠 수 있습니다.

const rotate = ([first, ... rest]) => [... rest, first];
console.log (회전 ([1, 2, 3])); // [2, 3, 1]

계산 된 속성 키

앞에서 대괄호 계산 속성 액세스 표기법을 사용하여 액세스 할 개체 속성을 동적으로 결정했습니다.

const 키 = '아바타';
console.log (user [key]); // "echo.png"

할당 할 키 값을 계산할 수도 있습니다.

const arrToObj = ([키, 값]) => ({[키] : 값});
console.log (arrToObj ([ 'foo', 'bar'])); // { "foo": "bar"}

이 예에서 arrToObj는 키 / 값 쌍 (일명 튜플)으로 구성된 배열을 가져 와서 객체로 변환합니다. 키 이름을 모르기 때문에 객체에 키 / 값 쌍을 설정하려면 속성 이름을 계산해야합니다. 이를 위해 계산 된 속성 접근 자에서 대괄호 표기법을 빌려 객체 리터럴을 빌드 할 때 재사용합니다.

{ [핵심 가치 }

보간이 끝나면 최종 객체로 끝납니다.

{ "foo": "bar"}

기본 매개 변수

JavaScript의 함수는 기본 매개 변수 값을 지원하며 다음과 같은 이점이 있습니다.

  1. 사용자는 적절한 기본값으로 매개 변수를 생략 할 수 있습니다.
  2. 기본값은 예상 입력의 예를 제공하기 때문에이 기능은보다 자체 문서화됩니다.
  3. IDE 및 정적 분석 도구는 기본값을 사용하여 매개 변수에 예상되는 유형을 유추 할 수 있습니다. 예를 들어, 기본값 1은 매개 변수가 숫자 유형의 멤버를 취할 수 있음을 의미합니다.

기본 매개 변수를 사용하여 createUser 팩토리의 예상 인터페이스를 문서화하고 사용자 정보가 제공되지 않은 경우 '익명'세부 사항을 자동으로 채울 수 있습니다.

const createUser = ({
  userName = '익명',
  avatar = 'anon.png'
} = {}) => ({
  사용자 이름,
  화신
});
console.log (
  // {userName : "echo", 아바타 : 'anon.png'}
  createUser ({userName : 'echo'}),
  // {userName : "익명", 아바타 : 'anon.png'}
  createUser ()
);

함수 시그니처의 마지막 부분은 아마도 약간 재미있을 것입니다.

} = {}) => ({

매개 변수 시그니처가 닫히기 직전의 마지막 = {} 비트는이 매개 변수에 아무것도 전달되지 않으면 빈 객체를 기본값으로 사용한다는 것을 의미합니다. 빈 객체에서 값을 구조화하려고하면 속성의 기본값이 자동으로 사용됩니다. 기본값은 미리 정의 된 값으로 대체되지 않기 때문입니다.

= {} 기본값이 없으면 인수가없는 createUser ()는 정의되지 않은 속성에 액세스하려고 시도 할 수 없으므로 오류가 발생합니다.

타입 추론

JavaScript에는이 글을 쓰는 시점에 기본 유형 주석이 없지만 JSDoc (더 나은 옵션의 등장으로 인한 감소), Facebook의 흐름 및 Microsoft의 TypeScript를 포함하여 몇 가지 경쟁 형식이 격차를 메우기 위해 수년에 걸쳐 발생했습니다. 설명서에는 rtype을 사용합니다. 함수형 프로그래밍에서는 TypeScript보다 훨씬 읽기 쉽습니다.

이 글을 쓰는 시점에는 형식 주석에 대한 확실한 승자가 없습니다. 대안 중 어느 것도 JavaScript 사양에 의해 축복받지 못했으며, 모든 대안에 명확한 단점이있는 것 같습니다.

형식 유추는 사용되는 컨텍스트에 따라 형식을 유추하는 프로세스입니다. JavaScript에서는 주석을 입력하는 것이 매우 좋습니다.

표준 JavaScript 함수 시그니처에서 유추 할 수있는 충분한 힌트를 제공하면 비용이나 위험 없이도 유형 주석의 이점을 최대한 활용할 수 있습니다.

TypeScript 또는 Flow와 같은 도구를 사용하기로 결정하더라도 유형 유추로 최대한 많은 작업을 수행하고 유형 유추가 부족한 상황에 대해 유형 주석을 저장해야합니다. 예를 들어, JavaScript에는 공유 인터페이스를 지정하는 기본 방법이 없습니다. TypeScript 또는 rtype을 사용하면 쉽고 유용합니다.

Tern.js는 많은 코드 편집기 및 IDE 용 플러그인이있는 JavaScript 용으로 널리 사용되는 형식 유추 도구입니다.

Microsoft의 Visual Studio Code는 TypeScript의 형식 유추 기능을 일반 JavaScript 코드에 제공하므로 Tern이 필요하지 않습니다.

JavaScript에서 함수의 기본 매개 변수를 지정하면 Tern.js, TypeScript 및 Flow와 같은 유형 유추 가능한 도구가 올바르게 작업하는 API를 사용하는 데 도움이되는 IDE 힌트를 제공 할 수 있습니다.

기본값이 없으면 IDE (및 종종 사람)는 예상되는 매개 변수 유형을 파악하기에 충분한 힌트가 없습니다.

기본값이 없으면`userName`에 대한 유형을 알 수 없습니다.

기본값을 사용하면 IDE (및 종종 사람)가 예제에서 유형을 유추 할 수 있습니다.

IDE는 기본값으로`userName`이 문자열을 기대하고 있다고 제안 할 수 있습니다.

매개 변수를 고정 유형으로 제한하는 것이 항상 의미가있는 것은 아니지만 (일반적인 함수와 고차 함수를 어렵게 함), 이해가 되더라도 기본 매개 변수가 가장 좋은 방법입니다. TypeScript 또는 Flow를 다시 사용하십시오.

믹스 인 구성을위한 팩토리 함수

팩토리는 멋진 호출 API를 사용하여 객체를 크랭크하는 데 탁월합니다. 일반적으로 필요한 것만으로도 비슷한 기능을 다른 유형의 객체로 만드는 것을 발견 할 수 있으며 이러한 기능을 기능성 믹스 인으로 추상화하여보다 쉽게 ​​재사용 할 수 있습니다.

기능성 믹스 인이 빛을 발하는 곳입니다. withConstructor mixin을 빌드하여 모든 객체 인스턴스에 .constructor 속성을 추가해 보겠습니다.

with-constructor.js :

const withConstructor = 생성자 => o => ({
  // 델리게이트 생성 [[Prototype]]
  __proto__ : {
    // 생성자 소품을 새로운 [[Prototype]]에 추가
    건설자
  },
  // 모든 o의 소품을 새 객체에 혼합
  ...영형
});

이제 가져 와서 다른 믹스 인과 함께 사용할 수 있습니다.

`./with-constructor '에서 withConstructor로 가져 오기;
const 파이프 = (... fns) => x => fns.reduce ((y, f) => f (y), x);
// 또는 'lodash / fp / flow'에서 파이프 가져 오기;`
// 일부 기능 믹스 인 설정
const withFlying = o => {
  isFlying = 거짓이라고하자;
  {
    ...영형,
    파리 () {
      isFlying = true;
      이것을 돌려줍니다;
    },
    땅 () {
      isFlying = 거짓;
      이것을 돌려줍니다;
    },
    isFlying : () => isFlying
  }
};
const withBattery = ({capacity}) => o => {
  percentCharged = 100;
  {
    ...영형,
    추첨 (백분율) {
      const 남아있는 = percentCharged-퍼센트;
      percentCharged = 남음> 0? 남은 : 0;
      이것을 돌려줍니다;
    },
    getCharge : () => percentCharged,
    getCapacity : () => 용량
  };
};
const createDrone = ({용량 = '3000mAh'}) => 파이프 (
  날기,
  withBattery ({capacity}),
  withConstructor (createDrone)로
) ({});
const myDrone = createDrone ({용량 : '5500mAh'});
console.log (`
  비행 가능 : $ {myDrone.fly (). isFlying () === true}
  착륙 가능 : $ {myDrone.land (). isFlying () === false}
  배터리 용량 : $ {myDrone.getCapacity ()}
  배터리 상태 : $ {myDrone.draw (50) .getCharge ()} %
  배터리 소모 : $ {myDrone.draw (75) .getCharge ()} % 남음
`);
console.log (`
  생성자 연결 : $ {myDrone.constructor === createDrone}
`);

보시다시피 재사용 가능한 withConstructor () 믹스 인은 다른 믹스 인과 함께 파이프 라인에 간단히 드롭됩니다. withBattery ()는 로봇, 전기 스케이트 보드 또는 휴대용 장치 충전기와 같은 다른 종류의 객체와 함께 사용할 수 있습니다. withFlying ()을 사용하여 비행 자동차, 로켓 또는 공기 풍선을 모델링 할 수 있습니다.

구성은 코드의 특정 기술보다 사고 방식에 가깝습니다. 여러 가지 방법으로 달성 할 수 있습니다. 함수 구성은 처음부터 새로 작성하는 가장 쉬운 방법이며 팩토리 함수는 구현 세부 사항을 친숙한 API로 감싸는 간단한 방법입니다.

결론

ES6는 객체 생성 및 팩토리 기능을 처리하기위한 편리한 구문을 제공합니다. 대부분의 경우, 이것이 필요한 전부입니다. 그러나 이것이 JavaScript이기 때문에 Java와 같은 느낌을주는 또 다른 접근법 인 class 키워드가 있습니다.

JavaScript에서 클래스는 팩토리보다 더 장황하고 제한적이며 리팩토링과 관련하여 약간의 마인 필드이지만 React 및 Angular와 같은 주요 프론트 엔드 프레임 워크에 수용되어 드물게 사용됩니다. 복잡성을 가치있게 만드는 사례.

“때때로 우아한 구현은 단지 기능 일뿐입니다. 방법이 아닙니다. 수업이 아닙니다. 프레임 워크가 아닙니다. 그냥 기능입니다.”~ John Carmack

가장 간단한 구현부터 시작하여 필요에 따라 더 복잡한 구현으로 이동하십시오.

다음 : 클래스가있는 컴포지션이 어려운 이유>

다음 단계

JavaScript로 객체 구성에 대해 더 알고 싶으십니까?

Eric Elliott와 함께 JavaScript를 배우십시오. 회원이 아닌 경우 누락 된 것입니다.

Eric Elliott는 분산 시스템 전문가이며“Composing Software”및“JavaScript Applications 프로그래밍”책의 저자입니다. DevAnywhere.io의 공동 창립자로서 개발자에게 원격으로 작업하고 업무 / 생활 균형을 수용하는 데 필요한 기술을 가르칩니다. 그는 암호화 프로젝트를위한 개발 팀을 구성하고 조언하며, Adobe Systems, Zumba Fitness, Wall Street Journal, ESPN, BBC 및 Usher, Frank Ocean, Metallica 등 최고의 레코딩 아티스트를위한 소프트웨어 경험에 기여했습니다.

그는 세상에서 가장 아름다운 여성과 함께 외진 생활을 즐깁니다.