JavaScriptの矢印関数を理解する
著者は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
オブジェクトには、phrase
とnumbers
の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メソッドに渡された無名関数内のthis
がprintNumbers
オブジェクトを参照していないことを示しています。 これは、従来の関数が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
これらの例は、 forEach 、 map 、 filter 、reduceなどの組み込み配列メソッドで矢印関数を使用する方が直感的であることを示しています。読みやすく、この戦略が期待に応える可能性が高くなります。
矢印はオブジェクトメソッドとして機能します
矢印関数は、配列メソッドに渡されるパラメーター関数としては優れていますが、this
の字句スコープを使用する方法のため、オブジェクトメソッドとしては効果的ではありません。 前と同じ例を使用して、loop
メソッドを取得し、それを矢印関数に変換して、実行方法を確認します。
const printNumbers = { phrase: 'The current value is:', numbers: [1, 2, 3, 4], loop: () => { this.numbers.forEach((number) => { console.log(this.phrase, number) }) }, }
このオブジェクトメソッドの場合、this
はprintNumbers
オブジェクトのプロパティとメソッドを参照する必要があります。 ただし、オブジェクトは新しい字句スコープを作成しないため、矢印関数はオブジェクトを超えて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
暗黙的な戻り値は、map
、filter
、およびその他の一般的な配列メソッドで簡潔な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での変数、スコープ、およびホイストについてを参照してください。