Puntatore Javascript "this" all'interno di una funzione annidata


94

Ho una domanda su come viene trattato il puntatore "this" in uno scenario di funzione annidata.

Supponiamo che inserisca il seguente codice di esempio in una pagina web. Ricevo un errore quando chiamo la funzione nidificata "doSomeEffects ()". Ho controllato in Firebug e indica che quando sono in quella funzione annidata, il puntatore "this" sta effettivamente puntando all'oggetto "finestra" globale - cosa che non mi aspettavo. Non devo capire qualcosa correttamente perché ho pensato, poiché ho dichiarato la funzione annidata all'interno di una funzione dell'oggetto, dovrebbe avere uno scope "locale" in relazione alla funzione (cioè il puntatore "this" si riferirebbe all'oggetto stesso come com'è nella mia prima dichiarazione "if").

Qualsiasi suggerimento (nessun gioco di parole) sarebbe apprezzato.

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();

Qual è esattamente la tua domanda?
Sarfraz

2
Quando viene utilizzato all'interno di una funzione, si thisriferisce all'oggetto su cui viene richiamata la funzione.
circa il

8
Quello che potresti fare nello scopo esterno è qualcosa di simile var self = this;e poi fare riferimento selfnella funzione interna tramite chiusura.
Kai

1
doSomeEffectsnon è associato a nessun oggetto in particolare, quindi thissi presume che sia la finestra, la madre di tutti gli elementi.
circa il

3
@JoJoeDad Come lo dico diplomaticamente? Ma la risposta data da chuckj di seguito è di gran lunga la risposta alla tua domanda. Per capire veramente cosa sta succedendo, dovresti leggere il contesto di esecuzione , la catena dell'ambito e questa parola chiave . E dall'aspetto di alcune delle risposte qui, anche altre persone dovrebbero leggerle. Cerco di evangelizzare un buon javascript. Questo è il motivo per cui mi prendo il tempo per fornire questi collegamenti.
onefootswill

Risposte:


120

In JavaScript l' thisoggetto è realmente basato su come effettui le chiamate di funzione.

In generale ci sono tre modi per impostare l' thisoggetto:

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

In tutti gli esempi precedenti l' thisoggetto sarà someThing. Chiamare una funzione senza un oggetto genitore principale ti darà generalmente l' oggetto globale che nella maggior parte dei browser significa l' windowoggetto.


Ho cambiato il codice in this.doSomeEffects();base alla tua risposta, ma ancora non funziona. Perché?
Arashsoft

1
@Arashsoft thisin this.doSomeEffects()punta a std_obj. Come spiegato nella risposta sopra, se una funzione non ha un riferimento thisa un oggetto, deve essere un oggetto finestra.
Shivam

38

Poiché questa sembra essere tra le domande più votate nel suo genere, lasciatemi aggiungere, dopo tutti questi anni, la soluzione ES6 che utilizza le funzioni freccia:

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") { }
    };
    ...    
  }
};

5
Ora che di progresso!
Joshua Pinter,

Ottima, semplice, soluzione.
Joshua Schlichting

32

thisnon fa parte dell'ambito di chiusura, può essere pensato come un parametro aggiuntivo alla funzione associata al sito della chiamata. Se il metodo non viene chiamato come metodo, l'oggetto globale viene passato come this. Nel browser, l'oggetto globale è identico a window. Ad esempio, considera la seguente funzione,

function someFunction() {
}

e il seguente oggetto,

var obj = { someFunction: someFunction };

Se chiami la funzione utilizzando la sintassi del metodo come,

obj.someFunciton();

quindi thisè vincolato a obj.

Se chiami direttamente someFunction (), come,

someFunction();

quindi thisè vincolato all'oggetto globale, cioè window.

Il modo più comune per aggirare è catturare questo nella chiusura come,

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();         
 }      

che = questo è perfetto
Nather Webber

10

