Domanda

Mi sono appena imbattuto in una situazione interessante in JavaScript. Ho una classe con un metodo che definisce diversi oggetti usando la notazione letterale oggetto. All'interno di tali oggetti, viene utilizzato il puntatore this . Dal comportamento del programma, ho dedotto che il puntatore this si riferisce alla classe su cui è stato invocato il metodo e non all'oggetto creato dal letterale.

Questo sembra arbitrario, sebbene sia il modo in cui mi aspetterei che funzioni. Questo comportamento è definito? È cross-browser sicuro? C'è qualche ragionamento alla base del perché sia ??così oltre "le specifiche lo dicono" (ad esempio, è una conseguenza di una decisione / filosofia progettuale più ampia)? Esempio di codice ridotto:

// 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
}
È stato utile?

Soluzione

Cannibalizzato da un altro mio post, ecco più di quanto tu abbia mai voluto sapere su questo .

Prima di iniziare, ecco la cosa più importante da tenere a mente su Javascript e da ripetere quando non ha senso. Javascript non ha classi (ES6 class è zucchero sintattico ). Se qualcosa sembra una classe, è un trucco intelligente. Javascript ha oggetti e funzioni . (non è preciso al 100%, le funzioni sono solo oggetti, ma a volte può essere utile pensarli come cose separate)

La questa variabile è collegata alle funzioni. Ogni volta che invochi una funzione, this viene dato un certo valore, a seconda di come invoca la funzione. Questo è spesso chiamato il modello di invocazione.

Esistono quattro modi per invocare funzioni in javascript. Puoi invocare la funzione come metodo , come funzione , come costruttore e con applica .

Come metodo

Un metodo è una funzione collegata a un oggetto

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

Se invocato come metodo, this sarà associato all'oggetto di cui fa parte la funzione / metodo. In questo esempio, questo sarà legato a pippo.

Come funzione

Se hai una funzione autonoma, la variabile questa sarà associata alla "quotazione globale" oggetto, quasi sempre l'oggetto finestra nel contesto di un browser.

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

Potrebbe essere questo a farti inciampare , ma non stare male. Molte persone considerano questa una cattiva decisione di progettazione. Poiché un callback viene invocato come funzione e non come metodo, ecco perché stai vedendo quello che sembra essere un comportamento incoerente.

Molte persone risolvono il problema facendo qualcosa del genere, um, questo

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

Definisci una variabile che che punta a questo . La chiusura (un argomento tutto suo) mantiene quello , quindi se chiami la barra come callback, ha ancora un riferimento.

NOTA: in usa la modalità rigorosa se usato come funzione, this non è vincolato al globale. (È undefined ).

Come costruttore

Puoi anche invocare una funzione come costruttore. In base alla convenzione di denominazione che stai utilizzando (TestObject), anche questo potrebbe essere quello che stai facendo ed è ciò che ti sta facendo inciampare .

Hai invocato una funzione come costruttore con la nuova parola chiave.

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

Quando viene invocato come costruttore, verrà creato un nuovo oggetto e questo verrà associato a quell'oggetto. Ancora una volta, se hai funzioni interne e sono usate come callback, le invocherai come funzioni e questo sarà associato all'oggetto globale. Usa quel var che = questo trucco / schema.

Alcune persone pensano che il costruttore / nuova parola chiave sia stato un osso lanciato ai programmatori OOP Java / tradizionali come un modo per creare qualcosa di simile alle classi.

Con il metodo Apply

Infine, ogni funzione ha un metodo (sì, le funzioni sono oggetti in Javascript) chiamato " apply " ;. Applica ti consente di determinare quale sarà il valore di questo e ti consente anche di passare una serie di argomenti. Ecco un esempio inutile.

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

Altri suggerimenti

Chiamate di funzione

Le funzioni sono solo un tipo di oggetto.

Tutti gli oggetti Function hanno call e apply che eseguono l'oggetto Function su cui sono chiamati.

Quando viene chiamato, il primo argomento di questi metodi specifica l'oggetto a cui farà riferimento la parola chiave this durante l'esecuzione della funzione - se è null o undefined , l'oggetto globale, window , viene utilizzato per this .

Quindi, chiamando una funzione ...

whereAmI = "window";

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

... tra parentesi - foo () - equivale a foo.call (non definito) o foo.apply (non definito) , che è effettivamente uguale a foo.call (finestra) o foo.apply (finestra) .

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

