web-dev-qa-db-ja.com

ネストされた関数内のJavascript「this」ポインター

ネストされた関数のシナリオで「this」ポインターがどのように扱われるかについて質問があります。

次のサンプルコードをWebページに挿入するとします。ネストされた関数「doSomeEffects()」を呼び出すとエラーが発生します。 Firebugをチェックしたところ、ネストされた関数にいるとき、「this」ポインターが実際にはグローバルな「window」オブジェクトを指していることが示されました。オブジェクトの関数内でネストされた関数を宣言したため、関数に関連する「ローカル」スコープを持つ必要があると考えたため、何かを正しく理解してはなりません(つまり、「this」ポインターはオブジェクト自体を参照します)最初の「if」ステートメントにどのように含まれているか)。

どんなポインタ(しゃれも意図されていない)は高く評価されるでしょう。

var std_obj = {
  options : { rows: 0, cols: 0 },
  activeEffect : "none",
  displayMe : function() {

    // the 'this' pointer is referring to the std_obj
    if (this.activeEffect=="fade") { }

    var doSomeEffects = function() {

      // the 'this' pointer is referring to the window obj, why?
      if (this.activeEffect=="fade") { }

    }

    doSomeEffects();   
  }
};

std_obj.displayMe();
80
JoJoeDad

JavaScriptでは、thisオブジェクトは実際に関数呼び出しを行う方法に基づいています。

一般に、thisオブジェクトをセットアップするには3つの方法があります。

  1. someThing.someFunction(arg1, arg2, argN)
  2. someFunction.call(someThing, arg1, arg2, argN)
  3. someFunction.apply(someThing, [arg1, arg2, argN])

上記のすべての例で、thisオブジェクトはsomeThingになります。通常、先頭の親オブジェクトなしで関数を呼び出すと、ほとんどのブラウザでwindowオブジェクトを意味するglobalオブジェクトが取得されます。

111
KylePDavis

thisはクロージャースコープの一部ではなく、呼び出しサイトでバインドされている関数への追加パラメーターと考えることができます。メソッドがメソッドとして呼び出されない場合、グローバルオブジェクトはthisとして渡されます。ブラウザでは、グローバルオブジェクトはwindowと同じです。たとえば、次の機能を考えます。

function someFunction() {
}

そして次のオブジェクト、

var obj = { someFunction: someFunction };

次のようなメソッド構文を使用して関数を呼び出す場合、

obj.someFunciton();

thisobjにバインドされます。

次のようにsomeFunction()を直接呼び出す場合、

someFunction();

thisがグローバルオブジェクト、つまりwindowにバインドされます。

最も一般的な回避策は、これを次のようにクロージャに取り込むことです。

displayMe : function() {      

    // the 'this' pointer is referring to the std_obj      
    if (this.activeEffect=="fade") { }      
    var that = this;  
    var doSomeEffects = function() {      

      // the 'this' pointer is referring to global
      // that, however, refers to the outscope this
      if (that.activeEffect=="fade") { }      
    }      

    doSomeEffects();         
 }      
31
chuckj

これは、この種の最も支持の高い質問の1つであるように思われるため、これらすべての年の後に、矢印関数を使用したES6ソリューションを追加してみましょう。

var std_obj = {
  ...
  displayMe() {
    ...
    var doSomeEffects = () => {
                        ^^^^^^^    ARROW FUNCTION    
      // In an arrow function, the 'this' pointer is interpreted lexically,
      // so it will refer to the object as desired.
      if (this.activeEffect=="fade") { }
    };
    ...    
  }
};
30
user663031

エンクロージャー変数と「this」には違いがあります。 「this」は実際には関数の呼び出し側によって定義されますが、明示的な変数はEnclosureと呼ばれる関数宣言ブロック内にそのまま残ります。以下の例を参照してください。

function myFirstObject(){
    var _this = this;
    this.name = "myFirstObject";
    this.getName = function(){
       console.log("_this.name = " + _this.name + " this.name = " + this.name);  
    }
}

function mySecondObject(){
    var _this = this;
    this.name = "mySecondObject";
    var firstObject = new myFirstObject();
    this.getName = firstObject.getName
}

var secondObject = new mySecondObject();
secondObject.getName();

あなたはここでそれを試すことができます: http://jsfiddle.net/kSTBy/

あなたの関数で起こっていることは「doSomeEffects()」であり、明示的に呼び出されています。これはコンテキストまたは関数の「this」がウィンドウであることを意味します。 「doSomeEffects」がプロトタイプメソッドの場合this.doSomeEffectsが「myObject」と言うと、myObject.doSomeEffects()は「this」を「myObject」にします。

10
Shane

この質問を理解するには、次のスニペットの出力を取得してください

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

上記のコードは、コンソールに次を出力します。

outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

外部関数では、thisとselfの両方がmyObjectを参照するため、どちらもfooを適切に参照およびアクセスできます。

ただし、内部関数では、これはmyObjectを参照しなくなりました。結果として、this.fooは内部関数では未定義ですが、ローカル変数selfへの参照はスコープ内に残り、そこでアクセスできます。 (ECMA 5より前では、これは内部関数でグローバルウィンドウオブジェクトを参照していましたが、ECMA 5では、内部関数でこれは未定義になります。)

6
ronakshah725

