web-dev-qa-db-ja.com

関数内で「this」キーワードはどのように機能しますか?

JavaScriptの興味深い状況に出会いました。オブジェクトリテラル表記を使用していくつかのオブジェクトを定義するメソッドを持つクラスがあります。これらのオブジェクト内では、thisポインターが使用されています。プログラムの動作から、thisポインターは、リテラルによって作成されているオブジェクトではなく、メソッドが呼び出されたクラスを参照していると推測しました。

これはarbitrary意的なように思えますが、それは私が期待する方法です。これは定義された動作ですか?クロスブラウザで安全ですか? 「仕様がそう言っている」以上の理由がある理由はありますか(たとえば、より広範な設計決定/哲学の結果)。簡略化されたコード例:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}
246
rmeador

私の別の投稿から食い物にされた、これはthisについて知りたいと思った以上のものです。

始める前に、Javascriptについて念頭に置いて、それが意味をなさないときに自分自身に繰り返すための最も重要なことを以下に示します。 Javascriptにはクラスがありません(ES6 class構文糖 です)。クラスのように見えるものは、巧妙なトリックです。 Javascriptにはobjectsおよびfunctionsがあります。 (それは100%正確ではありません。関数は単なるオブジェクトですが、それらを別々の物と考えると役立つ場合があります)

this変数は関数に付加されます。関数を呼び出すたびに、関数の呼び出し方法に応じて、thisに特定の値が与えられます。これは多くの場合、呼び出しパターンと呼ばれます。

JavaScriptで関数を呼び出すには4つの方法があります。 methodfunctionconstructor、およびapply

方法として

メソッドは、オブジェクトにアタッチされる関数です

var foo = {};
foo.someMethod = function(){
    alert(this);
}

メソッドとして呼び出されると、thisは、関数/メソッドが属するオブジェクトにバインドされます。この例では、これはfooにバインドされます。

機能として

スタンドアロン関数を使用している場合、this変数は「グローバル」オブジェクトにバインドされ、ほとんどの場合windowオブジェクトブラウザのコンテキストで。

 var foo = function(){
    alert(this);
 }
 foo();

これはあなたをつまずかせるものかもしれませんが、気分は悪くありません。多くの人は、これを悪い設計決定だと考えています。コールバックはメソッドとしてではなく関数として呼び出されるため、一貫性のない動作と思われるものが表示されるのはそのためです。

多くの人々は、このようなことをすることで問題を回避します

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

thisを指す変数thatを定義します。クロージャー(それ自体がすべてのトピック)はthatを保持するため、barをコールバックとして呼び出す場合、まだ参照があります。

注:use strictモードで関数として使用する場合、thisはグローバルにバインドされません。 (undefinedです)。

コンストラクターとして

関数をコンストラクターとして呼び出すこともできます。 (TestObject)を使用している命名規則に基づいて、これもあなたがしていることであり、あなたをつまずかせるものです

新しいキーワードを使用して、コンストラクターとして関数を呼び出します。

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

コンストラクターとして呼び出されると、新しいオブジェクトが作成され、thisがそのオブジェクトにバインドされます。繰り返しますが、内部関数があり、それらがコールバックとして使用されている場合、それらを関数として呼び出し、thisはグローバルオブジェクトにバインドされます。 var that = this trick/patternを使用します。

一部の人々は、コンストラクター/新しいキーワードは、クラスに似たものを作成する方法として、Java /伝統的なOOPプログラマーに投げかけられた骨だと考えています。

Applyメソッドを使用

最後に、すべての関数には「apply」という名前のメソッドがあります(はい、関数はJavascriptのオブジェクトです)。 Applyを使用すると、thisの値を決定することができ、引数の配列を渡すこともできます。これは役に立たない例です。

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
557
Alan Storm

関数呼び出し

関数はオブジェクトの一種です。

すべてのFunctionオブジェクトには、呼び出されるFunctionオブジェクトを実行する call および apply メソッドがあります。

呼び出されると、これらのメソッドの最初の引数は、関数の実行中にthisキーワードによって参照されるオブジェクトを指定します。nullまたはundefinedの場合、グローバルオブジェクトwindowthisに使用されます。

したがって、関数を呼び出す...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

