web-dev-qa-db-ja.com

JavaScriptイベントコードのコールバックとパラメーターに名前付き関数の代わりに匿名関数を使用する利点は何ですか?

私はJavaScriptに慣れていない。言語の概念の多くを理解しており、プロトタイプ継承モデルを読んでいます。そして、ますますインタラクティブなフロントエンドのもので口whiを吹いています。興味深い言語ですが、多くの非自明な相互作用モデルに典型的なコールバックスパゲッティによって、私はいつも少しオフになっています。

JavaScriptのネストされたコールバックのネストである読みやすさの悪夢にもかかわらず、私にとっていつも奇妙に思える何かは、多くの例やチュートリアルで私がめったに見ない1つのことは、コールバック引数として定義済みの名前付き関数の使用です。私はJava日ごとにプログラマーであり、 コード単位のEnterprise-y名に関するステレオタイプなジャブを捨てる 私が楽しんでいるものの1つです機能性の高いIDEを厳選した言語で作業すると、意味のある名前を使用すると、実際の生産性を高めることなく、コードの意図と意味をより明確にすることができます。コード?

考えてみると、この考えに賛成と反対の両方の議論を思いつくことができますが、言語に対する私の素朴さと新しさは、なぜこれが技術レベルで良いのかという結論に達するのを妨げます。

長所:

  • 柔軟性。コールバックパラメーターを持つ非同期関数は、多くの異なるコードパスのいずれかで到達でき、可能性のあるすべてのEdgeケースを説明するために名前付き関数を記述する必要があります。
  • 速度。ハッカーのメンタリティに大きく影響します。動作するまで物をボルトで固定します。
  • 他のみんながやっています
  • たとえそうであっても小さなファイルサイズですが、すべてのビットがウェブ上で重要です。
  • シンプルなAST?匿名関数は実行時に生成されると想定しているため、JITは名前を命令にマッピングすることをいじりませんが、この時点で推測しているだけです。
  • より迅速な派遣?これについてもわからない。再び推測。

短所:

  • 恐ろしくて読めない
  • コールバックの群れの奥深くにナットを入れ子にしている場合、混乱を助長します(これは、公平を期すために、おそらく最初は不十分に構築されたコードを書いていることを意味しますが、かなり一般的です)。
  • 機能的なバックグラウンドを持たない人にとっては、それは奇妙な概念かもしれません

JavaScriptコードを以前よりもはるかに高速に実行する能力を備えた最新のブラウザーが多数あるため、匿名のコールバックを使用してパフォーマンスを向上させることが必要になる場合があることを理解できません。名前付き関数の使用が実行可能な状況にある場合(予測可能な動作と実行パス)、そうしない理由はないようです。

それで、私が気付いていない技術的な理由や落とし穴があり、それがこの慣行をある理由でとても一般的にしているのですか?

41
Doug Stephen

私は3つの理由で匿名関数を使用しています。

  1. 関数が1か所でしか呼び出されないために名前が不要な場合は、名前空間に名前を追加します。
  2. 無名関数はインラインで宣言され、インライン関数は親スコープの変数にアクセスできるという利点があります。はい、匿名関数に名前を付けることができますが、インラインで宣言されている場合、通常は意味がありません。そのため、インラインには大きな利点があります。インラインを使用している場合、名前を付ける理由はほとんどありません。
  3. 呼び出し元のコード内でハンドラーを定義すると、コードは自己完結型で読みやすいように見えます。その名前の関数を探しに行かずに、ほとんど連続してコードを読むことができます。

匿名関数を深くネストすることは避けようとします。なぜなら、それは理解して読むのが難しいからです。通常、それが発生した場合、コードを構造化するより良い方法があり(ループを使用したり、データテーブルを使用したりするなど)、名前付き関数も通常は解決策ではありません。

コールバックが約15-20行を超える長さを取得し始め、親スコープの変数に直接アクセスする必要がない場合、名前を付けてそれを分割したくなると思います他の場所で宣言された独自の名前付き関数です。ここには間違いなく読みやすいポイントがあります。それは、長くなる自明でない関数が、それ自身の名前付きユニットに置かれた場合に、より保守しやすいということです。しかし、私が遭遇するほとんどのコールバックはそれほど長くはないので、インラインに保つ方が読みやすいと思います。

