Wie funktioniert "dieses" Schlüsselwort innerhalb einer Funktion?

248

Ich bin gerade auf eine interessante Situation in JavaScript gestoßen. Ich habe eine Klasse mit einer Methode, die mehrere Objekte in Objekt-Literal-Notation definiert. In diesen Objekten wird der thisZeiger verwendet. Aus dem Verhalten des Programms habe ich abgeleitet, dass sich der thisZeiger auf die Klasse bezieht, für die die Methode aufgerufen wurde, und nicht auf das Objekt, das vom Literal erstellt wird.

Dies scheint willkürlich, obwohl ich erwarten würde, dass es so funktioniert. Ist das definiertes Verhalten? Ist es browserübergreifend sicher? Gibt es eine Begründung dafür, warum es so ist, wie es jenseits der "Spezifikation sagt" (zum Beispiel ist es eine Folge einer umfassenderen Designentscheidung / -philosophie)? Beispiel für einen reduzierten Code:

// 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
}
rmeador
quelle
es passiert, wenn ich das auch mache var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons
Schauen Sie sich diesen Beitrag an . Enthält einige gute Erklärungen zu verschiedenen Verwendungszwecken und Verhaltensweisen dieses Schlüsselworts.
Liebe Hasija

Antworten:

558

Schlachtete aus einem anderen Beitrag von mir, hier ist mehr , als Sie jemals darüber wissen wollten dies .

Bevor ich anfange, ist hier das Wichtigste, was Sie über Javascript wissen und sich wiederholen sollten, wenn es keinen Sinn ergibt. Javascript hat keine Klassen (ES6 classist syntaktischer Zucker ). Wenn etwas wie eine Klasse aussieht, ist es ein kluger Trick. Javascript hat Objekte und Funktionen . (Das ist nicht 100% genau, Funktionen sind nur Objekte, aber es kann manchmal hilfreich sein, sie als separate Dinge zu betrachten.)

Die Variable this ist an Funktionen angehängt. Jedes Mal , wenn Sie eine Funktion aufrufen, dies wird einen bestimmten Wert gegeben, je nachdem , wie Sie rufen die Funktion. Dies wird oft als Aufrufmuster bezeichnet.

Es gibt vier Möglichkeiten, Funktionen in Javascript aufzurufen. Sie können die Funktion als Methode , als Funktion , als Konstruktor und mit apply aufrufen .

Als Methode

Eine Methode ist eine Funktion, die an ein Objekt angehängt ist

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

Wenn sie als eine Methode aufgerufen, dies wird die Funktion / Methode an das Objekt gebunden wird , ist ein Teil der. In diesem Beispiel ist dies an foo gebunden.

Als eine Funktion

Wenn Sie eine eigenständige Funktion haben, wird diese Variable an das "globale" Objekt gebunden, fast immer an das Fensterobjekt im Kontext eines Browsers.

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

Das mag dich stolpern lassen , aber fühle dich nicht schlecht. Viele Leute halten dies für eine schlechte Designentscheidung. Da ein Rückruf als Funktion und nicht als Methode aufgerufen wird, sehen Sie ein scheinbar inkonsistentes Verhalten.

Viele Leute umgehen das Problem, indem sie so etwas tun

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

Sie definieren eine Variable , dass der zu den Punkten dieses . Closure (ein Thema , all seine eigene) hält diese um, wenn Sie also bar als Rückruf nennen, es hat immer noch einen Verweis.

HINWEIS: use strictWenn der Modus als Funktion verwendet wird, thisist er nicht an global gebunden. (Es ist undefined).

Als Konstrukteur

Sie können eine Funktion auch als Konstruktor aufrufen. Basierend auf der von Ihnen verwendeten Namenskonvention (TestObject) kann dies auch das sein, was Sie tun und was Sie auslöst .

Sie rufen eine Funktion als Konstruktor mit dem neuen Schlüsselwort auf.

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

Wenn als Konstruktor aufgerufen wird , wird ein neues Objekt erstellt werden, und dies wird auf das Objekt gebunden werden. Wenn Sie innere Funktionen haben und diese als Rückrufe verwendet werden, rufen Sie sie als Funktionen auf, und dies wird an das globale Objekt gebunden. Verwenden Sie diese var that = diesen Trick / dieses Muster.

