Hero

Avatar image Por Héctor Villar Mozo

1. Dado este código. ¿Cuál es el resultado de los console.log?, ¿hay algún problema?. Si lo hay, ¿cómo lo solucionarías?

        (function () {
          'use strict';

          var User = {
            count: 7,
            getCount: function () {
              return this.count;
            }
          }

          var funcOA = User.getCount;
          console.log(User.getCount());
          console.log(funcOA());
        }();
      

Resultado:

          7
          undefined
        

Respuesta:

  • La keyword this asume diferentes valores dependiendo desde qué contexto está siendo llamada. En la línea 6, getCount es un método del objeto User, es decir, una propiedad del objeto en forma de función, donde el valor de this apunta al objeto llamador de dicho método, en este caso, User. En la línea 5, a User le pertenece la propiedad count, por lo que this.count se resuelve naturalmente como 7.

  • En el caso de la línea 11, hemos cacheado el método getCount en la variable funcOA, fuera del objeto User, perdiendo así la referencia de this de dentro del método. Ahora, this apunta al contexto desde donde esta siendo llamado, en este caso, al scope de la función IIFE. this, cuando es llamado en funciones, apunta al objeto global del entorno JS actual, en el caso del navegador, al objeto global window. Sin embargo no devuelve este objeto, devuelve undefined (dando así error en runtime), porque usamos el modo 'use strict' del interprete JS (Ver nota).
  • Nota: Esto se diseñó así en cierto draft de ECMAScript para evitar intoxicar el objeto global de variables (window en este caso), cuando los desarrolladores olvidaban prefijar los constructores con la keyword new, evitando así conflictos de nombres, resultados inesperados y apoyarse en scope global (se considera un antipatrón).
  • Existen varias soluciones:

    1️⃣ Una de las maneras sería hacer referencia al objeto propietario de count dentro del método (nótese la forma de shortcut, aunque no interviene en la solución), tal que así:

                  // [...]
                    var User = {
                      count: 7,
                      getCount () {
                        return User.count;
                      }
                    }
                    // [...]
                

    2️⃣ Aunque autoreferenciarse podría dar problemas en el futuro. Otra mejor solución sería convertir ese método en un getter, tal que así:

                // [...]
                var User = {
                  count: 7,
                  get getCount () {
                    return this.count;
                  }
                }
    
                var funcOA = User.getCount;
                console.log(User.getCount);
                console.log(funcOA);
                // [...]
                

    3️⃣ Otra solución sería bindear el contexto de User a la función funcOA, con bind, asignadole el this de User a dicha función, tal que así:

                // [...]
                var User = {
                  count: 7,
                  getCount: function () {
                    return this.count;
                  }
                }
    
                var funcOA = User.getCount.bind(User)
                console.log(User.getCount());
                console.log(funcOA());
                
                
  • Nota: Es una de las soluciones "históricas", antes de ECMAScript 6, y de hecho, era la manera de asignar manejadores de eventos a elementos JSX en clases JS sin perder referencia de this en React antes de los hooks.

  • Existen otras soluciones, como usar un Proxy que intercepte el acceso a dicha propiedad y asignarle un manejador que devuelva el resultado deseado. Aunque en este escenario es pecar de over-engineering, si es un patrón bastante común hoy en día, por ejemplo, es uno de los pilares de la reactividad de Vue 3.