JavaScriptの矢印関数を理解する

提供:Dev Guides
移動先:案内検索

著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

ECMAScript仕様(ES6) 2015エディションでは、JavaScript言語に矢印関数式が追加されました。 矢印関数は無名関数式を記述する新しい方法であり、Pythonなどの他のプログラミング言語のラムダ関数に似ています。

矢印関数は、スコープの決定方法や構文の表現方法など、多くの点で従来の関数とは異なります。 このため、矢印関数は、組み込みのイテレータメソッドを使用して配列をループする場合など、関数をパラメータとして高階関数に渡す場合に特に便利です。 。 それらの構文上の省略形により、コードの可読性を向上させることもできます。

この記事では、関数の宣言と式を確認し、従来の関数式と矢印関数式の違いについて学び、矢印関数に関連する字句スコープについて学び、矢印関数で許可されている構文の省略形について説明します。

関数の定義

矢印関数式の詳細を掘り下げる前に、このチュートリアルでは、後で矢印関数の固有の側面をよりよく示すために、従来のJavaScript関数を簡単に確認します。

このシリーズの前半のJavaScriptで関数を定義する方法チュートリアルでは、関数宣言および関数式の概念を紹介しました。 関数宣言は、functionキーワードで記述された名前付き関数です。 関数宣言は、コードが実行される前に実行コンテキストにロードされます。 これはhoistingと呼ばれ、宣言する前に関数を使用できることを意味します。

次に、2つのパラメーターの合計を返すsum関数の例を示します。

function sum(a, b) {
  return a + b
}

巻き上げのために関数を宣言する前に、sum関数を実行できます。

sum(1, 2)

function sum(a, b) {
  return a + b
}

このコードを実行すると、次の出力が得られます。

Output3

関数自体をログに記録することで、関数の名前を見つけることができます。

console.log(sum)

これにより、関数とその名前が返されます。

Outputƒ sum(a, b) {
  return a + b
}

関数式は、実行コンテキストにプリロードされていない関数であり、コードがそれに遭遇したときにのみ実行されます。 関数式は通常、変数に割り当てられ、匿名にすることができます。これは、関数に名前がないことを意味します。

この例では、無名関数式と同じsum関数を記述します。

const sum = function (a, b) {
  return a + b
}

これで、無名関数をsum定数に割り当てました。 宣言される前に関数を実行しようとすると、エラーが発生します。

sum(1, 2)

const sum = function (a, b) {
  return a + b
}

これを実行すると、次のようになります。

OutputUncaught ReferenceError: Cannot access 'sum' before initialization

また、関数には名前付き識別子がないことに注意してください。 これを説明するために、sumに割り当てられた同じ無名関数を記述してから、sumをコンソールに記録します。

const sum = function (a, b) {
  return a + b
}

console.log(sum)

これにより、次のことが表示されます。

Outputƒ (a, b) {
  return a + b
}

sumの値は無名関数であり、名前付き関数ではありません。

functionキーワードで記述された関数式に名前を付けることができますが、これは実際には一般的ではありません。 関数式に名前を付ける理由の1つは、エラースタックトレースのデバッグを容易にするためです。

次の関数について考えてみます。この関数は、 ifステートメントを使用して、関数パラメーターが欠落している場合にエラーをスローします。

const sum = function namedSumFunction(a, b) {
  if (!a || !b) throw new Error('Parameters are required.')

  return a + b
}

sum();

強調表示されたセクションは関数に名前を付け、関数はまたは ||演算子を使用して、いずれかのパラメーターが欠落している場合にエラーobjectをスローします。

このコードを実行すると、次のようになります。

OutputUncaught Error: Parameters are required.
    at namedSumFunction (<anonymous>:3:23)
    at <anonymous>:1:1

この場合、関数に名前を付けると、エラーがどこにあるかがすぐにわかります。

矢印関数式は、「太い矢印」構文(=>)で記述された無名関数式です。

sum関数を矢印関数の構文で書き直します。

const sum = (a, b) => {
  return a + b
}

従来の関数式と同様に、矢印関数は吊り上げられていないため、宣言する前に呼び出すことはできません。 また、これらは常に匿名です。矢印関数に名前を付ける方法はありません。 次のセクションでは、矢印関数と従来の関数の構文上および実際上の違いについて詳しく説明します。

矢印関数の動作と構文

