Buscar
  • Angel Taborda

¿Qué es this?

Seguramente no es la primera vez que ves esta pregunta. Quizás te la han hecho en alguna entrevista de trabajo o en un curso de JavaScript.


Lo cierto es que pocos saben lo que realmente es this en JavaScript. La razón más común es por el concepto que tenemos de esa palabra en lenguajes como C#, y que nos hace pensar erróneamente que debe referirse a lo mismo.


Normalmente hasta que no nos topamos con un funcionamiento “extraño” en nuestro código, no nos detenemos a pensar en si sabemos lo que realmente es. De hecho lo primero que hacemos es echarle la culpa a JavaScript y no a nuestra falta de interés por saber lo que está pasando.


Si te sentiste identificado o simplemente quieres saber de un modo sencillo lo que es this, sigue leyendo.


¿Qué NO es this?


NO es una referencia a sí mismo. Esta quizás es la respuesta más común que escuchamos, decir que this es una referencia a la función en la que se encuentra.


NO es una referencia al lexical scope de la función. Tampoco es una referencia al scope que definimos al momento de escribir nuestro código. (Más sobre lexical scope en un próximo post).


...entonces ¿Qué es this?


Es un binding que se hace cuando una función es invocada, y a lo que realmente hace referencia this dependerá completamente del lugar en dónde se ha llamado (call-site) dicha función, no el lugar en donde se ha declarado.


Pero, ¿cómo funciona? si es un binding, ¿se puede cambiar? Veamos ahora qué 4 reglas se aplican para determinar que es this.


Default Binding


Esta es la que se aplica cuando ninguna otra regla pueda hacerlo. El siguiente ejemplo lo ilustra:


    function foo() {
        console.log(this.a);
    }
    
    var a = 2;
    
    foo(); // 2
        

En este caso this.a hace referencia a la variable global a que hemos declarado. Como la llamada a la función foo() es una llamada común y ninguna de las otras reglas que veremos más adelante es aplicable, la regla por defecto es la ganadora.


Implicit Binding


La siguiente regla aplicaría cuando el lugar en donde se hace la llamada tiene un objeto como contexto, por ejemplo:


    function foo() {
        console.log(this.a);
    }
    
    var obj = {
        a: 2,
        foo: foo
    };
    
    obj.foo(); // 2
        

Como obj es this para foo(), this.a sería sinónimo de obj.a

Cabe destacar que solo el nivel más alto de la cadena de referencias a una propiedad es la que se tomaría en cuenta, es decir:


    function foo() {
        console.log(this.a);
    }
    
    var obj2 = {
        a: 42,
        foo: foo
    };
    
    var obj1 = {
        a: 2,
        obj2: obj2
    };
    
    obj1.obj2.foo(); // 42
        

Una de las cosas que pueden frustrarnos a veces es la pérdida de este binding implícito, lo que usualmente significa que se aplicaría la regla por defecto y this acabaría siendo el global object o undefined, depende de si usamos strict mode o no, por ejemplo:


    function foo() {
        console.log(this.a);
    }
    
    var obj = {
        a: 2,
        foo: foo
    };
    
    var bar = obj.foo; // alias con referencia a la función
    
    var a = 'global!!'; // propiedad 'a' en objeto global
    
    bar(); // 'global!!'
        

Puede parecer que bar es una referencia a obj.foo pero en realidad es una referencia a foo en sí mismo, además recordemos que lo que importa es el lugar en donde se llama y bar() es una llamada común sin decoración por lo que la regla por defecto aplicaría.


Otro escenario muy común donde ocurre esto es cuando pasamos una función como callback:


    function foo() {
        console.log(this.a);
    }
    
    function doFoo(fn) {
        // 'fn' otra referencia a foo
    
        fn(); // <-- call-site
    }
    
    var obj = {
        a: 2,
        foo: foo
    };
    
    var a = 'global!!'; // propiedad 'a' en objeto global
    
    doFoo(obj.foo); // 'global!!'
        

Lo mismo ocurre si usamos una función built-in en JavaScript:


    function foo() {
        console.log(this.a);
    }
    
    var obj = {
        a: 2,
        foo: foo
    };
    
    var a = 'global!!'; // propiedad 'a' en objeto global
    
    setTimeout(obj.foo, 100); // 'global!!'
        

Veamos ahora cómo “solucionar” este problema con la siguiente regla.


Explicit Binding


En la regla anterior creamos una propiedad en un objeto y le asignamos una referencia a una función para que se enlazara this al objeto de manera indirecta.


Pero, y si queremos forzar a una función a que use un objeto en particular para this, sin tener que crear una propiedad con la referencia a la función dentro del objeto en cuestión?

¿Te suenan los métodos call o apply?


Todas las funciones tienen estos métodos disponibles, a través de su [[Prototype]], los cuales son muy útiles para lograr este cometido.


¿Cómo funcionan? Ambos reciben, como primer parámetro, el objeto que deben usar como this, y luego invocan la función. Cómo estamos indicando directamente lo que queremos que sea this, lo llamamos binding explícito.


    function foo() {
        console.log(this.a);
    }
    
    var obj = {
        a: 2
    };
    
    foo.call(obj); // 2
        

Desafortunadamente, el explicit binding sigue sin ofrecernos una solución al problema planteado anteriormente.


Veamos una variación de este patrón que sí puede darnos una salida y que se introdujo como una utilidad built-in en JavaScript a partir de ES5.

Estamos hablando de Function.prototype.bind, y se usa de la siguiente manera:


Hard Binding


    function foo(something) {
        console.log(this.a, something);
        return this.a + something;
    }
    
    var obj = {
        a: 2
    };
    
    var bar = foo.bind(obj);
    
    var b = bar(3); // 2 3
    console.log(b); // 5
        

bind(...) retorna una nueva función que está forzada a llamar a la función original usando el contexto de this que le hemos especificado.


new Binding


La cuarta y última regla se aplica cuando una función es invocada usando new (constructor call).


Esto es lo que pasa cuando usamos new frente a una función:

  1. Un objeto nuevo se crea de la nada.

  2. El objeto nuevo se establece como this para la llamada de esa función.

  3. A menos que la función retorne su propio objeto, la llamada retornará automáticamente el nuevo objeto creado.


    function foo(a) {
        this.a = a;
    }
    
    var bar = new foo(2);
    console.log(bar.a); // 2
        


Bonus: Lexical this


Este tipo de binding es posible a partir de ES6 y se aplica al usar arrow-functions.

En este caso se usa el lexical scope como this, es decir se tomara del contexto que encierre la llamada a la función.


    function foo() {
        return (a) => {
            console.log(this.a);
        };
    }
    
    var obj1 = {
        a: 2
    };
    
    var obj2 = {
        a: 3
    };
    
    var bar = foo.call(obj1);
    bar.call(obj2); // 2
        

Este tipo de binding ignora todas las reglas previamente mencionadas y no puede ser sobrescrito.


Resumen


Hemos visto que this es un binding y que para determinarlo durante la ejecución de una función es necesario encontrar el call-site de esa función.


Una vez examinado, 4 reglas pueden aplicar en el siguiente orden:

  1. ¿Se llamo usando new? Entonces se usa el objeto nuevo que se creo.

  2. ¿Se llamo con call o apply o bind? Entonces se usa el objeto especificado.

  3. ¿Se llamo bajo el contexto de un objeto? Entonces se usa ese objeto.

  4. ¿Ninguna de las anteriores? undefinded en strict mode, sino el objeto global.

Fuentes

5 vistas0 comentarios

Entradas Recientes

Ver todo
 
  • LinkedIn

©2021 Angel Taborda Chinea