JavaScriptの別の関数内で関数を定義する


83
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

上記と同様の構造の関数があります。タスク3を関数に抽象化したいのですがbar()、この関数へのアクセスをfoo(a)。の範囲内に限定したいと思います。

私が望むことを達成するために、次のように変更するのは正しいですか?

function foo(a) {
    function bar() {
        // Perform task 3
    }

    if (/* Some condition */) {
        // Perform task 1
        bar();
    }
    else {
        // Perform task 2
        bar();
    }
}

上記が正しければ、呼び出されるbar()たびに再定義さfoo(a)れますか?(ここではCPUリソースの浪費が心配です。)


1
自分で価値があるかどうかをテストします:jsperf.comtask3に依存すると思います。
tomByrer 2012

1
@tomByer -ツールを示唆ための1
tamakisquare

回答:


123

はい、あなたが持っているものは正しいです。いくつかの注意:

  • bar関数を呼び出すたびに作成されますが、次のようになりますfoo
    • 最近のブラウザでは、これは非常に高速なプロセスです。(一部のエンジンは、コードを1回だけコンパイルし、そのコードを毎回異なるコンテキストで再利用する場合があります。ほとんどの場合、GoogleのV8エンジン(Chromeなど)がそれを行います。)
    • また、機能によってbarは、一部のエンジンが「インライン化」できると判断し、関数呼び出しを完全に排除する場合があります。V8はこれを行います、そして私はそれがする唯一のエンジンではないと確信しています。当然、コードの動作を変更しない場合にのみ、これを実行できます。
  • bar毎回作成した場合のパフォーマンスへの影響は、JavaScriptエンジン間で大きく異なります。barが些細な場合は、検出できないものからかなり小さいものまでさまざまです。foo何千回も続けて(たとえば、mousemoveハンドラーから)呼び出していない場合は、心配する必要はありません。たとえそうだとしても、遅いエンジンで問題が発生した場合にのみ心配します。これはDOM操作を含むテストケースです。これは影響があることを示唆していますが、些細なものです(おそらくDOMのものによって洗い流されています)。ここでは、純粋な計算やってテストケースだはるかに高いの影響を示しているが、率直に言ってさえ、我々はの違い話をしているマイクロ取る何かでさえ92%増ので秒マイクロ発生する秒数はまだ非常に、非常に速いです。実際の影響が見られるまで/見ない限り、心配する必要はありません。
  • bar関数内からのみアクセス可能であり、関数へのその呼び出しのすべての変数と引数にアクセスできます。これはこれを非常に便利なパターンにします。
  • 関数宣言を使用したので、宣言をどこに置くかは問題ではないことに注意してください(上、下、または中央-フロー制御ステートメント内ではなく、関数のトップレベルにある限り)。構文エラー)、段階的なコードの最初の行が実行される前に定義されます。

あなたの答えのためのThx。それで、これは無視できるパフォーマンスヒットだと言っていますか?(のコピーがのbarすべての呼び出しで作成される場合foo
tamakisquare 2012

2
@ahmoo:JavaScriptのパフォーマンスでは、ほとんどの場合、答えは次のとおりです。状況によって異なります。:-)それは、それを実行するエンジンと、呼び出す頻度によって異なりますfoofoo何千回も続けて呼び出していない場合(たとえば、mousemoveハンドラーではない場合)、私はそれについてまったく心配しません。また、一部のエンジン(V8など)はとにかくコードをインライン化し、関数呼び出しを完全に排除します。ただし、そうすることで、外部で検出できる方法で何が起こっているかが変わらない場合に限ります。
TJ Crowder 2012

@TJCrowder:robrichの答えについてコメントできますか?その解決策はbar()、各呼び出しでの再作成を防ぎますか?また、foo.prototype.bar関数を定義するために使用することは役に立ちますか?
rkw 2012

4
@rkw:robrichの回答のように、関数を1回作成することは、呼び出しごとに関数を作成するコストを回避するための便利な方法です。bar呼び出しの変数と引数にアクセスできるという事実を失いfooます(操作したいものはすべて、渡す必要があります)。これは少し複雑になる可能性がありますが、パフォーマンスが重要な状況では実際の問題を確認したら、そのようにリファクタリングして、問題が解決するかどうかを確認します。いいえ、使用foo.prototypeしても実際には役に立ちません(1つには、barプライベートではなくなります)。
TJ Crowder 2012

@ahmoo:テストケースを追加しました。興味深いことに、Guffaのテストケースとは異なる結果が得られます。彼の関数は、少し単純すぎるかもしれません。しかし、私はまだパフォーマンスが問題になる必要はないと思います。
TJ Crowder 2012