44
jfriend00

私は自分で名前付き関数を好むが、私にとっては一つの質問に帰着する:

この関数を他の場所で使用しますか?

答えが「はい」の場合、名前を付けて定義します。そうでない場合は、匿名関数として渡します。

一度しか使用しない場合、グローバルネームスペースを使用することは意味がありません。今日の複雑なフロントエンドでは、匿名である可能性のある名前付き関数の数が急速に増加し(実際に複雑な設計では1000を超えると容易に)、匿名関数を優先することで(比較的)パフォーマンスが大幅に向上します。

ただし、コードの保守性も非常に重要です。それぞれの状況は異なります。そもそもこれらの関数の多くを書いていないのであれば、どちらにしてもそれは害になりません。本当にあなたの好み次第です。

名前に関する別の注意。長い名前を定義する習慣を身に付けると、ファイルサイズが本当に損なわれます。次の例をご覧ください。

これらの関数の両方が同じことをすると仮定します。

function addTimes(time1, time2)
{
    // return time1 + time2;
}

function addTwoTimesIn24HourFormat(time1, time2)
{
    // return time1 + time2;
}

2番目は、名前で何をするかを正確に示しています。 1つ目はより曖昧です。ただし、名前には17文字の違いがあります。関数がコード全体で8回呼び出されたとします。これは153余分なバイトで、コードは不要でした。巨大ではありませんが、それが習慣である場合、それを10から100の関数に外挿すると、ダウンロードで数KBの違いが生じやすくなります。

ただし、パフォーマンスのメリットと保守性を比較検討する必要があります。これはスクリプト言語を扱うことの苦痛です。

10
Andrew Ensley

パーティーに少し遅れましたが、匿名またはそれ以外の機能に関するいくつかの側面がまだ言及されていません...

Anon funcsは、チーム間でのコードに関するヒューマノイドの会話では簡単に参照されません。たとえば、「ジョー、その関数内でアルゴリズムが何をするのか説明してもらえますか?...どれですか?fooApp関数内の17番目の匿名関数です... ...いいえ、そうではありません!17番目のもの!」

匿名関数もデバッガーに対して匿名です。 (duh!)したがって、デバッガースタックトレースは通常、疑問符などを表示するだけであり、複数のブレークポイントを設定した場合はあまり役に立ちません。ブレークポイントにヒットしますが、デバッグウィンドウを上下にスクロールして、プログラムのどこにいるのかを見つけます。これは、疑問符の機能ではうまくいかないからです。

グローバル名前空間の汚染に関する懸念は有効ですが、「myFooApp.happyFunc = function(...){...};」のように、関数を独自のルートオブジェクト内のノードとして命名することで簡単に修正できます。

グローバル名前空間で、または上記のようにルートオブジェクトのノードとして使用可能な関数は、開発およびデバッグ中にデバッガーから直接呼び出すことができます。たとえば、コンソールコマンドラインで「myFooApp.happyFunc(42)」を実行します。これは、コンパイルされたプログラミング言語には(ネイティブでは)存在しない非常に強力な機能です。 anon funcで試してみてください。

Anon funcsは、varに割り当ててから、インライン化の代わりにコールバックとしてvarを渡すことで読みやすくすることができます。例:var funky = function(...){...}; jQuery( '#otis')。click(funky);

上記のアプローチを使用すると、親funcの上部でいくつかのanon funcをグループ化することができ、その下で、シーケンシャルステートメントの内容がより厳密にグループ化され、読みやすくなります。

3
RickS

さて、私の議論のために明確にするために、以下はすべて私の本の匿名関数/関数式です:

_var x = function(){ alert('hi'); },

indexOfHandyMethods = {
   hi: function(){ alert('hi'); },
   high: function(){
       buyPotatoChips();
       playBobMarley();
   }
};

someObject.someEventListenerHandlerAssigner( function(e){
    if(e.doIt === true){ doStuff(e.someId); }
} );

(function namedButAnon(){ alert('name visible internally only'); })()
_