Argomenti aggiuntivi per chiamare vengono passati come argomenti alla chiamata di funzione, mentre un singolo argomento aggiuntivo per applicare può specificare gli argomenti per la chiamata di funzione come un array- come oggetto.

Pertanto, foo (1, 2, 3) è equivalente a foo.call (null, 1, 2, 3) o 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"

Se una funzione è una proprietà di un oggetto ...

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

... accedere a un riferimento alla Funzione tramite l'oggetto e chiamarlo tra parentesi - obj.foo () - equivale a foo.call (obj) o foo.apply (obj) .

Tuttavia, le funzioni mantenute come proprietà degli oggetti non sono "legate". a quegli oggetti. Come puoi vedere nella definizione di obj sopra, poiché le funzioni sono solo un tipo di oggetto, possono essere referenziate (e quindi possono essere passate come riferimento a una chiamata di funzione o restituite come riferimento da una funzione chiamata). Quando viene passato un riferimento a una funzione, non viene trasportata alcuna informazione aggiuntiva su dove è stata passata da , motivo per cui accade quanto segue:

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

La chiamata al nostro riferimento di funzione, baz , non fornisce alcun contesto per la chiamata, quindi è effettivamente uguale a baz.call (non definito) , quindi this finisce per fare riferimento a window . Se vogliamo che baz sappia che appartiene a obj , dobbiamo in qualche modo fornire tali informazioni quando viene chiamato baz , dove è il primo argomento per chiamare o applicare e le chiusure entrano in gioco.

Catene dell'ambito

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

Quando una funzione viene eseguita, crea un nuovo ambito e ha un riferimento a qualsiasi ambito racchiuso. Quando la funzione anonima viene creata nell'esempio sopra, ha un riferimento all'ambito in cui è stata creata, che è l'ambito di bind . Questo è noto come "chiusura."

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

Quando tenti di accedere a una variabile questa "catena di ambito" viene camminato per trovare una variabile con il nome dato - se l'ambito corrente non contiene la variabile, guardi l'ambito successivo nella catena e così via fino a raggiungere l'ambito globale. Quando viene restituita la funzione anonima e l'esecuzione di bind , la funzione anonima ha ancora un riferimento all'ambito di bind , quindi l'ambito di bind non " andare via " ;.

Alla luce di quanto sopra, ora dovresti essere in grado di capire come funziona l'ambito nell'esempio seguente e perché la tecnica per passare una funzione attorno a "pre-associato" con un valore particolare di this avrà quando viene chiamato funziona:

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

Questo comportamento definito? È   cross-browser sicuro?

Sì. E sì.

  

C'è qualche ragionamento alla base del perché   è come è ...

Il significato di this è piuttosto semplice da dedurre:

  1. Se this viene utilizzato all'interno di una funzione di costruzione e la funzione è stata invocata con la parola chiave new , this si riferisce all'oggetto che verrà essere creato. this continuerà a significare l'oggetto anche con metodi pubblici.
  2. Se questo viene utilizzato altrove, comprese le funzioni protette nidificate, si riferisce all'ambito globale (che nel caso del browser è l'oggetto finestra).

Il secondo caso è ovviamente un difetto di progettazione, ma è abbastanza facile aggirarlo usando le chiusure.

In questo caso il this interno è associato all'oggetto globale anziché alla variabile this della funzione esterna. È il modo in cui è progettata la lingua.

Vedi " JavaScript: le parti buone " di Douglas Crockford per una buona spiegazione.

Ho trovato un bel tutorial sul ECMAScript questo

  

A questo valore è un oggetto speciale correlato con l'esecuzione   contesto. Pertanto, può essere denominato come oggetto di contesto (ovvero un   oggetto in quale contesto viene attivato il contesto di esecuzione).

Qualsiasi oggetto può essere utilizzato come questo valore del contesto.

  

a questo valore è una proprietà del contesto di esecuzione, ma non a   proprietà dell'oggetto variabile.

Questa funzione è molto importante, perché contrariamente alle variabili, questo valore non partecipa mai al processo di risoluzione dell'identificatore. Cioè quando si accede a questo in un codice, il suo valore viene preso direttamente dal contesto di esecuzione e senza alcuna ricerca della catena dell'ambito. Il valore di questo è determinato solo una volta quando si entra nel contesto.

Nel contesto globale, questo valore è l'oggetto globale stesso (ciò significa che questo valore è uguale all'oggetto variabile)

Nel caso di un contesto di funzione, questo valore in ogni singola chiamata di funzione può essere diverso

Riferimento Javascript-the-core e Chapter-3-this

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top