¿Cómo funciona "esta" palabra clave funciona dentro de una función?

248

Acabo de encontrar una situación interesante en JavaScript. Tengo una clase con un método que define varios objetos usando notación literal de objeto. Dentro de esos objetos, thisse está utilizando el puntero. Por el comportamiento del programa, deduje que el thispuntero se refiere a la clase en la que se invocó el método, y no al objeto creado por el literal.

Esto parece arbitrario, aunque es la forma en que esperaría que funcione. ¿Es este comportamiento definido? ¿Es seguro para todos los navegadores? ¿Hay algún razonamiento subyacente por qué está más allá de lo que dice "la especificación lo dice" (por ejemplo, ¿es consecuencia de alguna decisión / filosofía de diseño más amplia)? Ejemplo de código reducido:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}
rmeador
fuente
sucede también cuando hago esto var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons
echa un vistazo a este post . Tiene una buena explicación de varios usos y comportamientos de esta palabra clave.
Amor Hasija
mira
AL-zami

Respuestas:

558

Canibalizado de otra publicación mía, aquí hay más de lo que siempre quisiste saber sobre esto .

Antes de comenzar, esto es lo más importante que debe tener en cuenta sobre Javascript, y repetirse a sí mismo cuando no tiene sentido. Javascript no tiene clases (ES6 classes azúcar sintáctico ). Si algo parece una clase, es un truco inteligente. Javascript tiene objetos y funciones . (eso no es 100% preciso, las funciones son solo objetos, pero a veces puede ser útil pensar en ellas como cosas separadas)

La variable this está asociada a funciones. Cada vez que se invoca una función, esto se da un cierto valor, dependiendo de cómo se invoca la función. Esto a menudo se llama patrón de invocación.

Hay cuatro formas de invocar funciones en javascript. Puede invocar la función como método , como función , como constructor y con apply .

Como un método

Un método es una función adjunta a un objeto.

var foo = {};
foo.someMethod = function(){
    alert(this);
}

Cuando se invoca como un método, este se enlaza con el objeto de la función / método es una parte de. En este ejemplo, esto estará obligado a engañar.

Como una función

Si tiene una función independiente, esta variable estará vinculada al objeto "global", casi siempre el objeto de ventana en el contexto de un navegador.

 var foo = function(){
    alert(this);
 }
 foo();

Esto puede ser lo que te hace tropezar , pero no te sientas mal. Muchas personas consideran que esta es una mala decisión de diseño. Dado que una devolución de llamada se invoca como una función y no como un método, es por eso que está viendo lo que parece ser un comportamiento inconsistente.

Muchas personas solucionan el problema haciendo algo como, um, esto

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Define una variable que apunta a esto . Cierre (un tema de todo es propio) mantiene que los rodea, por lo que si se llama a la barra como una devolución de llamada, todavía tiene una referencia.

NOTA: en use strictmodo si se usa como función, thisno está vinculado a global. (Lo es undefined)

Como constructor

También puede invocar una función como constructor. Según la convención de nomenclatura que está utilizando (TestObject), esto también puede ser lo que está haciendo y lo que lo está tropezando .

Invoca una función como Constructor con la nueva palabra clave.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

Cuando se invoca como un constructor, se creará un nuevo objeto, y este estará obligado a ese objeto. Nuevamente, si tiene funciones internas y se usan como devoluciones de llamada, las invocará como funciones, y esto estará vinculado al objeto global. Usa esa var that = este truco / patrón.

Algunas personas piensan que el constructor / nueva palabra clave fue un hueso lanzado a los programadores tradicionales de Java / OOP como una forma de crear algo similar a las clases.

Con el método Apply

Finalmente, cada función tiene un método (sí, las funciones son objetos en Javascript) llamado "aplicar". Aplicar le permite determinar cuál será el valor de esto , y también le permite pasar una serie de argumentos. Aquí hay un ejemplo inútil.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
Alan Storm
fuente
8
Nota: En modo estricto , thisserá undefinedpara invocaciones de funciones.
Malhechor
1
Una declaración de función, por ejemplo. function myfunction () {}, es un caso especial de "como método" donde "this" es el alcance global (ventana).
richard
1
@richard: Excepto en modo estricto, y thisno tiene nada que ver con el alcance. Te refieres al objeto global .
TJ Crowder
@ alan-storm. En el caso de "Como constructor" es this.confusing = 'hell yeah';lo mismo que var confusing = 'hell yeah';? Entonces, ¿ambos permitirán myObject.confusing? Sería bueno si no solo para que pueda usar thispara crear las propiedades y otras variables para el trabajo interno.
wunth
Pero, de nuevo, supongo que las cosas de trabajo se pueden hacer fuera de la función y el valor pasado al constructor: function Foo(thought){ this.confusing = thought; }y luegovar myObject = new Foo("hell yeah");
wunth
35

Llamadas a funciones

Las funciones son solo un tipo de objeto.

Todos los objetos de función tienen métodos de llamada y aplicación que ejecutan el objeto de función al que están llamados.

Cuando se llama, el primer argumento de estos métodos especifica el objeto al que hará referencia la thispalabra clave durante la ejecución de la función, si se usa nullo undefined, el objeto global, windowpara this.

