65

私はJavaScriptが初めてです。私は言語の概念の多くを理解しており、プロトタイプの継承モデルを読み進めてきました。また、ますますインタラクティブなフロントエンドのもので口笛を吹いています。これは興味深い言語ですが、多くの重要な対話モデルに典型的なコールバック スパゲッティに、私はいつも少しうんざりしています。

私にはいつも奇妙に思えますが、これは JavaScript のネストされたコールバックの入れ子であり、可読性の悪夢であるにもかかわらず、多くの例やチュートリアルでめったに見られないことの 1 つは、定義済みの名前付き関数をコールバック引数として使用していることです。私は日々 Java プログラマーであり、コード単位の Enterprise-y 名に関するステレオタイプのジャブを破棄します。機能豊富な IDE の強力な選択を備えた言語で作業することについて私が楽しむようになったものの 1 つは、意味のある、名前が長い場合、実際の生産性を低下させることなく、コードの意図と意味をより明確にすることができます。では、JavaScript コードを記述するときに同じアプローチを使用してみませんか?

考えてみると、このアイデアに賛成でも反対でもある議論を思いつくことができますが、私の素朴さと言語への新しさにより、なぜこれが技術的なレベルで良いのかについての結論に達することができません.

長所:

  • 柔軟性。コールバック パラメーターを持つ非同期関数は、多くの異なるコード パスの 1 つによって到達される可能性があり、考えられるすべてのエッジ ケースを説明するために名前付き関数を記述するのが面倒になる可能性があります。
  • スピード。それはハッカーの考え方に大きく影響します。動作するまでボルトで固定します。
  • 他はみんなやってる
  • たとえ些細なことであっても、ファイルサイズは小さくなりますが、すべてのビットが Web でカウントされます。
  • より単純な AST? 匿名関数は実行時に生成されるため、JIT は名前を命令にマッピングすることはありませんが、この時点では推測しているだけです。
  • 発送が早い?これについてもよくわかりません。もう一度推測します。

短所:

  • むずかしくて読めない
  • コールバックの沼地の奥深くに入れ子になっていると、混乱が増します (公平を期すために、最初から不十分に構築されたコードを書いている可能性がありますが、これは非常に一般的なことです)。
  • 機能的なバックグラウンドのない人にとって、理解するのは奇妙な概念になる可能性があります

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

それで、私が気付いていない技術的な理由や落とし穴があり、それが理由でこの慣習が非常に一般的になっていますか?

4

6 に答える 6

59

私が無名関数を使用する理由は 3 つあります。

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

匿名関数の深い入れ子は避けようとしていますが、それは理解したり読んだりするのが面倒になる可能性があるためです。通常、それが発生した場合、コードを構造化するためのより良い方法があり (ループを使用する場合もあれば、データ テーブルを使用する場合もあります...)、名前付き関数も通常は解決策ではありません。

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

于 2012-04-22T23:58:49.390 に答える
13

私自身は名前付き関数を好みますが、私にとっては 1 つの質問に行き着きます。

この機能を他の場所で使用する予定はありますか?

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

1 回しか使用しない場合、グローバル名前空間にそれを詰め込むのは意味がありません。今日の複雑なフロントエンドでは、匿名にすることができた名前付き関数の数が急速に増加し (非常に複雑な設計では 1000 を簡単に超えます)、匿名関数を優先することで (比較的) パフォーマンスが大幅に向上します。

ただし、コードの保守性も非常に重要です。それぞれの状況は異なります。最初からこれらの関数をあまり作成していない場合は、どちらの方法で作成しても害はありません。それは本当にあなたの好み次第です。

名前についてもう一つ注意点。長い名前を定義する習慣を身につけると、ファイル サイズが大幅に悪化します。次の例を見てください。

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

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

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

2番目は、名前で何をするかを正確に示しています。最初のものはもっとあいまいです。ただし、名前には17文字の違いがあります。関数がコード全体で 8 回呼び出されたとします。これは、コードに必要のない余分な 153 バイトです。巨大ではありませんが、それが習慣である場合、それを数十または数百の関数に外挿すると、ダウンロードで数 KB の違いが生じることを簡単に意味します。

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

于 2012-04-23T14:55:33.820 に答える
10

パーティーには少し遅れましたが、匿名またはその他の関数の側面についてまだ言及されていないものもあります...

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

Anon 関数は、デバッガーに対しても匿名です。(当然!) したがって、デバッガーのスタック トレースは通常、疑問符または同様のものを表示するだけであり、複数のブレークポイントを設定した場合はあまり役に立ちません。ブレークポイントに到達しましたが、デバッグ ウィンドウを上下にスクロールして、プログラムのどこにいるのかを調べていることに気付きました。

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

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

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

上記のアプローチを使用すると、親関数の上部にいくつかのアノン関数を潜在的にグループ化でき、その下では、シーケンシャル ステートメントの肉がより緊密にグループ化され、読みやすくなります。

于 2016-11-20T16:54:52.667 に答える
1

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

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が非推奨であるため、最後の例のように名前付き参照を内部的に使用できる場合)では、少し面倒なことを減らすことができ、関数がこの関数でのみ起動することを明確にします場所。

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

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

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

  • 関数定義の順序を重視したい場合に便利です。場合によっては、デフォルトの関数が、再定義しても問題ないコードの特定のポイントまで、自分が思っているとおりであることを確認したいことがあります。または、依存関係がシャッフルされたときに破損がより明白になるようにする必要があります。

短所:

  • Anon 関数は、関数巻き上げを利用できません。これは大きな違いです。私は巻き上げを大いに利用して、明示的に名前を付けた独自の関数とオブジェクト コンストラクターを一番下に定義し、オブジェクト定義とメインループ タイプのものを一番上に配置する傾向があります。変数に適切な名前を付けて、重要な場合にのみ詳細についてctrl-Fingする前に何が起こっているかを幅広く把握すると、コードが読みやすくなります。巻き上げは、非常にイベント駆動型のインターフェイスでも大きなメリットとなります。いつ何が利用可能になるかを厳密に指定すると、厄介な問題が発生する可能性があります。巻き上げには独自の注意点 (循環参照の可能性など) がありますが、正しく使用すると、コードを整理して読みやすくするための非常に便利なツールです。

  • 読みやすさ/デバッグ。確かに、それらは時々あまりにも頻繁に使用され、デバッグやコードの読みやすさが面倒になる可能性があります. たとえば、JQ に大きく依存しているコードベースは、$ スープのほぼ必然的な非重くて大量にオーバーロードされた引数を賢明な方法でカプセル化しない場合、読み取りとデバッグが深刻な PITA になる可能性があります。たとえば、JQuery のホバー メソッドは、2 つのアノン関数をドロップした場合のアノン関数の過剰使用の典型的な例です。これは、初めて使用する人が、割り当てるためにオーバーロードされた 1 つのメソッドではなく、標準のイベント リスナー割り当てメソッドであると想定しやすいためです。 1 つまたは 2 つのイベントのハンドラー。$(this).hover(onMouseOver, onMouseOut)2つのアノン関数よりもはるかに明確です。

于 2012-05-02T22:08:58.210 に答える