Kyleが説明したように、callまたはapplyを使用して、関数内でthisを指定できます。

コードに適用される概念は次のとおりです。

var std_obj = {
    options: {
        rows: 0,
        cols: 0
    },
    activeEffect: "none",
    displayMe: function() {

        // the 'this' pointer is referring to the std_obj
        if (this.activeEffect == "fade") {}

        var doSomeEffects = function() {
            // the 'this' pointer is referring to the window obj, why?
            if (this.activeEffect == "fade") {}
        }

        doSomeEffects.apply(this,[]);
    }
};

std_obj.displayMe();

JsFiddle

3

.bind()メソッドでも実行できます。

var std_obj = {
  options : { rows: 0, cols: 0 },
  activeEffect : "none", 

  displayMe : function() {

    // the 'this' pointer is referring to the std_obj
    if (this.activeEffect=="fade") { }

    var doSomeEffects = function() {

      // now 'this' pointer is referring to the std_obj when calling by bound function
      if (this.activeEffect=="fade") { }
          alert(this.activeEffect);
    }

    var newBoundFunction= doSomeEffects.bind(std_obj);
      newBoundFunction();   
  }
};

call()、bind()、apply()を使用して、実際にこの値を明示的に設定できます。 3つは非常に似ていますが、わずかな違いを理解することが重要です。

呼び出しと適用はそれぞれすぐに呼び出されます。呼び出しは任意の数のパラメーターを取ります:これに追加の引数が続きます。 Applyは2つのパラメーターのみを取ります:これに追加の引数の配列が続きます。

あなたはまだ私をフォローしていますか?例により、これを明確にする必要があります。以下のコードを見てください。数字を追加しようとしています。これをブラウザコンソールにコピーして、関数を呼び出します。

function add(c, d) {
  console.log(this.a + this.b + c + d);
}
add(3,4);
// logs => NaN

Add関数はNaN(数値ではない)をログに記録します。これはthis.aとthis.bが未定義だからです。それらは存在しません。また、未定義のものに番号を追加することはできません。

方程式にオブジェクトを導入しましょう。 call()およびapply()を使用して、オブジェクトで関数を呼び出すことができます。

function add(c, d) {
  console.log(this.a + this.b + c + d);
}
var ten = {a: 1, b: 2};
add.call(ten, 3, 4);
// logs => 10
add.apply(ten, [3,4]);
// logs => 10

Add.call()を使用する場合、最初のパラメーターはこれをバインドする必要があります。後続のパラメーターは、呼び出している関数に渡されます。したがって、add()では、this.aはten.aを参照し、this.bはten.bを参照し、1 + 2 + 3 + 4が返されます(10)。

add.apply()も同様です。最初のパラメーターは、これがバインドされるべきものです。後続のパラメーターは、関数で使用される引数の配列です。

バインドはどうですか? bind()のパラメーターはcall()と同じですが、bind()はすぐには呼び出されません。代わりに、bind()はすでにこのバインドのコンテキストを持つ関数を返します。このため、bind()は、引数のすべてが事前にわからない場合に役立ちます。繰り返しますが、例を理解するのに役立ちます。

var small = {
  a: 1,
  go: function(b,c,d){
    console.log(this.a+b+c+d);
  }
}
var large = {
  a: 100
}

上記をコンソールにコピーします。それから電話する

small.go(2,3,4);
// logs 1+2+3+4 => 10

クール。ここに新しいものはありません。しかし、代わりにlarge.aの値を使用したい場合はどうでしょうか? call/applyを使用できます。

small.go.call(large,2,3,4);
// logs 100+2+3+4 => 109

では、3つの引数すべてがまだわからない場合はどうでしょうか。バインドを使用できます:

var bindTest = small.go.bind(large,2);

上記の変数bindTestのconsole.logを実行すると、作業内容を確認できます

console.log(bindTest);
// logs => function (b,c,d){console.log(this.a+b+c+d);}

バインドでは、すでにこのバインドを持っている関数が返されることを忘れないでください!したがって、これは大きなオブジェクトに正常にバインドされました。また、2番目の引数には2という数字を既に渡しました。後で残りの引数がわかったら、それらを渡すことができます。

bindTest(3,4);
// logs 100+2+3+4 => 109

わかりやすくするために、ここではすべてのコードを1つのブロックにまとめています。よく見て、コンソールにコピーして、何が起こっているのかを本当に理解してください!

var small = {
  a: 1,
  go: function(b,c,d){
    console.log(this.a+b+c+d);
  }
}
var large = {
  a: 100
}
small.go(2,3,4);
// logs 1+2+3+4 => 10
var bindTest = small.go.bind(large,2);
console.log(bindTest);
// logs => function (b,c,d){console.log(this.a+b+c+d);}
bindTest(3,4);
// logs 100+2+3+4 => 109

いくつか覚えておいてください:

この値は通常、関数実行コンテキストによって決定されます。

グローバルスコープでは、これはグローバルオブジェクト(ウィンドウオブジェクト)を指します。

新しいキーワードが使用されると(コンストラクター)、これは作成される新しいオブジェクトにバインドされます。

Call()、bind()、apply()でこの値を明示的に設定できます。

Arrow Functionsはこれをバインドしません-代わりに、これは字句的にバインドされます(つまり、元のコンテキストに基づいて)

3