465

とES6() => {}function () {}関数を記述する 2 つの非常によく似た方法が得られます。他の言語では、ラムダ関数は匿名であることで区別されることがよくありますが、ECMAScript では、どの関数も匿名にすることができます。2 つのタイプのそれぞれに固有の使用ドメインがあります (つまり、this明示的にバインドする必要があるか、明示的にバインドしない必要がある場合)。これらのドメインの間には、どちらの表記法でも対応できる膨大な数のケースがあります。

ES6 のアロー関数には、少なくとも 2 つの制限があります。

  • を使用しないでくださいnew。作成時には使用できませんprototype
  • this初期化時のスコープへのバインドを修正

これらの 2 つの制限は別として、アロー関数は理論的にはほとんどどこでも通常の関数を置き換えることができます。実際にそれらを使用する正しいアプローチは何ですか? アロー関数を使用する必要があります。例:

  • 「どこでも機能する」、つまり、関数がthis変数にとらわれない必要はなく、オブジェクトを作成していません。
  • 特定のスコープにバインドする必要がある「必要な場所」、つまりイベントリスナー、タイムアウトのみ
  • 「短い」関数を使用するが、「長い」関数を使用しない
  • 別のアロー関数を含まない関数のみ

ECMAScript の将来のバージョンで適切な関数表記を選択するためのガイドラインを探しています。ガイドラインは、チーム内の開発者に教えられるように明確にする必要があります。また、ある関数表記から別の関数表記へと頻繁にリファクタリングする必要がないように、一貫性がある必要があります。

この質問は、今後の ECMAScript 6 (Harmony) のコンテキストでコード スタイルについて考えたことがある人、およびこの言語を既に使用したことがある人に向けられています。

4

9 に答える 9

3

アロー関数またはラムダは、ES 6 で導入されました。最小限の構文の優雅さは別として、最も顕著な機能上の違いは、アロー関数内のスコープです。 this

正規の関数式では、キーワードは呼び出されたコンテキストthisに基づいて異なる値にバインドされます。

これは、アローthis関数が定義されたスコープ (親スコープ) から閉じ、thisどこでどのように呼び出されても変更されないことを意味します。

オブジェクトのメソッドとしてのアロー関数の制限

// this = global Window
let objA = {
  id: 10,
  name: "Simar",
  print () { // same as print: function()
    console.log(`[${this.id} -> ${this.name}]`);
  }
}

objA.print(); // logs: [10 -> Simar]

objA = {
  id: 10,
  name: "Simar",
  print: () => {
    // Closes over this lexically (global Window)
    console.log(`[${this.id} -> ${this.name}]`);
  }
};

objA.print(); // logs: [undefined -> undefined]

レギュラーでメソッド定義した場合、objA.print()メソッド呼び出しに対してはちゃんと解決して動作しましたが、アロー関数として定義すると失敗しました。これは、オブジェクト ( ) のメソッドとして呼び出される通常の関数では、オブジェクト自体であるためです。print()functionthisobjA=>thisobjA

ただし、アロー関数の場合、それが定義された外側のスコープ (この場合はグローバル/ウィンドウ)thisの the にレキシカルにバインドさthisれ、メソッド on としての呼び出し中も同じままobjAです。

オブジェクトのメソッド内の通常の関数よりもアロー関数の利点がありますが、それthisは定義時に固定およびバインドされることが予想される場合のみです。

/* this = global | Window (enclosing scope) */

let objB = {
  id: 20,
  name: "Paul",
  print () { // Same as print: function()
    setTimeout( function() {
      // Invoked async, not bound to objB
      console.log(`[${this.id} -> ${this.name}]`);
    }, 1)
  }
};

objB.print(); // Logs: [undefined -> undefined]'

objB = {
  id: 20,
  name: "Paul",
  print () { // Same as print: function()
    setTimeout( () => {
      // Closes over bind to this from objB.print()
      console.log(`[${this.id} -> ${this.name}]`);
    }, 1)
  }
};

objB.print(); // Logs: [20 -> Paul]

