ES6 +でのJavaScriptファクトリー機能

スモークアートキューブからスモーク— MattysFlicks —(CC BY 2.0)
注:これは、JavaScript ES6 +で関数型プログラミングと合成ソフトウェア技術を一から学ぶ「ソフトウェアの作成」シリーズ(現在は本です!)の一部です。乞うご期待。まだまだたくさんあります!
本を購入する|インデックス| <前へ|次へ>

ファクトリー関数は、(おそらく新しい)オブジェクトを返すクラスまたはコンストラクターではない関数です。 JavaScriptでは、どの関数もオブジェクトを返すことができます。新しいキーワードなしでこれを行うと、ファクトリー機能になります。

ファクトリ関数は、クラスの複雑さと新しいキーワードに飛び込むことなく、オブジェクトインスタンスを簡単に生成できるため、JavaScriptでは常に魅力的です。

JavaScriptは非常に便利なオブジェクトリテラル構文を提供します。次のようになります。

const user = {
  userName: 'echo'、
  アバター: 'echo.png'
};

JSON(JavaScriptのオブジェクトリテラル表記に基づいています)と同様に、:の左側はプロパティ名で、右側は値です。ドット表記で小道具にアクセスできます:

console.log(user.userName); // "エコー"

角括弧表記を使用して、計算されたプロパティ名にアクセスできます。

const key = 'avatar';
console.log(user [key]); // "echo.png"

目的のプロパティ名と同じ名前のスコープ内の変数がある場合は、オブジェクトリテラルの作成でコロンと値を省略できます。

const userName = 'echo';
const avatar = 'echo.png';
const user = {
  userName、
  アバター
};
console.log(user);
// {"アバター": "echo.png"、 "userName": "echo"}

オブジェクトリテラルは、簡潔なメソッド構文をサポートしています。 .setUserName()メソッドを追加できます。

const userName = 'echo';
const avatar = 'echo.png';
const user = {
  userName、
  アバター、
  setUserName(userName){
    this.userName = userName;
    これを返す;
  }
};
console.log(user.setUserName( 'Foo')。userName); //「フー」

簡潔なメソッドでは、これはメソッドが呼び出されるオブジェクトを指します。オブジェクトのメソッドを呼び出すには、オブジェクトのドット表記を使用してメソッドにアクセスし、括弧を使用してメソッドを呼び出します。たとえば、game.play()は.play()をゲームオブジェクトに適用します。ドット表記を使用してメソッドを適用するには、そのメソッドが問題のオブジェクトのプロパティである必要があります。関数プロトタイプメソッド、.call()、. apply()、または.bind()を使用して、他の任意のオブジェクトにメソッドを適用することもできます。

この場合、user.setUserName( 'Foo')は.setUserName()をユーザーに適用するため、これは=== userです。 .setUserName()メソッドでは、thisバインディングを介してユーザーオブジェクトの.userNameプロパティを変更し、メソッドチェーンに同じオブジェクトインスタンスを返します。

1つのリテラル、多くのファクトリー

多くのオブジェクトを作成する必要がある場合は、オブジェクトリテラルとファクトリー関数のパワーを組み合わせたいと思うでしょう。

ファクトリ関数を使用すると、必要な数のユーザーオブジェクトを作成できます。たとえば、チャットアプリを構築している場合、現在のユーザーを表すユーザーオブジェクトと、現在サインインしてチャットしている他のすべてのユーザーを表す他の多くのユーザーオブジェクトを使用して、名前を表示できます。アバターも。

ユーザーオブジェクトをcreateUser()ファクトリに変えましょう。

const createUser =({userName、avatar})=>({
  userName、
  アバター、
  setUserName(userName){
    this.userName = userName;
    これを返す;
  }
});
console.log(createUser({userName: 'echo'、avatar: 'echo.png'}));
/ *
{
  「アバター」:「echo.png」、
  「userName」:「echo」、
  「setUserName」:[関数setUserName]
}
* /

オブジェクトを返す

矢印関数(=>)には暗黙的な戻り機能があります。関数の本体が単一の式で構成されている場合、returnキーワードを省略できます。()=> 'foo'はパラメーターを取らず、文字列 " foo」。

オブジェクトリテラルを返すときは注意してください。デフォルトでは、JavaScriptは、中括弧を使用するときに関数本体を作成することを想定しています(例:{broken:true})。オブジェクトリテラルに暗黙的な戻り値を使用する場合は、オブジェクトリテラルをかっこで囲むことで明確にする必要があります。

const noop =()=> {foo: 'bar'};
console.log(noop()); //未定義
const createFoo =()=>({foo: 'bar'});
console.log(createFoo()); // {foo: "bar"}

最初の例では、foo:はラベルとして解釈され、barは割り当てまたは返されない式として解釈されます。関数は未定義を返します。

createFoo()の例では、括弧は関数の本体ブロックではなく、評価される式として解釈されるように括弧を強制します。

破壊

関数のシグネチャに特に注意してください:

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