...括弧付き-foo()-はfoo.call(undefined)またはfoo.apply(undefined)と同等で、実質的に同じfoo.call(window)またはfoo.apply(window)として。

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

callへの追加の引数は、関数呼び出しの引数として渡されますが、applyへの単一の追加の引数は、関数呼び出しの引数を配列のようなオブジェクトとして指定できます。

したがって、foo(1, 2, 3)foo.call(null, 1, 2, 3)またはfoo.apply(null, [1, 2, 3])と同等です。

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

関数がオブジェクトのプロパティである場合...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

...オブジェクトを介して関数への参照にアクセスし、括弧で呼び出す-obj.foo()-はfoo.call(obj)またはfoo.apply(obj)と同等です。

ただし、オブジェクトのプロパティとして保持される関数は、それらのオブジェクトに「バインド」されていません。上記のobjの定義を見るとわかるように、関数は単なるオブジェクトの一種であるため、参照することができます(したがって、関数呼び出しへの参照によって渡すか、関数呼び出しからの参照によって返すことができます)。関数への参照が渡されると、それが渡された場所に関する追加情報fromは運ばれません。そのため、次のことが起こります。

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

関数参照bazの呼び出しは、呼び出しのコンテキストを提供しません。したがって、baz.call(undefined)と実質的に同じであるため、thisは最終的にwindowを参照します。 bazobjに属していることを知りたい場合は、bazが呼び出されたときにその情報を提供する必要があります。callまたはapplyの最初の引数とクロージャーが機能します。

スコープチェーン

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

関数が実行されると、新しいスコープが作成され、外側のスコープへの参照があります。匿名関数が上記の例で作成されると、作成されたスコープへの参照があります。これはbindのスコープです。これは「クロージャ」として知られています。

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

変数にアクセスしようとすると、この「スコープチェーン」は、指定された名前の変数を見つけるためにウォークされます。現在のスコープに変数が含まれていない場合は、チェーン内の次のスコープを確認します。グローバルスコープ。無名関数が返され、bindの実行が終了しても、匿名関数はbindのスコープへの参照を保持しているため、bindのスコープは「消えません」。

上記のすべてを考えると、次の例でスコープがどのように機能するか、およびthisの特定の値を持つ「事前バインド」の周りに関数を渡すためのテクニックがなぜ呼び出されるかを理解できるはずです:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
35
Jonny Buchanan

これは定義された動作ですか?クロスブラウザで安全ですか?

はい。はい。

なぜそうなっているのかという根本的な根拠はありますか...

thisの意味は簡単に推測できます。

  1. thisがコンストラクター関数内で使用され、newキーワードで関数が呼び出された場合、thisは作成されるオブジェクトを指します。 thisは、パブリックメソッドであってもオブジェクトを意味し続けます。
  2. ネストされたprotected関数を含むthisが他の場所で使用される場合、グローバルスコープ(ブラウザの場合はウィンドウオブジェクト)を参照します。

2番目のケースは明らかに設計上の欠陥ですが、クロージャーを使用することで簡単に回避できます。

9
Rakesh Pai

この場合、内側のthisは、外側の関数のthis変数ではなく、グローバルオブジェクトにバインドされます。それが言語の設計方法です。

適切な説明については、Douglas Crockfordによる「JavaScript:The Good Parts」を参照してください。

4
Santiago Cepas

ECMAScript thisに関する素晴らしいチュートリアルを見つけました

This値は、実行コンテキストに関連する特別なオブジェクトです。したがって、コンテキストオブジェクト(つまり、実行コンテキストがアクティブ化されるコンテキスト)として名前を付けることができます。

コンテキストのこの値として、任意のオブジェクトを使用できます。

this値は実行コンテキストのプロパティですが、変数オブジェクトのプロパティではありません。

この機能は非常に重要です。変数とは異なり、この値は識別子解決プロセスに関与しないためです。つまりコードでこれにアクセスすると、その値は実行コンテキストから直接取得され、スコープチェーンルックアップは行われません。この値は、コンテキストに入るときに一度だけ確定します。

グローバルコンテキストでは、this値はグローバルオブジェクト自体です(つまり、この値は変数オブジェクトに等しい)

関数コンテキストの場合、すべての単一の関数呼び出しのこの値は異なる場合があります

参照 Javascript-the-core および Chapter-3-this

4
Damodaran