objB.print()メソッドが[${this.id} -> {this.name}]をコールバックとして非同期にprint()呼び出す関数として定義されている場合、アロー関数がコールバックとして使用された場合に正しく解決されます。ただし、コールバックが通常の関数として定義されている場合は失敗しました。console.log()setTimeoutthisobjB

これは=>、 に渡されたアロー関数が、その親からレキシカルsetTimeout(()=>..)に閉じているためです。つまり、それを定義した の呼び出しです。つまり、 に渡されたアロー関数は、 の呼び出しがそれ自体だったため、 にバインドされています。thisobjB.print()=>setTimeout(()==>...objBthisobjB.print() thisobjB

Function.prototype.bind()正しい にバインドすることで、通常の関数として定義されたコールバックを簡単に機能させることができますthis

const objB = {
  id: 20,
  name: "Singh",
  print () { // The same as print: function()
    setTimeout( (function() {
      console.log(`[${this.id} -> ${this.name}]`);
    }).bind(this), 1)
  }
}

objB.print() // logs: [20 -> Singh]

ただし、アロー関数は便利であり、非同期コールバックの場合はエラーが発生しにくくなりますthis。関数の定義時に、アロー関数を取得してバインドする必要があることがわかっている場合です。

this呼び出し間で変更する必要があるアロー関数の制限

いつでも、this呼び出し時に変更できる関数が必要ですが、アロー関数は使用できません。

/* this = global | Window (enclosing scope) */

function print() {
  console.log(`[${this.id} -> {this.name}]`);
}

const obj1 = {
  id: 10,
  name: "Simar",
  print // The same as print: print
};

obj.print(); // Logs: [10 -> Simar]

const obj2 = {
  id: 20,
  name: "Paul",
};

printObj2 = obj2.bind(obj2);
printObj2(); // Logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]

上記のいずれもアロー関数const print = () => { console.log([${this.id} -> {this.name}]);}では機能しthisません。変更できないためthis、それが定義された外側のスコープ (グローバル / ウィンドウ) にバインドされたままになります。

これらのすべての例では、関数が宣言された後に作成された異なるオブジェクト (obj1および) を使用して、同じ関数を次々に呼び出しました。obj2print()

これらは不自然な例ですが、もう少し実際の例について考えてみましょう。reduce()で動作するメソッドと同様のメソッドを作成する必要がある場合、呼び出しコンテキスト、つまり呼び出された配列arraysから推論する必要があるため、ラムダとして定義することはできません。this

このため、コンストラクターthis関数は宣言時に設定できないため、コンストラクター関数をアロー関数として定義することはできません。コンストラクター関数がキーワードで呼び出されるたびnewに、新しいオブジェクトが作成され、その特定の呼び出しにバインドされます。

また、フレームワークまたはシステムが動的コンテキストで後で呼び出されるコールバック関数を受け入れる場合、呼び出しごとに変更する必要がある可能性があるため、thisアロー関数を使用できません。thisこの状況は、DOM イベント ハンドラーでよく発生します。

'use strict'
var button = document.getElementById('button');

button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});

var button = document.getElementById('button');

button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

これは、 Angular 2+Vue.jsthisなどのフレームワークで、呼び出しがバインディング関数のフレームワークによって管理されるため、テンプレート コンポーネント バインディング メソッドが通常の関数/メソッドであることを期待する理由でもあります。(Angular は Zone.js を使用して、ビュー テンプレート バインディング関数の呼び出しの非同期コンテキストを管理します。)

一方、Reactでは、コンポーネントのメソッドをイベント ハンドラーとして渡したい場合、たとえば のように、呼び出しごとにアロー関数として<input onChange={this.handleOnchange} />定義する必要があります。handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}これは、レンダリングされた DOM 要素の JSX を生成したコンポーネントと同じインスタンスにする必要があります。


この記事は、Medium の出版物にも掲載されています。この記事が気に入った場合、またはコメントや提案がある場合は、拍手するか、 Mediumにコメントを残してください。

于 2019-03-24T00:06:42.520 に答える