この行では、中括弧({、})はオブジェクトの破壊を表します。この関数は1つの引数(オブジェクト)を受け取りますが、その単一の引数userNameおよびavatarから2つの仮パラメーターを非構造化します。これらのパラメーターは、関数本体のスコープで変数として使用できます。配列を分解することもできます:

const swap =([first、second])=> [second、first];
console.log(swap([1、2])); // [2、1]

また、rest and spread構文(... varName)を使用して、配列(または引数のリスト)から残りの値を収集し、それらの配列要素を個々の要素に戻すことができます。

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

計算されたプロパティキー

前に、角かっこで計算されたプロパティアクセス表記を使用して、アクセスするオブジェクトプロパティを動的に決定しました。

const key = 'avatar';
console.log(user [key]); // "echo.png"

また、割り当てるキーの値を計算することもできます。

const arrToObj =([key、value])=>({[key]:value});
console.log(arrToObj(['foo'、 'bar'])); // {"foo": "bar"}

この例では、arrToObjはキー/値のペア(別名タプル)で構成される配列を取り、それをオブジェクトに変換します。キーの名前がわからないため、オブジェクトにキー/値のペアを設定するためにプロパティ名を計算する必要があります。そのために、計算されたプロパティアクセサから角括弧表記の概念を借用し、オブジェクトリテラルを構築するコンテキストで再利用します。

{[キー]:値}

補間が完了したら、最終的なオブジェクトになります。

{"foo": "bar"}

デフォルトパラメータ

JavaScriptの関数はデフォルトのパラメーター値をサポートしますが、これにはいくつかの利点があります。

  1. ユーザーは、適切なデフォルトのパラメーターを省略することができます。
  2. デフォルト値は予想される入力の例を提供するため、関数はより自己文書化されます。
  3. IDEおよび静的分析ツールは、デフォルト値を使用して、パラメーターに必要なタイプを推測できます。たとえば、デフォルト値の1は、パラメーターがNumber型のメンバーを取ることができることを意味します。

デフォルトのパラメーターを使用して、createUserファクトリの予想されるインターフェースを文書化し、ユーザーの情報が提供されない場合は「匿名」の詳細を自動的に入力できます。

const createUser =({
  userName = '匿名'、
  avatar = 'anon.png'
} = {})=>({
  userName、
  アバター
});
console.log(
  // {userName: "echo"、avatar: 'anon.png'}
  createUser({userName: 'echo'})、
  // {userName: "匿名"、アバター: 'anon.png'}
  ユーザーを作成()
);

