web-dev-qa-db-ja.com

コールバック関数/バインディングにスコープを渡す

関数のスコープをコールバックメソッドに渡そうとしています。私が抱えている問題は、オブジェクトスコープを取得しているため、元の関数のパラメーターとローカル変数にアクセスできないことです。 「これ」についての私の理解は、現在のコンテキスト(ウィンドウであろうと、何らかのオブジェクトであろうと)を意味しますに加えてローカルに宣言された変数とパラメーター。 [リチャードコーンフォードの優れた業績を http://jibbering.com/faq/notes/closures/ の「実行コンテキスト」セクションに引用してください]。また、JavaScriptの変数には関数スコープがあることも理解しています(それらが関数内で宣言されている場合、その関数内からのみアクセスできます)。

その理解を得て、新しい環境では、以前の雇用主のために私が多く行ったパターンをコーディングしようとしています。非同期メソッドを呼び出し、コールバックハンドラーを指定し、現在のスコープを渡します。コールバックメソッドで使用できることを期待しています。 。私の現在の環境では、これは当てはまりません。 (開示:私は以前の環境でExtJSを使用していました...何が起こっているのかを想定して、フレームワークを使用すると少し居心地がよくなりすぎたように感じます)。

私の簡単なテストコードは、私が何をしようとしているか、何がうまくいかないかを示しています。

function myHandler(data, ctx) {
    console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
    console.log(JSON.stringify(data));
}
MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

lookup();

ここでの問題は、この場合、MySvcWrap.doWorkに渡される「this」がWindowグローバルオブジェクトであることです。私の意図は、関数の実行コンテキストをmyHandlerに渡すことです。

私が試したこと。 「これ」の代わりに、私がオブジェクトを渡す場合、それは機能します、例えば:

function myHandler(data, ctx) {
    console.log('myHandler():  this.bar: ' + this.bar);  // <- no prob: this.bar 
    console.log(JSON.stringify(data));
}

function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, {bar: bar}); // scope object is just object
}

私は頭の中で私を団結させる誰かが必要です...最初のケースで 'this'を渡すとき、もちろんこれはグローバルスコープです(私はグローバルに定義された関数にいます)...私の問題は、渡すときに私が考えたことです。そのコンテキストでローカルに定義された変数とパラメーターにアクセスできたスコープ... JSの以前の理解を揺るがしていますか?このタスクを実行する方法は?

16
Merl

ところで、コード内のスコープに関するいくつかの単語:

function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, this); // this here points to window object and not to the function scope
}

だからそれは書くことと同じです:

function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler, window); 
}

これは、ルックアップ関数をグローバルに定義するためです。 doWork関数内で、thisを記述すると、MySvcWrapオブジェクトを指します(その関数はそのオブジェクト内で定義されているため)。

コールバック関数がbar変数を参照する必要がある場合は、同じスコープで定義する必要があります。

MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', 
        function (data, ctx) {
            console.log('myHandler():  bar: ' + bar); 
            console.log(JSON.stringify(data));
        }, 
        this); // scope object is this
}

lookup();

この場合、匿名関数をコールバックとして送信すると、それはルックアップ関数内で定義されるため、ローカル変数にアクセスできます。私のコンソールはこのcaeで私を示しています:

myHandler(): bar: food
{"colors":["red","green"],"name":"Jones","what":"thang"}

サポートを簡単にするために、検索関数内でmyHandlerを定義できます。

function lookup() {
    var bar = 'food'; // local var
    var myHandler = function(data, ctx) {
        console.log('myHandler():  bar: ' + bar);
        console.log(JSON.stringify(data));
    };
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

一方、ある関数が別の関数のローカル変数にアクセスする必要があるのはなぜですか?多分それは再設計することができます...

コードを機能させるもう1つの方法は、ルックアップの代わりに無名関数を使用することです(その関数を宣言して1回実行するだけで機能します)。

(function() {
   var bar = 'food';

   function myHandler(data, ctx) {
       console.log('myHandler():  bar: ' + bar);  
       console.log(JSON.stringify(data));
    } 
    MySvcWrap = {
       doWork: function(p1, callback, scope) {
           var result = {colors: ['red', 'green'], name:'Jones', what: p1};
           if (callback) {
               callback.call(scope||this,result, scope);
           } 
       }
    }
    MySvcWrap.doWork('thang', myHandler, this);
  }
)();

結果は同じですが、ルックアップ関数はもうありません...

それを機能させるためのもう1つのアイデア...実際には、bar変数が定義されているのと同じスコープでコールバックハンドラーを定義する必要があります。

function myHandler(bar) { // actually in this case better to call it createHandler 
    return function(data, ctx) {
        console.log('myHandler():  bar: ' + bar); 
        console.log(JSON.stringify(data));
    } 
}
MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler(bar), this); // scope object is this
}