長所:

  • 特に再帰関数(実際にはarguments.calleeが非推奨になっているため)が内部で最後の例ごとに名前付き参照を使用する可能性がある場合、特にこの関数でのみ実行される関数を明確にします場所。

  • コードの読みやすさの勝利:メソッドとして割り当てられたanon funcを使用したオブジェクトリテラルの例では、そのオブジェクトリテラルのポイント全体が関連する機能を実装することである場合、コード内のロジックのハントとペックに場所を追加するのは愚かなことです同じ便利に参照されるスポット。ただし、コンストラクターでパブリックメソッドを宣言する場合、ラベル付き関数をインラインで定義し、this.sameFuncNameの参照として割り当てる傾向があります。 「this」なしで内部的に同じメソッドを使用できます。お互いを呼び出したときに、定義の順序を気にしなくても問題ありません。

  • 不必要なグローバル名前空間の汚染を回避するのに役立ちます-ただし、内部名前空間は、複数のチームによって同時に広範囲に満たされたり処理されたりするべきではないので、その議論は私にとって少しばかげているようです。

  • 短いイベントハンドラーを設定するときのインラインコールバックに同意します。 1〜5行の関数を探す必要があるのはばかげています。特にJSと関数の巻き上げでは、定義は同じファイル内でもどこでも終わる可能性があるためです。これは、何も壊さずに偶然に発生する可能性があり、いいえ、あなたは常にそのようなものを制御するわけではありません。イベントは常にコールバック関数が起動される結果になります。大きなコードベースで単純なイベントハンドラーをリバースエンジニアリングするためだけにスキャンする必要のある名前のチェーンにリンクを追加する理由はなく、デバッグ時に有用な情報を記録するメソッドにイベントトリガー自体を抽象化することでスタックトレースの問題に対処できますモードがオンで、トリガーを起動します。私は実際にこの方法でインターフェース全体を構築し始めています。

  • 関数定義の順序が重要な場合に役立ちます。場合によっては、既定の関数が、コード内の特定のポイントで再定義してもよいと思うまで、それが正しいと思うようにしたいことがあります。または、依存関係がシャッフルされたときに破損をより明確にしたい場合。

短所:

  • Anon関数は、関数ホイストを利用できません。これは大きな違いです。明示的に名前を付けた独自のfuncsとオブジェクトコンストラクターを下部に向かって定義し、オブジェクト定義とメインループタイプのものを上部に配置するために、ホイストを利用する傾向があります。 varsに適切な名前を付けるとコードが読みやすくなり、ctrl-Fingの前に何が起こっているのかを大まかに把握して、重要な場合にのみ詳細を確認できます。高度なイベント駆動型インターフェイスでは、利用可能なものに厳密な順序を課すことで、あなたが尻に噛みつくことができるので、巻き上げは大きな利点にもなります。巻き上げには独自の注意事項(循環参照電位など)がありますが、正しく使用するとコードを整理して読みやすくするための非常に便利なツールです。

  • 読みやすさ/デバッグ。絶対に使い古されており、デバッグやコードの読みやすさが面倒になります。たとえば、JQに大きく依存しているコードベースは、$スープのほとんど避けられない、非重くて非常に過負荷な引数を賢明な方法でカプセル化しない場合、読み取りおよびデバッグする深刻なPITAになります。たとえば、JQueryのホバーメソッドは、2つのanon funcをドロップしたときに、anon funcを使いすぎる典型的な例です。 1つまたは2つのイベントのハンドラー。 $(this).hover(onMouseOver, onMouseOut)は、2つの匿名関数よりも明確です。

1
Erik Reppen

名前付き関数を使用すると読みやすくなり、以下の例のように自己参照することもできます。

(function recursion(iteration){
    if (iteration > 0) {
      console.log(iteration);
      recursion(--iteration);
    } else {
      console.log('done');
    }
})(20);

console.log('recursion defined? ' + (typeof recursion === 'function'));

http://jsfiddle.net/Yq2WD/

これは、それ自体を参照するが、グローバル名前空間に追加しない関数をすぐに呼び出したい場合に便利です。読み取り可能ですが、汚染はありません。ケーキを持って食べてください。

こんにちは、私の名前はジェイソンですORこんにちは、私の名前は????あなたが選んだものです。

1
Jason Sebring