矢印関数には、従来の関数と区別するための動作方法にいくつかの重要な違いがあり、構文上の機能もいくつか強化されています。 最大の機能の違いは、矢印関数には独自のthisバインディングまたはプロトタイプがなく、コンストラクターとして使用できないことです。 矢印関数は、パラメーターの前後の括弧を省略し、暗黙的な戻りを伴う簡潔な関数本体の概念を追加する機能を付与するため、従来の関数のよりコンパクトな代替手段として作成することもできます。

このセクションでは、これらの各ケースを説明する例を見ていきます。

字句this

キーワードthisは、JavaScriptではトリッキーなトピックと見なされることがよくあります。 記事JavaScriptでこれを理解し、バインドし、呼び出し、適用するは、thisがどのように機能するか、およびプログラムがグローバルコンテキスト、オブジェクト内のメソッド、関数またはクラスのコンストラクター、またはDOM[X298X]イベントハンドラーとして。

矢印関数には字句thisがあります。つまり、thisの値は、周囲のスコープ(字句環境)によって決定されます。

次の例では、従来の関数と矢印関数がthisを処理する方法の違いを示します。 次のprintNumbersオブジェクトには、phrasenumbersの2つのプロパティがあります。 オブジェクトには、loopというメソッドもあります。このメソッドは、phrase文字列と現在の値をnumbersに出力する必要があります。

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    this.numbers.forEach(function (number) {
      console.log(this.phrase, number)
    })
  },
}

loop関数が、各反復のループ内の文字列と現在の数値を出力することを期待するかもしれません。 ただし、関数を実行した結果、phraseは実際にはundefinedになります。

printNumbers.loop()

これにより、次のようになります。

Outputundefined 1
undefined 2
undefined 3
undefined 4

このように、this.phraseは未定義であり、forEachメソッドに渡された無名関数内のthisprintNumbersオブジェクトを参照していないことを示しています。 これは、従来の関数がprintNumbersオブジェクトである環境のスコープからthis値を決定しないためです。

古いバージョンのJavaScriptでは、bindメソッドを使用する必要がありました。このメソッドはthisを明示的に設定します。 このパターンは、ES6が登場する前の、Reactなどの以前のバージョンのフレームワークでよく見られます。

bindを使用して、機能を修正します。

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    // Bind the `this` from printNumbers to the inner forEach function
    this.numbers.forEach(
      function (number) {
        console.log(this.phrase, number)
      }.bind(this),
    )
  },
}

printNumbers.loop()

これにより、期待される結果が得られます。

OutputThe current value is: 1
The current value is: 2
The current value is: 3
The current value is: 4

矢印関数は、これに対処するためのより直接的な方法を提供します。 それらのthis値は字句スコープに基づいて決定されるため、forEachで呼び出される内部関数は、次に示すように、外部printNumbersオブジェクトのプロパティにアクセスできるようになります。

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop() {
    this.numbers.forEach((number) => {
      console.log(this.phrase, number)
    })
  },
}

printNumbers.loop()

これにより、期待される結果が得られます。

OutputThe current value is: 1
The current value is: 2
The current value is: 3
The current value is: 4

これらの例は、 forEachmapfilterreduceなどの組み込み配列メソッドで矢印関数を使用する方が直感的であることを示しています。読みやすく、この戦略が期待に応える可能性が高くなります。

矢印はオブジェクトメソッドとして機能します

矢印関数は、配列メソッドに渡されるパラメーター関数としては優れていますが、thisの字句スコープを使用する方法のため、オブジェクトメソッドとしては効果的ではありません。 前と同じ例を使用して、loopメソッドを取得し、それを矢印関数に変換して、実行方法を確認します。

const printNumbers = {
  phrase: 'The current value is:',
  numbers: [1, 2, 3, 4],

  loop: () => {
    this.numbers.forEach((number) => {
      console.log(this.phrase, number)
    })
  },
}

このオブジェクトメソッドの場合、thisprintNumbersオブジェクトのプロパティとメソッドを参照する必要があります。 ただし、オブジェクトは新しい字句スコープを作成しないため、矢印関数はオブジェクトを超えてthisの値を探します。

loop()メソッドを呼び出します。

printNumbers.loop()

これにより、次のようになります。

OutputUncaught TypeError: Cannot read property 'forEach' of undefined

オブジェクトは字句スコープを作成しないため、arrow関数メソッドは外部スコープ(この例では Window )でthisを検索します。 numbersプロパティはWindowオブジェクトに存在しないため、エラーがスローされます。 原則として、デフォルトでは従来の関数をオブジェクトメソッドとして使用する方が安全です。

矢印機能にはconstructorまたはprototypeはありません

