ventajas - javascript map
¿Las funciones de flecha de ES6 aún se cierran sobre "esto" incluso si no lo usan? (1)
Mi pregunta es: ¿la función de la flecha aún se une a esto de manera léxica, manteniendo vivo al Foo mientras el otro está vivo, o puede el Foo ser basura recolectada en esta situación?
En lo que concierne a la especificación, la función de flecha tiene una referencia al objeto de entorno donde se creó, y ese objeto de entorno tiene this
, y this
refiere a la instancia de Foo
creada por esa llamada. Por lo tanto, cualquier código que confíe en que Foo
no se guarda en la memoria depende de la optimización, no del comportamiento especificado.
En cuanto a la optimización, todo depende de si el motor de JavaScript que está utilizando optimiza los cierres y si puede optimizar el cierre en la situación específica. (Un número de cosas puede evitarlo). La situación es así con las funciones "normales":
function Foo(other) {
var t = this;
other.callback = function() { };
}
En esa situación, la función se cierra sobre el contexto que contiene t
, y así, en teoría, tiene una referencia a t
que a su vez mantiene la instancia de Foo
en la memoria.
Esa es la teoría, pero en la práctica, un motor JavaScript moderno puede ver que el cierre no lo utiliza y puede optimizarlo, siempre y cuando no presente un efecto secundario observable. Si lo hace y, en caso afirmativo, cuándo, es totalmente dependiente del motor.
Dado que las funciones de las flechas realmente son cierres léxicos, las situaciones son exactamente análogas, por lo que cabe esperar que el motor de JavaScript haga lo mismo: optimícelo a menos que cause un efecto secundario que pueda observarse. Dicho esto, recuerde que las funciones de flecha son muy nuevas , por lo que puede ser que los motores aún no tengan mucha optimización con esto (no hay juego de palabras) .
La versión de V8 en Chrome (estoy usando Chrome 48.0.2564.116 de 64 bits) actualmente parece hacer esto: Ejecuté Chrome en el modo que te permite forzar la recolección de basura ( google-chrome --js-flags="--expose-gc"
) y corrió esto:
"use strict";
function Foo(other) {
other.callback = () => this; // <== Note the use of `this` as the return value
}
let a = [];
for (let n = 0; n < 10000; ++n) {
a[n] = {};
new Foo(a[n]);
}
// Let''s keep a Foo just to make it easy to find in the heap snapshot
let f = new Foo({});
log("Done, check the heap");
function log(msg) {
let p = document.createElement(''p'');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
En Dev Tools, tomar una instantánea del montón muestra las 10,001 instancias esperadas de Foo
en la memoria. Luego gc()
en la consola (así es como se fuerza la recolección de basura) y tomé otra instantánea del montón. Las 10,001 instancias de Foo
todavía estaban allí:
Luego cambié la devolución de llamada para que no hiciera referencia a this
:
other.callback = () => { }; // <== No more `this`
"use strict";
function Foo(other) {
other.callback = () => {}; // <== No more `this`
}
let a = [];
for (let n = 0; n < 10000; ++n) {
a[n] = {};
new Foo(a[n]);
}
// Let''s keep a Foo just to make it easy to find in the heap snapshot
let f = new Foo({});
log("Done, check the heap");
function log(msg) {
let p = document.createElement(''p'');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
Y corrió la página de nuevo. Ni siquiera tuve que gc()
, solo había una instancia de Foo
en la memoria (la que puse allí para que sea fácil de encontrar en la instantánea) cuando el código terminó de ejecutarse:
Me pregunté si era el hecho de que la devolución de llamada estaba completamente vacía lo que permitía la optimización, y me sorprendió gratamente al descubrir que no lo estaba: Chrome se complace en retener partes del cierre mientras abandona this
, como se demuestra aquí:
"use strict";
function Foo(other, x) {
other.callback = () => x * 2;
}
let a = [];
for (let n = 0; n < 10000; ++n) {
a[n] = {};
new Foo(a[n], n);
}
// Let''s keep a Foo just to make it easy to find in the heap snapshot
let f = new Foo({}, 0);
document.getElementById("btn-call").onclick = function() {
let r = Math.floor(Math.random() * a.length);
log(`a[${r}].callback(): ${a[r].callback()}`);
};
log("Done, click the button to use the callbacks");
function log(msg) {
let p = document.createElement(''p'');
p.appendChild(document.createTextNode(msg));
document.body.appendChild(p);
}
<input type="button" id="btn-call" value="Call random callback">
A pesar de que las devoluciones de llamada están allí y tienen su referencia a x
, Chrome optimiza la instancia de Foo
.
Preguntó acerca de las referencias de especificaciones sobre cómo se resuelve esto en las funciones de flecha: El mecanismo se extiende a lo largo de la especificación. Cada environment (como el entorno creado al llamar a una función) tiene una ranura interna [[thisBindingStatus]]
, que es "lexical"
para las funciones de flecha. Al determinar el valor de this
, se utiliza la operación interna ResolveThisBinding
, que utiliza la operación interna GetThisEnviroment
para encontrar el entorno que tiene definido. Cuando se realiza una llamada de función "normal", BindThisValue
se utiliza para vincular this
para la llamada de función si el entorno no es un entorno "lexical"
. Así que podemos ver que resolver this
desde dentro de una función de flecha es como resolver una variable: el entorno actual se comprueba para this
enlace y, al no encontrar uno (porque no está vinculado cuando se llama una función de flecha), va al medio ambiente que contiene
Estoy tratando de entender las reglas de cuándo this
está ligado de manera léxica en una función de flecha ES6. Veamos primero esto:
function Foo(other) {
other.callback = () => { this.bar(); };
this.bar = function() {
console.log(''bar called'');
};
}
Cuando construyo un new Foo(other)
, se establece una devolución de llamada en ese otro objeto. La devolución de llamada es una función de flecha, y la función de this
en la flecha está ligada de manera léxica a la instancia de Foo
, por lo que no se recolectará la basura de Foo
incluso si no mantengo ninguna otra referencia a Foo
.
¿Qué pasa si hago esto en su lugar?
function Foo(other) {
other.callback = () => { };
}
Ahora configuro la devolución de llamada a nop, y nunca menciono this
en ella. Mi pregunta es: ¿la función de la flecha aún se une a this
léxica, manteniendo vivo al Foo
mientras el other
está vivo, o puede el Foo
ser basura recolectada en esta situación?