Einige Leute denken, dass der Konstruktor / das neue Schlüsselwort ein Knochen war, der Java / traditionellen OOP-Programmierern als eine Möglichkeit zum Erstellen von Klassen ähnlichem geworfen wurde.

Mit der Apply-Methode

Schließlich hat jede Funktion eine Methode (ja, Funktionen sind Objekte in Javascript) mit dem Namen "apply". Bewerben können Sie bestimmen , was der Wert dieser sein wird, und lässt Sie auch in einer Reihe von Argumenten übergeben. Hier ist ein nutzloses Beispiel.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
Alan Storm
quelle
8
Hinweis: In Strict - Modus , thiswird undefinedfür Funktionsaufrufe.
Miscreant
1
Eine Funktionsdeklaration, z. Die Funktion myfunction () {} ist ein Sonderfall von "als Methode", wobei "dies" der globale Bereich (Fenster) ist.
Richard
1
@richard: Außer im strengen Modus und thishat nichts mit Umfang zu tun. Du meinst das globale Objekt .
TJ Crowder
@ Alan-Storm. Im Fall von "Als Konstruktor" ist this.confusing = 'hell yeah';das gleiche wie var confusing = 'hell yeah';? Also werden beide erlauben myObject.confusing? Es wäre schön, wenn nicht nur, damit Sie thisdie Eigenschaften und andere Variablen für die interne Arbeit erstellen können .
wunth
Aber function Foo(thought){ this.confusing = thought; }var myObject = new Foo("hell yeah");
andererseits
35

Funktionsaufrufe

Funktionen sind nur eine Art Objekt.

Alle Funktionsobjekte verfügen über Methoden zum Aufrufen und Anwenden , mit denen das aufgerufene Funktionsobjekt ausgeführt wird.

Beim Aufruf gibt das erste Argument für diese Methoden das Objekt an, auf das das thisSchlüsselwort während der Ausführung der Funktion verweist - wenn es nulloder undefineddas globale Objekt windowfür verwendet wird this.

Aufrufen einer Funktion ...

whereAmI = "window";

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

... mit Klammern - foo()- entspricht foo.call(undefined)oder foo.apply(undefined), was praktisch dasselbe ist wie foo.call(window)oder foo.apply(window).

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

Zusätzliche Argumente, calldie als Argumente an den Funktionsaufruf übergeben werden sollen, während ein einzelnes zusätzliches Argument applydie Argumente für den Funktionsaufruf als Array-ähnliches Objekt angeben kann.

Somit foo(1, 2, 3)ist äquivalent zu foo.call(null, 1, 2, 3)oder 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"

Wenn eine Funktion eine Eigenschaft eines Objekts ist ...

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

... der Zugriff auf einen Verweis auf die Funktion über das Objekt und der Aufruf mit Klammern - obj.foo()- entspricht foo.call(obj)oder foo.apply(obj).

Funktionen, die als Eigenschaften von Objekten gehalten werden, sind jedoch nicht an diese Objekte "gebunden". Wie Sie in der objobigen Definition sehen können , können Funktionen, da sie nur eine Art von Objekt sind, referenziert werden (und somit als Referenz auf einen Funktionsaufruf übergeben oder als Referenz von einem Funktionsaufruf zurückgegeben werden). Wenn ein Verweis auf eine Funktion übergeben wird, werden keine zusätzlichen Informationen darüber mitgeführt, woher er übergeben wurde. Aus diesem Grund geschieht Folgendes:

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

Der Aufruf unserer Funktionsreferenz bazbietet keinen Kontext für den Aufruf, ist also praktisch derselbe wie baz.call(undefined), führt also zu thiseiner Referenzierung window. Wenn wir bazwissen wollen , dass es dazu gehört, objmüssen wir diese Informationen irgendwie bereitstellen, wenn sie bazaufgerufen werden. Hier kommt das erste Argument für calloder applyund Schließungen ins Spiel.

Zielfernrohrketten

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

Wenn eine Funktion ausgeführt wird, erstellt sie einen neuen Bereich und verweist auf einen beliebigen umschließenden Bereich. Wenn die anonyme Funktion im obigen Beispiel erstellt wird, verweist sie auf den Bereich, in dem sie erstellt wurde. Dies ist bindder Bereich. Dies ist als "Verschluss" bekannt.

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