このシリーズの前半のJavaScriptでのプロトタイプと継承の理解チュートリアルでは、関数とクラスにprototypeプロパティがあることを説明しました。これは、JavaScriptがクローン作成と継承の青写真として使用するものです。

これを説明するために、関数を作成し、自動的に割り当てられたprototypeプロパティをログに記録します。

function myFunction() {
  this.value = 5
}

// Log the prototype property of myFunction
console.log(myFunction.prototype)

これにより、コンソールに次のように出力されます。

Output{constructor: ƒ}

これは、prototypeプロパティに、constructorを持つオブジェクトがあることを示しています。 これにより、newキーワードを使用して、関数のインスタンスを作成できます。

const instance = new myFunction()

console.log(instance.value)

これにより、関数を最初に宣言したときに定義したvalueプロパティの値が生成されます。

Output5

対照的に、矢印関数にはprototypeプロパティがありません。 新しい矢印関数を作成し、そのプロトタイプをログに記録してみてください。

const myArrowFunction = () => {}

// Attempt to log the prototype property of myArrowFunction
console.log(myArrowFunction.prototype)

これにより、次のようになります。

Outputundefined

prototypeプロパティが欠落しているため、newキーワードは使用できず、矢印関数からインスタンスを作成できません。

const arrowInstance = new myArrowFunction()

console.log(arrowInstance)

これにより、次のエラーが発生します。

OutputUncaught TypeError: myArrowFunction is not a constructor

これは、前の例と一致しています。矢印関数には独自のthis値がないため、コンストラクターとして矢印関数を使用することはできません。

ここに示すように、矢印関数には多くの微妙な変更があり、ES5以前の従来の関数とは動作が異なります。 また、いくつかのオプションの構文上の変更があり、矢印関数の記述がより速く、より冗長ではなくなりました。 次のセクションでは、これらの構文変更の例を示します。

暗黙のリターン

従来の関数の本体は、中括弧{}を使用してブロック内に含まれ、コードがreturnキーワードに遭遇すると終了します。 以下は、この実装が矢印関数としてどのように見えるかです。

const sum = (a, b) => {
  return a + b
}

矢印関数は、簡潔な本体構文、または暗黙のreturnを導入します。 これにより、中括弧とreturnキーワードを省略できます。

const sum = (a, b) => a + b

暗黙的な戻り値は、mapfilter、およびその他の一般的な配列メソッドで簡潔な1行の操作を作成する場合に役立ちます。 角かっことreturnキーワードの両方を省略する必要があることに注意してください。 本文を1行のreturnステートメントとして記述できない場合は、通常のブロック本文構文を使用する必要があります。

オブジェクトを返す場合、構文では、オブジェクトリテラルを括弧で囲む必要があります。 それ以外の場合、角かっこは関数本体として扱われ、return値を計算しません。

これを説明するために、次の例を見つけてください。

const sum = (a, b) => ({result: a + b})

sum(1, 2)

これにより、次の出力が得られます。

Output{result: 3}

単一のパラメーターの前後の括弧の省略

もう1つの便利な構文拡張機能は、関数内の1つのパラメーターの前後から括弧を削除する機能です。 次の例では、square関数は1つのパラメーターxでのみ動作します。

const square = (x) => x * x

その結果、パラメーターを囲む括弧を省略でき、同じように機能します。

const square = x => x * x

square(10)

これにより、次のようになります。

Output100

関数がパラメータを受け取らない場合は、括弧が必要になることに注意してください。

const greet = () => 'Hello!'

greet()

greet()の呼び出しは、次のように機能します。

Output'Hello!'

一部のコードベースは、可能な限り括弧を省略することを選択し、他のコードベースは、特に TypeScript を使用し、各変数とパラメーターに関する詳細情報を必要とするコードベースでは、パラメーターの前後に常に括弧を保持することを選択します。 矢印関数の書き方を決めるときは、貢献しているプロジェクトのスタイルガイドを確認してください。

結論

この記事では、従来の関数と、関数宣言と関数式の違いについて説明しました。 矢印関数は常に匿名であり、prototypeまたはconstructorがなく、newキーワードと一緒に使用できず、thisの値を決定することを学びました。 ]字句スコープを介して。 最後に、単一パラメーター関数の暗黙的な戻りや括弧の省略など、矢印関数で使用できる新しい構文拡張機能について説明しました。

基本的な関数のレビューについては、JavaScriptで関数を定義する方法をお読みください。 JavaScriptでのスコープとホイストの概念の詳細については、 JavaScriptでの変数、スコープ、およびホイストについてを参照してください。