15

これがクロージャの目的です。

var foo = (function () {
  function bar() {
    // perform task 3
  };

  function innerfoo (a) { 
    if (/* some cond */ ) {
      // perform task 1
      bar();
    }
    else {
      // perform task 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo(クロージャ)はbarへの参照を保持し、innerfooへの参照のみが、クロージャを作成するために1回だけ呼び出される無名関数から返されます。

この方法でバーに外部からアクセスすることはできません。


1
面白い。私はJavaScriptへの露出が限られているので、クロージャは私にとって新しいものです。しかし、あなたは私が閉鎖を研究するための出発点をマークしました。ありがとう。
tamakisquare 2012

変数/関数スコープを処理するためにクロージャをどのくらいの頻度で使用しますか?たとえば、同じ3つの変数にアクセスする必要がある2つの関数がある場合、2つの関数とともにクロージャで3つの変数を宣言してから、2つの関数を返しますか?
doubleOrt

8
var foo = (function () {
    var bar = function () {
        // perform task 3
    }
    return function (a) {

        if (/*some condition*/) {
            // perform task 1
            bar();
        }
        else {
            // perform task 2
            bar();
        }
    };
}());

クロージャは包含のスコープを保持bar()し、自己実行無名関数から新しい関数を返すと、より目に見えるスコープがに設定されfoo()ます。匿名の自己実行関数は1回だけbar()実行foo()されるため、インスタンスは1つだけで、実行するたびにそれが使用されます。


面白い。その時、私は閉鎖を調べなければならない。ありがとう。
tamakisquare 2012

変数/関数スコープを処理するためにクロージャをどのくらいの頻度で使用しますか?たとえば、同じ3つの変数にアクセスする必要がある2つの関数がある場合、2つの関数とともにクロージャで3つの変数を宣言してから、2つの関数を返しますか?
doubleOrt

どのくらいの頻度でクロージャーを使用しますか?いつも。2つの関数に必要な3つの変数がある場合:1。(最良)3つの変数を両方の関数に渡します-関数は一度だけ定義できます。2.(良い)変数が両方の範囲外にある2つの関数を作成します。これは閉鎖です。(基本的にここでの答えです。)悲しいことに、関数は変数の新しい使用ごとに再定義されます。3.(悪い)関数を使用しないでください。両方のことを実行する1つの大きな長いメソッドがあるだけです。
robrich

5

はい、問題なく動作します。

内側の関数は、外側の関数に入るたびに再作成されるわけではありませんが、再割り当てされます。

このコードをテストする場合:

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

それが表示されます1212、ではありません1222


ご回答有難うございます。demo()毎回の再割り当てはtest()、パフォーマンスの問題と呼ばれるべきですか?それはの複雑さに依存しdemo()ますか?
tamakisquare 2012

1
パフォーマンステストを行いました:jsperf.com/inner-function-vs-global-function結論として、一般的にはパフォーマンスの問題ではありません(関数に入力したコードは、関数を作成するよりも実行に時間がかかるため)それ自体)が、その追加のパフォーマンスエッジが必要な場合は、ブラウザごとに異なるコードを作成する必要があります。
guffa 2012

テストの作成に時間を費やし、パフォーマンスに関するポイントを共有してくれたThx。大変感謝いたします。
tamakisquare 2012

「内なる機能は毎回再現されない」と自信を持っておっしゃっていますね。スペックによると、そうです。エンジンが最適化するかどうかは、エンジンによって異なります。(私はほとんどの意志を期待しています。)あなたのテストケースと私のテストケースがそのようにさまざまな結果をもたらすのを見て興味をそそられます:jsperf.com/cost-of-creating-inner-functionパフォーマンスが問題だとは思いませんが。
TJ Crowder 2012

@TJCrowder:はい、それは実装の詳細ですが、最近のJavascriptエンジンはコードをコンパイルするため、割り当てられるたびに関数を再コンパイルすることはありません。パフォーマンステストの結果が異なる理由は、テストが異なるためです。私のテストではグローバル関数とローカル関数を比較し、テストではローカル関数とインラインコードを比較します。もちろん、コードのインライン化は関数を呼び出すよりも高速です。これは一般的な最適化手法です。
guffa 2012

0

ネストされたテストケースとネストされていない関数、および関数式と関数宣言をテストするためにjsperfを作成しましたが、ネストされたテストケースがネストされていないテストケースよりも20倍高速に実行されたことに驚きました。(私は反対または無視できる違いを予想しました)。

https://jsperf.com/nested-functions-vs-not-nested-2/1

これはChrome76、macOSにあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.