また、JavaScriptのスコープとクロージャーについて読むためのいくつかのリソース:

  1. JavaScriptのスコープとクロージャーの説明
  2. Javascriptの取得-クロージャと字句スコープ
  3. JavaScript:高度なスコーピングとその他のパズル -トピックに関する非常に素晴らしいプレゼンテーション
16
Maxym

このように構成されていれば、コードは機能します。

MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback.call(scope||this,result, scope);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var

    function myHandler(data, ctx) {
        console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
        console.log(JSON.stringify(data));
    }

    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

lookup();

MyHandler関数は、それによって囲まれているため、クロージャであるルックアップのローカル変数にアクセスできます。

私はおそらく、このようなコードを構造化することにより、同じ結果を達成しようとしています。

function myHandler(data, bar, ctx) {
    console.log('myHandler():  bar: ' + bar);  // <- prob: bar undefined 
    console.log(JSON.stringify(data));
}
MySvcWrap = {
    doWork: function(p1, callback) {
        var result = {colors: ['red', 'green'], name:'Jones', what: p1};
        if (callback) {
            callback(result);
        } 
    }
}
function lookup() {
    var bar = 'food'; // local var
    MySvcWrap.doWork('thang', myHandler.bind(this, bar)); // callback function is bound to the scope
}

lookup();

スコープを渡すのではなく、ルックアップメソッド内でバインドを使用します。 bindを使用して、そのスコープから渡したいローカル変数を追加することもできます。

もちろん、古いブラウザではbindを使用できないため、フレームワークを介してバインドを追加するか、メソッドが存在しない場合はメソッドを追加する多数の小さなコードスニペットのいずれかを使用する必要があります。

2
leebriggs

最初の答えは少し早かったので、以下のコメントでMaxymが述べているように、ギャップの全体を残しました。私に知らせてくれてありがとう:) :P

私が最初の答えで得ようとしていたのは、バインディングを使用して、関数lookupから関数myHandlerの変数にアクセスする場合です(lookupのスコープ外です) )代わりにvarではなくthisを使用する必要があります。

varを使用すると、lookup'sプライベートスコープとそれにネストされた関数にのみアクセスできるようになります。これは、他の回答が示した方法です(そしておそらく最良のルートです)。

以下は、ルックアップのスコープをmyHandlerに渡し、myHandlerをルックアップのスコープから完全に外す方法の更新された例です。うまくいけば、これは少し光を当てるのに役立ちます:

「私が抱えている問題は、オブジェクトスコープを取得しているため、元の関数のパラメーターとローカル変数にアクセスできないことです。」

以前の回答に関するコメントで述べたように、thisはトリッキーになる可能性があり、最初の例で行ったように注意しなければ、globalスコープに何かを追加してしまう可能性があります。 :(それで、デモ目的でthiswindowでないことを確認しているスコープを確認するために、少しハックっぽいチェックを追加しました...

実例

function myHandler(data, ctx) {
    console.log('myHandler():  bar: ' + this.bar); // <- must use `this`
    console.log('myHandler():  privateBar: ' + this.privateBar); // <- undefined because it's privately scoped
    console.log(JSON.stringify(data));
}
MySvcWrap = {
    doWork: function(p1, callback, scope) {
        var result = {
            colors: ['red', 'green'],
            name: 'Jones',
            what: p1
        };
        if (callback) {
            callback.call(scope || this, result, scope);
        }
    }
}

function lookup() {
    if(this == window) return lookup.call(lookup); // <- my hack'ish check

    var privateBar = 'private food'; // private local var
    this.bar = 'food'; // public local var
    MySvcWrap.doWork('thang', myHandler, this); // scope object is this
}

lookup();

console.log('global space - bar: ' + this.bar);
0
subhaze