Wenn Sie versuchen, auf eine Variable zuzugreifen, wird diese "Bereichskette" durchlaufen, um eine Variable mit dem angegebenen Namen zu finden. Wenn der aktuelle Bereich die Variable nicht enthält, sehen Sie sich den nächsten Bereich in der Kette an und so weiter, bis Sie ihn erreichen der globale Umfang. Wenn die anonyme Funktion zurückgegeben wird und die bindAusführung abgeschlossen ist, hat die anonyme Funktion immer noch einen Verweis auf bindden Gültigkeitsbereich, sodass bindder Gültigkeitsbereich nicht "verschwindet".

Angesichts all der oben genannten Punkte sollten Sie nun in der Lage sein zu verstehen, wie der Gültigkeitsbereich im folgenden Beispiel funktioniert und warum die Technik zum Übergeben einer Funktion um "vorgebunden" mit einem bestimmten Wert thisfunktioniert, wenn sie aufgerufen wird:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
Jonny Buchanan
quelle
"Wenn ein Verweis auf eine Funktion übergeben wird, werden keine zusätzlichen Informationen darüber mitgeführt, woher er übergeben wurde", danke @insin dafür.
Alex Marandon
9

Ist das definiertes Verhalten? Ist es browserübergreifend sicher?

Ja. Und ja.

Gibt es Gründe dafür, warum es so ist, wie es ist ...

Die Bedeutung von thisist ziemlich einfach abzuleiten:

  1. Wenn thises in einer Konstruktorfunktion verwendet wird und die Funktion mit dem newSchlüsselwort aufgerufen wurde , thisbezieht es sich auf das Objekt, das erstellt wird. thiswird weiterhin das Objekt auch in öffentlichen Methoden bedeuten.
  2. Wenn thises an einer anderen Stelle verwendet wird, einschließlich verschachtelter geschützter Funktionen, bezieht es sich auf den globalen Bereich (der im Fall des Browsers das Fensterobjekt ist).

Der zweite Fall ist offensichtlich ein Konstruktionsfehler, aber es ist ziemlich einfach, ihn mithilfe von Verschlüssen zu umgehen.

Rakesh Pai
quelle
4

In diesem Fall ist das Innere thisan das globale Objekt gebunden und nicht an die thisVariable der äußeren Funktion. So ist die Sprache gestaltet.

Eine gute Erklärung finden Sie unter "JavaScript: The Good Parts" von Douglas Crockford.

Santiago Cepas
quelle
4

Ich habe ein nettes Tutorial zum ECMAScript gefunden

Ein dieser Wert ist ein spezielles Objekt, das mit dem Ausführungskontext zusammenhängt. Daher kann es als Kontextobjekt benannt werden (dh als Objekt, in dessen Kontext der Ausführungskontext aktiviert ist).

Jedes Objekt kann als dieser Wert des Kontexts verwendet werden.

a Dieser Wert ist eine Eigenschaft des Ausführungskontexts, jedoch keine Eigenschaft des variablen Objekts.

Diese Funktion ist sehr wichtig, da dieser Wert im Gegensatz zu Variablen niemals am Prozess der Auflösung von Bezeichnern teilnimmt. Das heißt, wenn in einem Code darauf zugegriffen wird, wird sein Wert direkt aus dem Ausführungskontext und ohne Suche nach Bereichsketten entnommen. Der Wert davon wird nur einmal beim Betreten des Kontexts bestimmt.

Im globalen Kontext ist ein dieser Wert das globale Objekt selbst (dh dieser Wert entspricht hier einem variablen Objekt).

Im Falle eines Funktionskontexts kann dieser Wert in jedem einzelnen Funktionsaufruf unterschiedlich sein

Referenz Javascript-the-Core und Kapitel-3-this

Damodaran
quelle
" Im globalen Kontext ist ein dieser Wert das globale Objekt selbst (das heißt, dieser Wert entspricht hier einem variablen Objekt). " Das globale Objekt ist Teil des globalen Ausführungskontexts, ebenso wie der (es4) "variable Objekt" und der ES5-Umgebungsdatensatz. Sie unterscheiden sich jedoch vom globalen Objekt (z. B. kann auf einen Umgebungsdatensatz nicht direkt verwiesen werden, dies ist in der Spezifikation verboten, das globale Objekt jedoch).
RobG