関数シグネチャの最後の部分は、おそらく少し面白そうです。

} = {})=>({

パラメータシグネチャが閉じる直前の最後の= {}ビットは、このパラメータに何も渡されない場合、デフォルトとして空のオブジェクトを使用することを意味します。空のオブジェクトから値を分解しようとすると、プロパティのデフォルト値が自動的に使用されます。これはデフォルト値の役割です。undefinedを定義済みの値に置き換えます。

= {}デフォルト値がないと、未定義のプロパティにアクセスできないため、引数のないcreateUser()はエラーをスローします。

型推論

この記事の執筆時点では、JavaScriptにはネイティブタイプの注釈はありませんが、ギャップを埋めるために、JSDoc(より優れたオプションの出現により減少)、FacebookのFlow、MicrosoftのTypeScriptなど、競合するフォーマットがいくつか出現しました。私はドキュメンテーションにrtypeを使用します—関数型プログラミングではTypeScriptよりもはるかに読みやすい表記法です。

この記事の執筆時点では、型注釈の明確な勝者はいません。 JavaScriptの仕様に恵まれた選択肢はなく、それらすべてに明らかな欠点があるようです。

型推論は、使用されるコンテキストに基づいて型を推論するプロセスです。 JavaScriptでは、注釈を入力するのに非常に優れた代替手段です。

標準のJavaScript関数シグネチャで推論のための十分な手がかりを提供すれば、コストやリスクなしで型注釈の利点を最大限に活用できます。

TypeScriptやFlowなどのツールを使用する場合でも、可能な限り型推論を行い、型推論が不十分な状況では型注釈を保存する必要があります。たとえば、JavaScriptには共有インターフェースを指定するネイティブな方法はありません。これは、TypeScriptまたはrtypeを使用すると簡単で便利です。

Tern.jsは、多くのコードエディターとIDEのプラグインを備えた、JavaScript用の一般的な型推論ツールです。

MicrosoftのVisual Studio Codeは、Ternを必要としません。これは、TypeScriptの型推論機能を通常のJavaScriptコードにもたらすためです。

JavaScriptの関数のデフォルトパラメータを指定すると、Tern.js、TypeScript、Flowなどの型推論が可能なツールは、作業中のAPIを正しく使用するのに役立つIDEヒントを提供できます。

デフォルトがない場合、IDE(および多くの場合、人間)には、予想されるパラメータータイプを把握するための十分なヒントがありません。

デフォルトがない場合、「userName」のタイプは不明です。

デフォルトでは、IDE(および多くの場合、人間)は例からタイプを推測できます。

デフォルトでは、IDEは `userName`が文字列を期待していることを示唆できます。

パラメーターを固定型に制限することは必ずしも意味がありません(汎用関数と高階関数が難しくなります)。 TypeScriptまたはFlowを使用して再。

Mixinコンポジションのファクトリー関数

ファクトリは、優れた呼び出しAPIを使用してオブジェクトをクランキングするのに優れています。通常、必要なのはそれだけですが、たまに似たような機能をさまざまな種類のオブジェクトに組み込むことができます。それらの機能を機能的なミックスインに抽象化し、再利用しやすくすることができます。

それが機能的なミックスインの輝きです。 withConstructorミックスインを作成して、すべてのオブジェクトインスタンスに.constructorプロパティを追加しましょう。

with-constructor.js:

const withConstructor = constructor => o =>({
  //デリゲート[[Prototype]]を作成します
  __proto__:{
    //コンストラクターpropを新しい[[Prototype]]に追加します
    コンストラクタ
  }、
  //すべてのoの小道具を新しいオブジェクトに混ぜます
  ... o
});

これでインポートして、他のミックスインで使用できます:

`./with-constructor 'からwithConstructorをインポートします;
const pipe =(... fns)=> x => fns.reduce((y、f)=> f(y)、x);
//または `'lodash / fp / flow';からパイプをインポート;`
//いくつかの機能的なミックスインを設定します
const withFlying = o => {
  let isFlying = false;
  return {
    ... o、
    飛ぶ () {
      isFlying = true;
      これを返す;
    }、
    土地 () {
      isFlying = false;
      これを返す;
    }、
    isFlying:()=> isFlying
  }
};
const withBattery =({容量})=> o => {
  letletCharged = 100;
  return {
    ... o、
    draw(percent){
      const remaining = percentCharged-パーセント;
      percentCharged = remaining> 0?残り:0;
      これを返す;
    }、
    getCharge:()=> percentCharged、
    getCapacity:()=>容量
  };
};
const createDrone =({capacity = '3000mAh'})=> pipe(
  飛んで、
  withBattery({capacity})、
  withConstructor(createDrone)
)({});
const myDrone = createDrone({capacity: '5500mAh'});
console.log( `
  飛ぶことができる:$ {myDrone.fly()。isFlying()=== true}
  着陸可能:$ {myDrone.land()。isFlying()=== false}
  バッテリー容量:$ {myDrone.getCapacity()}
  バッテリーの状態:$ {myDrone.draw(50).getCharge()}%
  バッテリーの消耗:$ {myDrone.draw(75).getCharge()}%remaining
`);
console.log( `
  リンクされたコンストラクター:$ {myDrone.constructor === createDrone}
`);

ご覧のとおり、再利用可能なwithConstructor()ミックスインは、他のミックスインと一緒にパイプラインにドロップされるだけです。 withBattery()は、ロボット、電動スケートボード、ポータブルデバイス充電器など、他の種類のオブジェクトで使用できます。 withFlying()は、空飛ぶ車、ロケット、または気球のモデル化に使用できます。

合成は、コードの特定の手法よりも考え方です。多くの方法でそれを達成できます。関数の構成は、ゼロから構築する最も簡単な方法であり、ファクトリ関数は、実装の詳細をわかりやすいAPIでラップする簡単な方法です。

結論

ES6は、オブジェクトの作成とファクトリー関数を処理するための便利な構文を提供します。ほとんどの場合、必要なのはそれだけですが、これはJavaScriptであるため、Javaのように感じる別のアプローチがあります。それはクラスキーワードです。

JavaScriptでは、クラスはファクトリよりも冗長で制限が多く、リファクタリングに関しては少し地雷原ですが、ReactやAngularなどの主要なフロントエンドフレームワークにも採用されており、いくつかのまれな用途があります-複雑さを価値のあるものにするケース。

「時々、エレガントな実装は単なる機能です。メソッドではありません。クラスではありません。フレームワークではありません。ただの機能です。」〜ジョン・カーマック

最も単純な実装から始め、必要に応じてより複雑な実装に移行します。

次:クラスで構成が難しい理由>

次のステップ

JavaScriptを使用したオブジェクトの構成について詳しく知りたいですか?

Eric ElliottでJavaScriptを学んでください。あなたがメンバーでない場合、あなたは逃しています!

Eric Elliottは、分散システムの専門家であり、「Composing Software」および「Programming JavaScript Applications」という本の著者です。 DevAnywhere.ioの共同創設者として、彼は開発者にリモートで作業し、ワーク/ライフバランスを受け入れるために必要なスキルを教えています。彼は暗号化プロジェクトの開発チームを構築して助言し、Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN、BBC、およびUsher、Frank Ocean、Metallicaなどのトップレコーディングアーティストのソフトウェアエクスペリエンスに貢献しています。

彼は世界で最も美しい女性との遠隔生活を楽しんでいます。