JavaScriptの矢印関数:それらが必要な理由、それらを処理する方法、それらを使用するタイミング、および使用しないタイミング

最新のJavaScriptの最も注目すべき革新の1つは、矢印関数の外観です。これは、「太い」矢印関数と呼ばれることもあります。 そのような関数を宣言するとき、特殊な文字の組み合わせ- =>ます。

矢印関数には、従来の関数よりも2つの大きな利点があります。 1つ目は、非常に便利でコンパクトな構文です。 2つ目は、矢印関数でthis値を使用する方法が、通常の関数よりも直感的に見えることです。

画像

これらの利点やその他の利点により、関数を宣言する他の方法よりも矢印構文に無条件の優先順位が与えられることがあります。 たとえば、Airbnbの人気のあるeslint構成では、匿名関数が作成されるたびに、そのような関数は矢印のようになります。

ただし、プログラミングで使用される他の概念やメカニズムと同様に、矢印関数には長所と短所があります。 それらの使用は、負の副作用を引き起こす可能性があります。 矢印関数を正しく使用するには、それらに関連する考えられる問題について知る必要があります。

本日発表する翻訳では、矢印の機能の仕組みに焦点を当てます。 ここでは、それらを使用することでコードを改善できる状況と、使用すべきでない状況を検討します。

JavaScriptの機能矢印関数


JavaScriptの矢印関数は、Pythonのラムダ関数やRubyのブロックのようなものです。

これらは、固定数の引数を取り、それらを含むスコープのコンテキスト、つまり、関数またはそれらが宣言されている他のコードのコンテキストで機能する特別な構文を持つ匿名関数です。

これについて詳しく説明しましょう。

▍構文矢印関数


