¿Cuando pasas 'esto' como argumento? [duplicar]

Resuelto noob-in-need asked hace 9 años • 0 respuestas

Estoy tratando de aprender sobre thisy me confunde un poco aquí:

var randomFunction = function(callback) {
    var data = 10;
    callback(data);
};

var obj = {
    initialData:  20,
    sumData: function(data) {
        var sum = this.initialData + data;
        console.log(sum);
    },
    prepareRandomFunction: function() {
        randomFunction(this.sumData.bind(this));
    }
};

obj.prepareRandomFunction();

¿ Está thisdiseñado para establecerse donde se representa por primera vez en el código?

Por ejemplo, en mi ejemplo lo estoy usando con éxito para hacer referencia objy también vincular la función a obj, pero dado que thisse pasa como una función de devolución de llamada, ¿qué impide que se establezca como randomFunction(es decir, qué impide que pase literalmente "este .sumData.bind(this)" para que thisse establezca randomFunctioncuando se llame desde allí)?

Actualizado

No estoy preguntando exactamente cómo funciona esto en general (no lo creo). Principalmente tengo curiosidad por saber por qué thisse establece donde lo defino como el argumento de mi randomFunctionllamada, y no donde callbackse llama dentro de randomFunction.

Podría estar equivocado, pero si cambiara this.sumData.bind(this)por el callback(data)que tengo actualmente creo que obtendría un resultado diferente. ¿Es porque callbackes una referencia a this.sumData.bind(this)cuándo se definió por primera vez (y dónde thisestá obj)?


Creo que he aprendido a través de este escenario que thisse establece cuando se ejecuta. No se pasa como un argumento que se establecerá más adelante cuando se invoque el argumento en el futuro.

noob-in-need avatar Jan 19 '15 08:01 noob-in-need
Aceptado

thisdentro de una llamada de función se configura de acuerdo con cómo se llama una función. Hay seis formas principales de thisconfigurarlo.

  1. Llamada de función normal: en una llamada de función normal como foo(), thisse establece en el objeto global (que está windowen un navegador o globalen nodejs) o en undefined(en el modo estricto de JavaScript).

  2. Llamada al método: si se llama a un método, por ejemplo obj.foo()cuando el método fooes una declaración de método normal usando la functionpalabra clave o usando la sintaxis de declaración de método normal para a class, entonces thisse establece objdentro de la función.

  3. .apply() o .call(): Si se usa .apply()o , entonces se establece de acuerdo con lo que se pasa a or . Por ejemplo, podría hacer y hacer que se establezca dentro de para esa llamada de función en particular..call()this.apply().call()foo.call(myObj)thismyObjfoo()

  4. Uso de nuevo: si llama a una función como new, new foo()entonces se crea un nuevo objeto y foose llama a la función constructora thisconfigurada para el objeto recién creado.

  5. Usando .bind(): cuando se usa .bind()una nueva función auxiliar, se devuelve desde esa llamada que se usa internamente .apply()para configurar el thispuntero tal como se pasó a .bind(). Para su información, este no es realmente un caso diferente porque .bind()se puede implementar con .apply().

  6. Uso de la función de flecha ancha de ES6 La definición de una función mediante la sintaxis de flecha en ES6+ vinculará el valor léxico actual de thisella. Entonces, no importa cómo se llame la función en otro lugar (con cualquiera de las formas anteriores de llamarla), thisel intérprete establecerá el valor que thistenía cuando se definió la función. Esto es completamente diferente a todas las demás llamadas a funciones.

Hay una especie de séptimo método, a través de una función de devolución de llamada , pero en realidad no es su propio esquema, sino que la función que llama a la devolución de llamada usa uno de los esquemas anteriores y eso determina cuál será el valor de thiscuando se llame a la devolución de llamada. Debe consultar la documentación o el código de la función de llamada o probarlo usted mismo para determinar qué thisse configurará en una devolución de llamada.


Lo que es importante entender en JavaScript es que cada función o llamada a método en JavaScript establece un nuevo valor para this. Y el valor que se establece está determinado por cómo se llama la función.

Por lo tanto, si pasa un método como una devolución de llamada simple, ese método, de forma predeterminada, no será llamado como obj.method()y, por lo tanto, no tendrá el valor correcto thisestablecido para él. Puede utilizar .bind()para solucionar ese problema.

También es útil saber que algunas funciones de devolución de llamada (como los controladores de eventos DOM) se llaman con un valor específico thisestablecido por la infraestructura que llama a la función de devolución de llamada. Internamente, todos usan .call()más o .apply()menos. Esta no es una regla nueva, pero es algo a tener en cuenta. El "contrato" para una función de devolución de llamada puede incluir cómo establece el valor de this. Si no establece explícitamente el valor de this, se establecerá de acuerdo con la regla n.° 1.

En ES6, llamar a una función mediante una función de flecha mantiene el valor léxico actual de this. Aquí hay un ejemplo de la función de matriz que mantiene el léxico this de MDN :

function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // |this| properly refers to the person object
  }, 1000);
}

var p = new Person();

Su ejemplo de obj.prepareRandomFunction();es la regla n.° 2 anterior, por lo que thisse establecerá en obj.

Su ejemplo de randomFunction(this.sumData.bind(this))es la regla n.° 1 anterior, por lo que thisel interior de randomFunctionse establecerá en el objeto global o undefined(si está en modo estricto).