C'è una differenza tra le variabili di allegato e "questo". "this" è effettivamente definito dall'invocatore della funzione, mentre le variabili esplicite rimangono intatte all'interno del blocco di dichiarazione della funzione noto come enclosure. Guarda l'esempio di seguito:

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();

puoi provarlo qui: http://jsfiddle.net/kSTBy/

Quello che sta succedendo nella tua funzione è "doSomeEffects ()", viene chiamato esplicitamente, questo significa contesto o "questo" della funzione è la finestra. se "doSomeEffects" fosse un metodo prototipo, ad esempio this.doSomeEffects su say "myObject", allora myObject.doSomeEffects () farebbe sì che "this" sia "myObject".


4
per i pigri e gli impazienti, questa demo registra:_this.name = myFirstObject this.name = mySecondObject
ptim

9

Per capire questa domanda, prova a ottenere l'output per il seguente frammento

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();

Il codice precedente produrrà quanto segue sulla console:

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

Nella funzione esterna, sia this che self si riferiscono a myObject e quindi entrambi possono fare correttamente riferimento e accedere a foo.

Nella funzione interna, tuttavia, questo non si riferisce più a myObject. Di conseguenza, this.foo non è definito nella funzione interna, mentre il riferimento alla variabile locale self rimane nell'ambito ed è accessibile da lì. (Prima di ECMA 5, questo nella funzione interna si riferiva all'oggetto finestra globale; mentre, a partire da ECMA 5, questo nella funzione interna sarebbe indefinito.)


1
Nella funzione interna, si thisriferisce all'oggetto finestra (in un ambiente browser) o all'oggetto GLOBAL (in un ambiente node.js)
raneshu

2
Perché succede?
setu

@raneshu "usa rigoroso" e thisnon si riferisce più alla finestra
rofrol

3

Come spiegato da Kyle, puoi usare callo applyper specificare thisall'interno della funzione:

Ecco quel concetto applicato al tuo codice:

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


0

Ho anche ricevuto un avviso " Accesso a riferimenti potenzialmente non valido a un campo di classe tramite questo "

class MyListItem {
    constructor(labelPrm) {
        this._flagActive = false;
        this._myLabel = labelPrm;
        labelPrm.addEventListener('click', ()=>{ this.onDropdownListsElementClick();}, false);
    }

    get myLabel() {
        return this._myLabel
    }
    get flagActive(){
        return this._flagActive;
    }

    onDropdownListsElementClick(){console.log("Now'this'refers to the MyListItem itself")}}//end of the class

0

Dal momento che non è stato menzionato, menzionerò che l'uso .bind()è una soluzione -


        doSomeEffects=doSomeEffect.bind(this);
        doSomeEffects();   
      }
    };

    std_obj.displayMe();

Ecco un esempio più semplice:

bad = { 
  name:'NAME', 
  z : function() { 
    function x() { console.log(this.name); }; 
    x() 
  } 
};
bad.z() // prints 'undefined'

good = { 
  name:'NAME', 
  z : function() { 
    function x() { console.log(this.name); }; 
    x=x.bind(this);
    x();
  } 
};
good.z() // prints 'NAME'

È vero che l'uso di una funzione freccia =>sembra più semplice ed è facile per il programmatore. Tuttavia, si dovrebbe tenere presente che è probabile che uno scope lessicale richieda più lavoro in termini di elaborazione e memoria per impostare e mantenere tale ambito lessicale, rispetto alla semplice associazione di una funzione thiscon un puntatore tramite .bind().

Parte del vantaggio dello sviluppo di classi in JS era di fornire un metodo per rendere thispresenti e disponibili in modo più affidabile, per allontanarsi dalla programmazione funzionale e dagli ambiti lessicali e quindi ridurre il sovraccarico.

Da MDN

Considerazioni sulle prestazioni Non è saggio creare inutilmente funzioni all'interno di altre funzioni se non sono necessarie chiusure per un'attività particolare, poiché influirà negativamente sulle prestazioni dello script sia in termini di velocità di elaborazione che di consumo di memoria.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.