Hoe werkt “dit” zoekwoord binnen een functie?

Ik kwam zojuist een interessante situatie tegen in JavaScript. Ik heb een klasse met een methode die verschillende objecten definieert met behulp van object-letterlijke notatie. Binnen die objecten wordt de aanwijzer thisgebruikt. Uit het gedrag van het programma heb ik afgeleid dat de this-aanwijzer verwijst naar de klasse waarop de methode werd aangeroepen, en niet naar het object dat door de letterlijke waarde werd gemaakt.

Dit lijkt willekeurig, hoewel het de manier is waarop ik zou verwachten dat het zou werken. Is dit gedefinieerd gedrag? Is het cross-browser veilig? Is er een reden waarom het is zoals het is buiten “de specificatie zegt het” (is het bijvoorbeeld een gevolg van een bredere ontwerpbeslissing / filosofie)? Voorbeeld van een uitgeklede 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
}

Antwoord 1, autoriteit 100%

Gekannibaliseerd uit een andere post van mij, hier is meer dan je ooit wilde weten over dit.

Voordat ik begin, is dit het belangrijkste om in gedachten te houden over Javascript, en om tegen jezelf te herhalen als het niet logisch is. Javascript heeft geen klassen (ES6 classis syntactische suiker). Als iets op een klas lijkt, is het een slimme truc. Javascript heeft objectenen functies. (dat is niet 100% nauwkeurig, functies zijn slechts objecten, maar het kan soms handig zijn om ze als afzonderlijke dingen te zien)

De variabele dezeis gekoppeld aan functies. Telkens wanneer u een functie aanroept, krijgt dezeeen bepaalde waarde, afhankelijk van hoe u de functie aanroept. Dit wordt vaak het aanroeppatroon genoemd.

Er zijn vier manieren om functies in javascript aan te roepen. Je kunt de functie aanroepen als een methode, als een functie, als een constructor, en met apply.

Als een methode

Een methode is een functie die aan een object is gekoppeld

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

Wanneer aangeroepen als een methode, zal ditgebonden zijn aan het object waar de functie/methode deel van uitmaakt. In dit voorbeeld is dit gebonden aan foo.

Als een functie

Als je een zelfstandige functie hebt, is de variabele thisgebonden aan het “algemene” object, bijna altijd het window-object in de context van een browser.

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

Misschien is dit wat je overstuur maakt, maar voel je niet rot. Veel mensen beschouwen dit als een slechte ontwerpbeslissing. Omdat een callback wordt aangeroepen als een functie en niet als een methode, zie je daarom wat inconsistent lijkt te zijn.

Veel mensen omzeilen het probleem door iets te doen als, eh, dit

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

Je definieert een variabele datdie verwijst naar dit. Sluiting (een geheel eigen onderwerp) houdt datin stand, dus als je bar aanroept om terug te bellen, heeft het nog steeds een referentie.

OPMERKING: In de modus use strictindien gebruikt als functie, is thisniet gebonden aan globaal. (Het is undefined).

Als Constructeur

Je kunt een functie ook als constructor aanroepen. Op basis van de naamgevingsconventie die u gebruikt (TestObject) misschien is dit ook wat u doet en maakt u zich zorgen.

Je roept een functie aan als Constructor met het nieuwe trefwoord.

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

Als het wordt aangeroepen als een constructor, wordt een nieuw object gemaakt en ditwordt aan dat object gekoppeld. Nogmaals, als je innerlijke functies hebt en ze worden gebruikt als callbacks, dan zul je ze aanroepen als functies, en ditzal gebonden zijn aan het globale object. Gebruik die var that = deze truc/dit patroon.

Sommige mensen denken dat de constructor/new-sleutelwoord een bot was dat naar Java-/traditionele OOP-programmeurs werd gegooid als een manier om iets te maken dat lijkt op klassen.

Met de Toepassen-methode

Eindelijk heeft elke functie een methode (ja, functies zijn objecten in Javascript) met de naam “toepassen”. Met Toepassen kunt u bepalen wat de waarde van ditzal zijn, en kunt u ook een reeks argumenten doorgeven. Hier is een nutteloos voorbeeld.

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

Antwoord 2, autoriteit 6%

Functie-oproepen

Functies zijn slechts een soort object.

Alle Function-objecten hebben callen pasmethoden toe die het Function-object uitvoeren waarop ze worden aangeroepen.

Wanneer aangeroepen, specificeert het eerste argument van deze methoden het object waarnaar zal worden verwezen door het thissleutelwoord tijdens het uitvoeren van de functie – als het nullof undefined, het globale object, window, wordt gebruikt voor this.

Dus een functie aanroepen…

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

…met haakjes – foo()– is gelijk aan foo.call(undefined)of foo.apply(undefined), wat effectiefhetzelfde is als foo.call(window)of foo.apply(window).

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

Aanvullende argumenten voor callworden doorgegeven als de argumenten voor de functieaanroep, terwijl een enkel aanvullend argument voor applyde argumenten voor de functieaanroep kan specificeren als een Array- zoals object.

Dus foo(1, 2, 3)is gelijk aan foo.call(null, 1, 2, 3)of 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"

Als een functie een eigenschap is van een object…

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

…een verwijzing naar de functie openen via het object en het aanroepen tussen haakjes – obj.foo()– is gelijk aan foo.call(obj)of foo.apply(obj).