Dado que randomFunction está llamando a una función de devolución de llamada que ella misma usó .bind(), entonces el valor de thisdentro de la función de devolución de llamada cuando se llama se establecerá en el valor que thisse pasó .bind()a this.sumData.bind(this)través de la regla n.° 5 anterior. .bind()en realidad crea una nueva función cuyo trabajo es llamar a la función original DESPUÉS de establecer un valor personalizado de this.


Aquí hay un par de referencias más sobre el tema:

Cómo evitar que "esto" se refiera al elemento DOM y se refiera al objeto

Una mejor comprensión de esto

¿Cómo funciona la palabra clave "esta"?


Tenga en cuenta que con el uso de .apply()o .call()o .bind()puede crear todo tipo de cosas un tanto extrañas y, a veces, bastante útiles que nunca se podrían hacer en algo como C++ . Puedes tomar cualquier función o método del mundo y llamarlo como si fuera un método de algún otro objeto.

Por ejemplo, esto se utiliza a menudo para hacer una copia de los elementos del argumentsobjeto en una matriz:

var args = Array.prototype.slice.call(arguments, 0);

o similar:

var args = [].slice.call(arguments, 0);

Esto toma el .slice()método de la matriz y lo llama, pero le proporciona un objeto de argumentos como thispuntero. El argumentsobjeto (aunque no es una matriz real), tiene suficiente funcionalidad similar a una matriz para que el .slice()método pueda operar en él y termina haciendo una copia de los argumentselementos en una matriz real que luego se puede operar directamente con operaciones de matriz reales. Este tipo de artimañas no se pueden hacer de cualquier manera. Si el .slice()método de matriz dependiera de otros métodos de matriz que no están presentes en el argumentsobjeto, entonces este truco no funcionaría, pero como solo depende de []y .length, los cuales argumentstiene el objeto, en realidad funciona.

Por lo tanto, este truco se puede utilizar para "tomar prestados" métodos de cualquier objeto y aplicarlos a otro objeto siempre que el objeto al que los esté aplicando admita los métodos o propiedades que el método realmente utiliza. Esto no se puede hacer en C++ porque los métodos y propiedades están "ligados" en tiempo de compilación (incluso los métodos virtuales en C++ están vinculados a una ubicación de tabla v específica establecida en tiempo de compilación), pero se puede hacer fácilmente en JavaScript porque las propiedades y los métodos se buscan en vivo en tiempo de ejecución a través de su nombre real, por lo que cualquier objeto que contenga las propiedades y métodos correctos funcionará con cualquier método que opere sobre ellos.

jfriend00 avatar Jan 19 '2015 01:01 jfriend00

Paso a paso.

  1. thisadentro prepareRandomFunctionestaobj

     obj.prepareRandomFunction()
    
  2. randomFunctiontoma una función:

     randomFunction(this.sumData);
    
  3. Esa función se llama:

     callback(data);
    

    El aviso callbackse llama sin punto, lo que significa que no tiene valor para this, lo que significa thisque es el objeto global (o undefineden modo estricto).

  4. sumDatase llama:

     var sum = this.initialData + data;
    

    thises el objeto global, initialDatano existe, lo agregas undefineda data. Resultados inesperados.

    Solución: enlazar thispermanentemente:

     randomFunction(this.sumData.bind(this));
    
  5. sumDatacorre, thises obj, obj.initialDataes 20. Funciona.

elclanrs avatar Jan 19 '2015 01:01 elclanrs

¿Está diseñado para establecerse donde se representa por primera vez en código?

No. Se establece según cómo se llama una función o mediante el uso de bind .

Por ejemplo, en mi ejemplo lo estoy usando con éxito para referirme a obj.

Porque la función se llama usando:

obj.prepareRandomFunction();

que establece esto en la función en obj .

y también vincular la función a obj

en:

var obj = {
  ...
  prepareRandomFunction: function() {
      randomFunction(this.sumData.bind(this));
  }
};

Dado que se ha llamado a prepareRandomFunction con obj como this , entonces el valor de this dentro de obj.sumData se establece en obj y la llamada a randomFunction se resuelve efectivamente como:

  randomFunction(obj.sumData.bind(obj));

pero dado que esto se pasa como una función de devolución de llamada, ¿qué impide que se establezca como función aleatoria?

El hecho de que hayas configurado esto como obj en la llamada. Si desea que esto sea función aleatoria , deberá configurarlo así. randomFunction no tiene valor intrínseco para this , se establece según cómo se llama a la función (o el uso de bind ).

(es decir, ¿qué le impide pasar literalmente "this.sumData.bind(this)" para que se establezca en función aleatoria)?

Porque esta no es una función aleatoria cuando se ejecuta ese código.

Entonces:

obj.prepareRandomFunction();

llama a prepareRandomFunction con esto configurado en obj , que luego lo hace (reemplazando esto con obj :

randomFunction(obj.sumData.bind(obj));

Dentro de randomFunction , esto no se ha configurado, por lo que el valor predeterminado es el objeto global (realmente irrelevante ya que no se usa aquí). Entonces:

var randomFunction = function(callback) {
    var data = 10;
    callback(data);
};

Lo que crea una variable local data con un valor de 10, luego llama a sumData con su conjunto this en obj y pasa el valor de data . Entonces:

sumData: function(data) {
    var sum = this.initialData + data;
    console.log(sum);
},

y dado que esto es obj , la asignación efectivamente resuelve:

  var sum = obj.initialData + 10;

que es 30.

RobG avatar Jan 19 '2015 01:01 RobG