¿Cuando pasas 'esto' como argumento? [duplicar]
Estoy tratando de aprender sobre this
y 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á this
diseñ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 obj
y también vincular la función a obj
, pero dado que this
se 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 this
se establezca randomFunction
cuando 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é this
se establece donde lo defino como el argumento de mi randomFunction
llamada, y no donde callback
se 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 callback
es una referencia a this.sumData.bind(this)
cuándo se definió por primera vez (y dónde this
está obj
)?
Creo que he aprendido a través de este escenario que this
se establece cuando se ejecuta. No se pasa como un argumento que se establecerá más adelante cuando se invoque el argumento en el futuro.
this
dentro de una llamada de función se configura de acuerdo con cómo se llama una función. Hay seis formas principales de this
configurarlo.
Llamada de función normal: en una llamada de función normal como
foo()
,this
se establece en el objeto global (que estáwindow
en un navegador oglobal
en nodejs) o enundefined
(en el modo estricto de JavaScript).Llamada al método: si se llama a un método, por ejemplo
obj.foo()
cuando el métodofoo
es una declaración de método normal usando lafunction
palabra clave o usando la sintaxis de declaración de método normal para aclass
, entoncesthis
se estableceobj
dentro de la función..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)
this
myObj
foo()
Uso de nuevo: si llama a una función como
new
,new foo()
entonces se crea un nuevo objeto yfoo
se llama a la función constructorathis
configurada para el objeto recién creado.Usando .bind(): cuando se usa
.bind()
una nueva función auxiliar, se devuelve desde esa llamada que se usa internamente.apply()
para configurar elthis
puntero tal como se pasó a.bind()
. Para su información, este no es realmente un caso diferente porque.bind()
se puede implementar con.apply()
.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
this
ella. Entonces, no importa cómo se llame la función en otro lugar (con cualquiera de las formas anteriores de llamarla),this
el intérprete establecerá el valor quethis
tení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 this
cuando 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é this
se 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 this
establecido 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 this
establecido 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 this
se establecerá en obj
.
Su ejemplo de randomFunction(this.sumData.bind(this))
es la regla n.° 1 anterior, por lo que this
el interior de randomFunction
se 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 this
dentro de la función de devolución de llamada cuando se llama se establecerá en el valor que this
se 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 arguments
objeto 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 this
puntero. El arguments
objeto (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 arguments
elementos 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 arguments
objeto, entonces este truco no funcionaría, pero como solo depende de []
y .length
, los cuales arguments
tiene 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.
Paso a paso.
this
adentroprepareRandomFunction
estaobj
obj.prepareRandomFunction()
randomFunction
toma una función:randomFunction(this.sumData);
Esa función se llama:
callback(data);
El aviso
callback
se llama sin punto, lo que significa que no tiene valor parathis
, lo que significathis
que es el objeto global (oundefined
en modo estricto).sumData
se llama:var sum = this.initialData + data;
this
es el objeto global,initialData
no existe, lo agregasundefined
adata
. Resultados inesperados.Solución: enlazar
this
permanentemente:randomFunction(this.sumData.bind(this));
sumData
corre,this
esobj
,obj.initialData
es20
. Funciona.
¿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.