JS: Introducción a la POO

Teoría: Uso de `this` con funciones de flecha

Las funciones de flecha difieren de las funciones normales no solo en la forma de escribirlas. La principal diferencia radica en cómo trabajan con el contexto. En resumen, el contexto de las funciones normales depende del lugar donde se llaman, mientras que el contexto de las funciones de flecha depende del lugar donde se definen.

Veamos algunos ejemplos. El caso más simple es definir una función a nivel de módulo. En este caso, el contexto de la llamada será el propio módulo.

Nota: los ejemplos a continuación son para Node.js. En el navegador, this por defecto contendrá el objeto global Window.

const f1 = () => { // función de flecha
  console.log(this);
};

f1(); // undefined

function f2() { // función normal
  console.log(this);
}

f2(); // undefined

Aquí el comportamiento de las funciones no difiere, ya que el contexto de llamada para ambas funciones es el propio módulo, y en ES6 los módulos no tienen un this definido. Ahora intentemos agregar estas funciones a un objeto:

const obj = {
  f1, f2,
};

obj.f1(); // undefined
obj.f2(); // { f1: [Function: f1], f2: [Function: f2] }

La función normal se vincula correctamente con el contexto del objeto en el que se llama. Sin embargo, esto no sucede con la función de flecha. ¿Por qué? La función de flecha no tiene su propio contexto, se vincula con el entorno léxico, es decir, la función en la que se define la función de flecha. Este es un punto muy importante. Es la función de nivel superior la que establece el contexto de la función de flecha, no otra cosa. Y este comportamiento no se puede cambiar utilizando las funciones call o bind.

f1.call({ name: 'códica' }); // undefined
f1.bind({ name: 'códica' })(); // undefined

Ahora definamos una función de flecha dentro de otro objeto y tratemos de llamarla:

const company = {
  f1: () => { // función de flecha
    console.log(this);
  },
  f2() { // función normal
    console.log(this);
  },
};

company.f1(); // undefined
company.f2(); // { f1: [Function: f1], f2: [Function: f2] }

Aquí vemos exactamente la misma situación. A pesar de que la función de flecha se define dentro del objeto y se llama desde el mismo objeto, el contexto sigue estando vinculado al lugar de definición de la función (entorno léxico), que es el propio módulo.

Ahora intentemos definir una función de flecha dentro de otra función:

const printer = {
  items: [1],
  print() { // es importante que la función externa tenga contexto
    // La función de flecha se define dentro de la función print,
    // pero se llama dentro del método forEach
    this.items.forEach(() => console.log(this.items));
  },
};

printer.print(); // [1]

Parece que la función de flecha tiene un this, pero este contexto no pertenece a la función, lo ha heredado de la función externa print. Para entenderlo mejor, imagina cómo se llama la función de flecha dentro de forEach. Se llama directamente, no desde el objeto. Las funciones normales en esta situación crean su propio contexto, pero las funciones de flecha no. Toman el contexto del lugar donde se definen. La función de flecha se define en la función print, por lo que toma el contexto de la función print.

El mismo código con una función normal no funcionará:

const printer = {
  items: [1],
  print() {
    this.items.forEach(function () { console.log(this.items); });
  },
};

printer.print(); // ¡Error!

Esto sucede precisamente porque la función se llama como una función normal, no como un método. En este caso, su contexto será el contexto global. Por lo general, este contexto es undefined, por lo que this.items generará un error.

¿Dónde puede ser útil todo esto? En la gran mayoría de los casos, no necesitamos this dentro de una función de flecha. Siempre es mejor trabajar con los datos que se pasan explícitamente. Sin embargo, hay algunos ejemplos en los que esta característica de las funciones de flecha ayuda a simplificar el código. Estos ejemplos incluyen situaciones en las que se llama a una función de orden superior dentro de un método de un objeto, y se pasa una función de flecha que trabaja con this. Esta situación es similar al código anterior.