矢印関数は単一のスキームに従って構築されますが、関数の構造は特別な場合に単純化できます。 矢印関数の基本構造は次のようになります。

 (argument1, argument2, ... argumentN) => { //   } 

関数の引数のリストは括弧で囲まれ、その後に=>文字で構成される矢印が続き、その後に関数の本体が中括弧で囲まれています。

これは、通常の関数の動作と非常によく似ています。主な違いは、 functionキーワードがここで省略され、引数リストの後に矢印が追加されることです。

ただし、場合によっては、よりコンパクトな構造を使用して単純な矢印関数を宣言できます。

関数の本体が単一の式で表される場合に使用される構文を検討してください。 関数の本体を囲む中括弧なしで行うことができ、この結果が自動的に返されるため、式の評価結果を明示的に返す必要がなくなります。 たとえば、次のようになります。

 const add = (a, b) => a + b; 

次に、関数の引数が1つしかない場合に使用される、関数の省略表記の別のバリアントを示します。

 const getFirst = array => array[0]; 

ご覧のとおり、ここでは引数リストを囲む括弧は省略されています。 さらに、この例では単一のコマンドで表される関数の本体も角括弧なしで記述されています。 後で、このような設計の利点について詳しく説明します。

turnオブジェクトと短いレコード矢印関数を返す


矢印関数を使用する場合、いくつかのより複雑な構文構造も使用されますが、これは知っておくと便利です。

たとえば、単一行の式を使用して、オブジェクトリテラル関数から戻るようにします。 矢印関数について既に知っていることを考えると、関数宣言は次のように見えるかもしれません。

 (name, description) => {name: name, description: description}; 

このコードの問題は曖昧さです。 つまり、オブジェクトリテラルを記述するために使用する中括弧は、関数の本体をそれらの中に入れようとしているように見えます。

オブジェクトリテラルを意味することをシステムに示すために、それを括弧で囲む必要があります:

 (name, description) => ({name: name, description: description}); 

▍矢印関数とその実行コンテキスト


他の関数とは異なり、矢印関数には独自の実行コンテキストがありません。

実際には、これは、エンティティthisおよびargumentsを親関数から継承することを意味します。

たとえば、次のコードに示されている2つの関数を比較します。 それらの1つは普通で、2つ目は矢印です。

 const test = { name: 'test object', createAnonFunction: function() {   return function() {     console.log(this.name);     console.log(arguments);   }; }, createArrowFunction: function() {   return () => {     console.log(this.name);     console.log(arguments);   }; } }; 

2つのメソッドを持つtestオブジェクトがあります。 それらはそれぞれ、匿名関数を作成して返す関数です。 これらの方法の違いは、最初の方法では従来の関数式が使用され、2番目の方法では-矢印機能のみが使用されることです。

コンソールでこのコードを試して、オブジェクトのメソッドに同じ引数を渡すと、メソッドは非常に似ていますが、異なる結果が得られます。

 > const anon = test.createAnonFunction('hello', 'world'); > const arrow = test.createArrowFunction('hello', 'world'); > anon(); undefined {} > arrow(); test object { '0': 'hello', '1': 'world' } 

匿名関数には独自のコンテキストがあるため、呼び出されたときにtest.nameを呼び出すと、オブジェクトのnameプロパティの値はtest.nameません。また、 argumentsを呼び出すと、調査中の関数を作成して返すために使用された関数の引数のリストは表示されません。

矢印関数の場合、そのコンテキストはそれを作成した関数のコンテキストと一致することがわかります。これにより、この関数によって渡される引数のリストと、この関数がメソッドであるオブジェクトのnameプロパティの両方にアクセスできます。

矢印関数がコードを改善する状況


values値のリストの処理


JavaScriptに表示された後の矢印関数と同様に、従来のラムダ関数は、通常、特定のリストの各要素に特定の関数が適用される状況で使用されます。

たとえば、 map配列メソッドを使用して変換する必要がある値の配列がある場合、矢印関数はそのような変換を記述するのに理想的です。

 const words = ['hello', 'WORLD', 'Whatever']; const downcasedWords = words.map(word => word.toLowerCase()); 

オブジェクトのプロパティを操作することで構成される、矢印関数の同様の使用の非常に一般的な例を次に示します。

 const names = objects.map(object => object.name); 

同様に、従来のforループの代わりに、現代のイテレータベースのforEachループを使用する場合、その矢印関数this親エンティティのthis使用し、直感的に使用できます。

 this.examples.forEach(example => { this.runExample(example); }); 

▍約束と約束の鎖


矢印関数を使用すると、より簡潔で理解しやすいコードを記述できる別の状況は、非同期ソフトウェア構成によって表されます。

そのため、 約束は非同期コードの操作大幅に簡素化します。 同時に、async / await コンストラクトを使用することを好む場合でも、このコンストラクトはそれらに基づいているため、promises理解せずにはできません。

ただし、promiseを使用する場合は、非同期コードの完了後または一部のAPIの非同期呼び出しの完了後に呼び出される関数を宣言する必要があります。

これは、特に結果の関数が特定の状態を持っている場合に、オブジェクト内の何かを参照する矢印関数を使用するのに理想的な場所です。 たとえば、次のようになります。

 this.doSomethingAsync().then((result) => { this.storeResult(result); }); 

▍オブジェクト変換


矢印関数のもう1つの一般的な使用例は、オブジェクト変換をカプセル化することです。

たとえば、Vue.jsには、mapStateを使用してVueコンポーネントにVuexストレージフラグメントを直接含める一般的なパターンがあります。

この操作には、特定のコンポーネントに必要なものを初期の完全な状態から正確に選択する「コンバーター」のセットの宣言が含まれます。

これらの単純な変換は、矢印関数を使用するのに理想的な場所です。 例:

 export default { computed: {   ...mapState({     results: state => state.results,     users: state => state.users,   }); } } 

矢印関数を使用すべきではない状況


▍オブジェクトメソッド


矢印関数を使用するのは良い考えではない状況がいくつかあります。 矢印関数を軽く使用すると、プログラマーを助けるだけでなく、問題の原因にもなります。

最初のそのような状況は、矢印メソッドをオブジェクトメソッドとして使用することです。 ここでは、従来の機能に固有の実行コンテキストとthisが重要です。

かつて、クラスプロパティと矢印関数の組み合わせを使用して、「自動バインディング」、つまりイベントハンドラで使用できるがクラスにバインドされたままのメソッドを作成することが一般的でした。 次のようになりました。

 class Counter { counter = 0; handleClick = () => {   this.counter++; } } 

同様の構造を使用して、 Counterクラスのインスタンスのコンテキストではなく、イベントハンドラーによってhandleClick関数handleClick呼び出された場合でも、この関数はこのインスタンスのデータにアクセスできました。

ただし、このアプローチには多くのマイナス面があり、 この点にはこの資料が当てられています。

もちろん、ここでの矢印関数の使用は、関数をバインドするための便利な外観の方法ですが、多くの面でこの関数の動作は直感的とはほど遠いものであり、たとえば、対応するオブジェクトをプロトタイプとして使用しようとする状況でのテストや問題の作成を妨げます。

このような場合、矢印関数の代わりに、通常の関数を使用し、必要に応じて、コンストラクターでオブジェクトのインスタンスをバインドします。

 class Counter { counter = 0; handleClick() {   this.counter++; } constructor() {   this.handleClick = this.handleClick.bind(this); } } 

ongロングコールチェーン


矢印関数は、多くの異なる組み合わせ、特に関数呼び出しの長いチェーンで使用する予定の場合、問題の原因になる可能性があります。

匿名関数を使用する場合など、このような問題の主な理由は、 呼び出しスタックトレースの非常に有益な結果が得られないことです。

たとえば、イテレータで使用される関数について話している場合など、関数呼び出しのネストのレベルが1レベルしかない場合、これはそれほど悪くはありません。 ただし、使用されているすべての関数が矢印関数として宣言されており、これらの関数が相互に呼び出している場合、エラーが発生すると、何が起こっているのかを簡単に把握できません。 エラーメッセージは次のようになります。

 {anonymous}() {anonymous}() {anonymous}() {anonymous}() {anonymous}() 

dynamic動的なコンテキストを持つ関数


矢印関数が問題の原因となる可能性のある最後の状況は、動的thisバインディングが必要な場合に使用することです。

そのような状況で矢印関数が使用される場合、動的thisバインディングは機能しません。 この不快な驚きは、矢印関数が誤って使用されているコードで作業しなければならない人々に何が起こるのかという理由について、人を困惑させる可能性があります。

矢印関数の使用を検討する際に留意すべき事項を次に示します。


もちろん、ソフトウェアメカニズムの標準的な動作を変更するために、矢印関数を意図的に使用することもできます。 しかし、特にjQueryとVueの場合、これはしばしばシステムの通常の機能と競合し、完全に正常に見えるコードが突然動作を拒否する理由をプログラマーが理解できないという事実につながります。

まとめ


矢印関数は、素晴らしい新鮮なJavaScript機能です。 多くの状況で、以前よりも便利なコードを書くことができます。 しかし、他の機能の場合と同様に、それらには利点と欠点の両方があります。 したがって、通常の機能の完全な代替と見なすことなく、便利な場合に矢印機能を使用する必要があります。

親愛なる読者! 矢印関数を使用すると、エラー、不便、またはプログラムの予期しない動作が発生する状況に遭遇しましたか?

Source: https://habr.com/ru/post/J428566/


All Articles