Functies die als eigenschappen van objecten worden vastgehouden, zijn echter niet “gebonden” aan die objecten. Zoals je kunt zien in de definitie van objhierboven, aangezien functies slechts een type object zijn, kan er naar worden verwezen (en kunnen dus worden doorgegeven door te verwijzen naar een functieaanroep of worden geretourneerd door te verwijzen vanuit een functie telefoongesprek). Wanneer een verwijzing naar een functie wordt doorgegeven, wordt er geen aanvullende informatie over waar deze vanvandaan is doorgegeven, en daarom gebeurt het volgende:

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

De aanroep van onze functiereferentie, baz, biedt geen context voor de aanroep, dus het is in feite hetzelfde als baz.call(undefined), dus thisverwijst uiteindelijk naar window. Als we willen dat bazweet dat het bij objhoort, moeten we die informatie op de een of andere manier verstrekken wanneer bazwordt aangeroepen, waar de eerste argument om callof applyte gebruiken en afsluitingen spelen een rol.

Scope-ketens

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

Als een functie wordt uitgevoerd, wordt er een nieuw bereik gemaakt en wordt er naar een omsluitend bereik verwezen. Wanneer de anonieme functie in het bovenstaande voorbeeld is gemaakt, heeft deze een verwijzing naar het bereik waarin het is gemaakt, namelijk het bereik van bind. Dit staat bekend als een ‘sluiting’.

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

Wanneer u probeert toegang te krijgen tot een variabele, wordt deze “scope chain” gelopen om een ​​variabele met de opgegeven naam te vinden – als de huidige scope de variabele niet bevat, kijkt u naar de volgende scope in de keten, enzovoort totdat u het globale bereik bereikt. Wanneer de anonieme functie wordt geretourneerd en bindklaar is met uitvoeren, heeft de anonieme functie nog steeds een verwijzing naar het bereik van bind, dus het bereik van bind“gaat niet weg”.

Gezien al het bovenstaande zou je nu in staat moeten zijn om te begrijpen hoe scope werkt in het volgende voorbeeld, en waarom de techniek voor het doorgeven van een functie rond “pre-bound” met een bepaalde waarde van thisit zal hebben als het werken wordt genoemd:

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

Antwoord 3, autoriteit 2%

Is dit gedefinieerd gedrag? Is het
cross-browser veilig?

Ja. En ja.

Is er een reden waarom?
het is zoals het is…

De betekenis van thisis vrij eenvoudig af te leiden:

  1. Als thiswordt gebruikt in een constructorfunctie en de functie is aangeroepen met het trefwoord new, verwijst thisnaar het object dat worden gemaakt. thisblijft het object betekenen, zelfs in openbare methoden.
  2. Als thisergens anders wordt gebruikt, inclusief geneste beschermdefuncties, verwijst het naar het globale bereik (wat in het geval van de browser het vensterobject is).

Het tweede geval is duidelijk een ontwerpfout, maar het is vrij eenvoudig om dit te omzeilen door sluitingen te gebruiken.


Antwoord 4

In dit geval is de binnenste thisgebonden aan het globale object in plaats van aan de variabele thisvan de buitenste functie.
Het is de manier waarop de taal is ontworpen.

Zie “JavaScript: The Good Parts” door Douglas Crockford voor een goede uitleg.


Antwoord 5

Ik heb een mooie tutorial gevonden over het ECMAScript dit

A deze waarde is een speciaal object dat gerelateerd is aan de uitvoering
context. Daarom kan het worden genoemd als een contextobject (d.w.z. an
object waarin de uitvoeringscontext is geactiveerd).

Elk object kan worden gebruikt als deze waarde van de context.

a deze waarde is een eigenschap van de uitvoeringscontext, maar niet a
eigenschap van het variabele object.

Deze functie is erg belangrijk, omdat deze waarde, in tegenstelling tot variabelen, nooit deelneemt aan het identificatieproces. D.w.z. bij toegang tot deze code in een code, wordt de waarde rechtstreeks uit de uitvoeringscontext gehaald en zonder enige zoekactie in de scopeketen. De waarde hiervan wordt slechts één keer bepaald bij het invoeren van de context.

In de globale context is a deze waarde het globale object zelf (dat betekent dat deze waarde hier gelijk is aan variabel object)

In het geval van een functiecontext kan deze waarde in elke afzonderlijke functieaanroep anders zijn

Referentie Javascript-the-coreen Hoofdstuk-3-this


Antwoord 6

Alle antwoorden hier zijn erg nuttig, maar ik had nog steeds moeite om erachter te komen waar thisin mijn geval op wees, namelijk het vernietigen van objecten. Dus ik zou nog een antwoord willen toevoegen met een vereenvoudigde versie van mijn code,

let testThis = {
  x: 12,
  y: 20,
  add({ a, b, c }) {
    let d = a + b + c()
    console.log(d)
  },
  test() {
    //the result is NaN
    this.add({
      a: this.x,
      b: this.y,
      c: () => {
        //this here is testThis, NOT the object literal here
        return this.a + this.b 
      },
    })
  },
  test2() {
    //64 as expected
    this.add({
      a: this.x,
      b: this.y,
      c: () => {
        return this.x + this.y
      },
    })
  },
  test3() {
    //NaN
    this.add({
      a: this.x,
      b: this.y,
      c: function () {
        //this here is the global object
        return this.x + this.y 
      },
    })
  },
}

Zoals hier uitgelegd Javascript – destructuring object – ‘this’ ingesteld op globaal of ongedefinieerd, in plaats van objectheeft het eigenlijk niets te maken met objectdestructurering, maar hoe c() wordt aangeroepen, maar het is niet gemakkelijk om hier doorheen te kijken.

MDNzegt ” pijlfunctie-uitdrukkingen zijn het meest geschikt voor niet-methodefuncties”, maar de pijlfunctie werkt hier.

Other episodes