Por lo tanto, llamando a una función ...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

... con paréntesis - foo()- es equivalente a foo.call(undefined)o foo.apply(undefined), que es efectivamente el mismo que foo.call(window)o foo.apply(window).

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

Los argumentos adicionales a callse pasan como argumentos a la llamada a la función, mientras que un único argumento adicional aapply puede especificar los argumentos para la llamada a la función como un objeto tipo Array.

Por lo tanto, foo(1, 2, 3)es equivalente a foo.call(null, 1, 2, 3)o foo.apply(null, [1, 2, 3]).

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

Si una función es propiedad de un objeto ...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

... acceder a una referencia a la Función a través del objeto y llamarla entre paréntesis - obj.foo()- es equivalente foo.call(obj)ofoo.apply(obj) .

Sin embargo, las funciones mantenidas como propiedades de los objetos no están "vinculadas" a esos objetos. Como puede ver en la definición de objarriba, dado que las funciones son solo un tipo de objeto, se pueden hacer referencia a ellas (y, por lo tanto, se pueden pasar por referencia a una llamada de función o devolverse por referencia desde una llamada de función). Cuando se pasa una referencia a una Función, no se lleva información adicional sobre el lugar desde el que se pasó, por lo que sucede lo siguiente:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

La llamada a nuestra referencia de función, bazno proporciona ningún contexto para la llamada, por lo que es efectivamente igual que baz.call(undefined), por lo que thistermina haciendo referencia window. Si queremos bazsaber que pertenece obj, de alguna manera debemos proporcionar esa información cuando bazse llama, que es donde entra en juego el primer argumento callo applycierre.

Cadenas de alcance

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

Cuando se ejecuta una función, crea un nuevo ámbito y tiene una referencia a cualquier ámbito de inclusión. Cuando se crea la función anónima en el ejemplo anterior, tiene una referencia al ámbito en el que se creó, que es bindel ámbito. Esto se conoce como un "cierre".

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

Cuando intente acceder a una variable, se recorre esta "cadena de alcance" para encontrar una variable con el nombre dado: si el alcance actual no contiene la variable, observe el siguiente alcance en la cadena, y así sucesivamente hasta llegar a El alcance global. Cuando se devuelve la función anónima y bindtermina de ejecutarse, la función anónima aún tiene una referencia al bindalcance de la aplicación, por lo que bindel alcance no "desaparece".

Dado todo lo anterior, ahora debería poder comprender cómo funciona el alcance en el siguiente ejemplo, y por qué la técnica para pasar una función alrededor de "pre-enlazado" con un valor particular de la thismisma tendrá lugar cuando se llama funciona:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
Jonny Buchanan
fuente
"Cuando se pasa una referencia a una Función, no se lleva información adicional sobre el lugar desde donde se pasó", gracias @insin por esto.
Alex Marandon
9

¿Es este comportamiento definido? ¿Es seguro para todos los navegadores?

Si. Y si.

¿Hay algún razonamiento subyacente por qué es así?

El significado de thises bastante simple de deducir:

  1. Si thisse usa dentro de una función constructora, y la función se invocó con la newpalabra clave, se thisrefiere al objeto que se creará.thisseguirá significando el objeto incluso en métodos públicos.
  2. Si thisse usa en cualquier otro lugar, incluidas las funciones protegidas anidadas , se refiere al alcance global (que en el caso del navegador es el objeto de ventana).

El segundo caso es obviamente un defecto de diseño, pero es bastante fácil solucionarlo utilizando cierres.

Rakesh Pai
fuente
4

En este caso, el interno thisestá vinculado al objeto global en lugar de a la thisvariable de la función externa. Es la forma en que está diseñado el lenguaje.

Consulte "JavaScript: las partes buenas" de Douglas Crockford para obtener una buena explicación.

Santiago Cepas
fuente
4

Encontré un buen tutorial sobre el ECMAScript esto

A este valor es un objeto especial que está relacionado con el contexto de ejecución. Por lo tanto, puede nombrarse como un objeto de contexto (es decir, un objeto en cuyo contexto se activa el contexto de ejecución).

Cualquier objeto puede usarse como este valor del contexto.

a este valor es una propiedad del contexto de ejecución, pero no una propiedad del objeto variable.

Esta característica es muy importante, porque a diferencia de las variables, este valor nunca participa en el proceso de resolución del identificador. Es decir, al acceder a esto en un código, su valor se toma directamente del contexto de ejecución y sin ninguna búsqueda de cadena de alcance. El valor de esto se determina solo una vez al ingresar al contexto.

En el contexto global, un valor de este es el objeto global en sí mismo (es decir, este valor aquí es igual a un objeto variable)

En el caso de un contexto de función, este valor en cada llamada de función puede ser diferente

Referencia Javascript-the-core y Capítulo-3-this

Damodaran
fuente
" En el contexto global, un valor de este es el objeto global en sí mismo (es decir, este valor aquí equivale a un objeto variable) ". El objeto global es parte del contexto de ejecución global, al igual que el (es4) "objeto variable" y el registro de entorno ES5. Pero son entidades diferentes al objeto global (p. Ej., Un registro de entorno no puede ser referenciado directamente, está prohibido por la especificación, pero el objeto global puede serlo).
RobG