Como a palavra-chave "this" funciona em uma função?

248

Acabei de encontrar uma situação interessante em JavaScript. Eu tenho uma classe com um método que define vários objetos usando a notação literal de objeto. Dentro desses objetos, o thisponteiro está sendo usado. Pelo comportamento do programa, deduzi que o thisponteiro está se referindo à classe na qual o método foi chamado, e não ao objeto que está sendo criado pelo literal.

Isso parece arbitrário, embora seja da maneira que eu esperaria que funcionasse. Esse comportamento é definido? É seguro para vários navegadores? Existe algum raciocínio subjacente ao motivo pelo qual está além da "especificação especificada" (por exemplo, é uma consequência de alguma decisão / filosofia de projeto mais ampla)? Exemplo de código reduzido:

// 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
fonte
isso acontece quando eu faço isso também var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons
confira este post . Tem uma boa explicação sobre os vários usos e comportamentos dessa palavra-chave.
Love Hasija
verifique
AL-zami

Respostas:

558

Canibalizado de outro post meu, aqui está mais do que você sempre quis saber sobre isso .

Antes de começar, eis a coisa mais importante a ter em mente sobre o Javascript e repetir para si mesmo quando não faz sentido. Javascript não tem classes (ES6 classé açúcar sintático ). Se algo parece uma classe, é um truque inteligente. Javascript tem objetos e funções . (isso não é 100% preciso, as funções são apenas objetos, mas às vezes pode ser útil pensar nelas como coisas separadas)

A variável this está anexada às funções. Sempre que você chama uma função, isso recebe um determinado valor, dependendo de como você chama a função. Isso geralmente é chamado de padrão de chamada.

Existem quatro maneiras de chamar funções em javascript. Você pode chamar a função como um método , como uma função , como um construtor e com apply .

Como método

Um método é uma função anexada a um objeto

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

Quando invocado como um método, isso será vinculado ao objeto do qual a função / método faz parte. Neste exemplo, isso será associado a foo.

Como uma função

Se você possui uma função autônoma, a variável this será vinculada ao objeto "global", quase sempre o objeto de janela no contexto de um navegador.

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

Pode ser isso que está atrapalhando você , mas não se sinta mal. Muitas pessoas consideram isso uma má decisão de design. Como um retorno de chamada é chamado como uma função e não como um método, é por isso que você está vendo o que parece ser um comportamento inconsistente.

Muitas pessoas resolvem o problema fazendo algo como, hum, isso

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

Você define uma variável que aponta para isso . O fechamento (um tópico próprio) mantém isso por aí; portanto, se você chamar a barra como retorno, ainda terá uma referência.

NOTA: No use strictmodo, se usado como função, thisnão está vinculado ao global. (É undefined)

Como Construtor

Você também pode chamar uma função como construtor. Com base na convenção de nomenclatura que você está usando (TestObject), isso também pode ser o que você está fazendo e é o que está enganando você .

Você invoca uma função como um Construtor com a nova palavra-chave.

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

Quando chamado como construtor, um novo Objeto será criado, e isso será vinculado a esse objeto. Novamente, se você tiver funções internas e elas forem usadas como retornos de chamada, você as chamará como funções, e isso será vinculado ao objeto global. Use essa var que = esse truque / padrão.

Algumas pessoas pensam que a palavra-chave construtor / nova foi um osso jogado para Java / programadores tradicionais de POO como uma maneira de criar algo semelhante a classes.

Com o método Apply

Por fim, toda função possui um método (sim, funções são objetos em Javascript) chamado "apply". Aplicar permite determinar qual será o valor disso e também transmitir uma matriz de argumentos. Aqui está um exemplo inútil.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);
Alan Storm
fonte
8
Nota: No modo estrito , thisserá undefinedpara invocações de funções.
Miscreant
1
Uma declaração de função, por exemplo. function myfunction () {}, é um caso especial de "como método" em que "this" é o escopo global (janela).
Richard
1
@richard: Exceto no modo estrito, e thisnão tem nada a ver com o escopo. Você quer dizer o objeto global .
TJ Crowder
@ Alan Storm. No caso de "Como construtor" é this.confusing = 'hell yeah';o mesmo que var confusing = 'hell yeah';? Então, ambos vão permitir myObject.confusing? Seria bom, se não apenas, para que você possa usar thispara criar as propriedades e outras variáveis ​​para o trabalho interno.
wunth 20/09/17
Mas, novamente, acho que o material de trabalho pode ser feito fora da função e o valor passado ao construtor: function Foo(thought){ this.confusing = thought; }e entãovar myObject = new Foo("hell yeah");
wunth 20/09/17
35

Chamadas de função

Funções são apenas um tipo de objeto.

Todos os objetos Function têm métodos de chamada e aplicação que executam o objeto Function em que são chamados.

Quando chamado, o primeiro argumento para esses métodos especifica o objeto que será referenciado pela thispalavra - chave durante a execução da Função - se for nullou undefined, o objeto global window, for usado this.

Assim, chamando uma função ...

whereAmI = "window";

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

... entre parênteses - foo()- é equivalente a foo.call(undefined)ou foo.apply(undefined), que é efetivamente o mesmo que foo.call(window)ou foo.apply(window).

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

Argumentos adicionais callsão passados ​​como argumentos para a chamada de função, enquanto um único argumento adicional para applypode especificar os argumentos para a chamada de função como um objeto semelhante a uma matriz.

Assim, foo(1, 2, 3)é equivalente a foo.call(null, 1, 2, 3)ou 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"

Se uma função é uma propriedade de um objeto ...

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

... acessar uma referência à Função por meio do objeto e chamá-la entre parênteses - obj.foo()- é equivalente a foo.call(obj)ou foo.apply(obj).

No entanto, funções mantidas como propriedades de objetos não são "vinculadas" a esses objetos. Como você pode ver na definição objacima, como Funções são apenas um tipo de Objeto, elas podem ser referenciadas (e, portanto, podem ser passadas por referência a uma chamada de Função ou retornadas por referência de uma chamada de Função). Quando uma referência a uma função é passado, nenhuma informação adicional sobre onde ele foi passado de é feita com ele, razão pela qual acontece o seguinte:

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

A chamada para nossa referência de função,, baznão fornece nenhum contexto para a chamada, portanto é efetivamente o mesmo que baz.call(undefined), portanto, thisacaba fazendo referência window. Se queremos bazsaber a que pertence obj, precisamos fornecer de alguma forma essas informações quando bazsão chamadas, e é aí que o primeiro argumento para callou applye fechamentos entra em jogo.

Cadeias de escopo

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

Quando uma Função é executada, ela cria um novo escopo e faz referência a qualquer escopo anexo. Quando a função anônima é criada no exemplo acima, ela tem uma referência ao escopo em que foi criada, que é bindo escopo. Isso é conhecido como "fechamento".

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

Quando você tenta acessar uma variável, essa "cadeia de escopo" é direcionada para encontrar uma variável com o nome fornecido - se o escopo atual não contiver a variável, você olha para o próximo escopo na cadeia e assim por diante até chegar o escopo global. Quando a função anônima é retornada e bindtermina a execução, a função anônima ainda tem uma referência ao bindescopo de tal modo que bindo escopo não "desaparece".

Dado todo o exposto, agora você deve entender como o escopo funciona no exemplo a seguir e por que a técnica para passar uma função em torno de "pré-vinculado" com um valor específico thisdela terá quando for chamada de obras:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"
Jonny Buchanan
fonte
"Quando uma referência a uma função é passada, nenhuma informação adicional sobre de onde ela foi passada é carregada com ela" obrigado @insin por isso.
Alex Marandon
9

Esse comportamento é definido? É seguro para vários navegadores?

Sim. E sim.

Existe algum raciocínio subjacente ao porquê dessa maneira ...

O significado de thisé bastante simples de deduzir:

  1. Se thisfor usado dentro de uma função construtora, e a função foi chamada com a newpalavra - chave, thisrefere-se ao objeto que será criado. thiscontinuará a significar o objeto, mesmo em métodos públicos.
  2. Se thisfor usado em qualquer outro lugar, incluindo funções protegidas aninhadas , refere-se ao escopo global (que no caso do navegador é o objeto de janela).

O segundo caso é obviamente uma falha de design, mas é muito fácil contornar isso usando fechamentos.

Rakesh Pai
fonte
4

Nesse caso, o interno thisé vinculado ao objeto global em vez da thisvariável da função externa. É assim que a linguagem é projetada.

Veja "JavaScript: The Good Parts", de Douglas Crockford, para uma boa explicação.

Santiago Cepas
fonte
4

Encontrei um bom tutorial sobre o ECMAScript neste

Um valor desse é um objeto especial relacionado ao contexto de execução. Portanto, pode ser nomeado como um objeto de contexto (ou seja, um objeto em que contexto o contexto de execução é ativado).

Qualquer objeto pode ser usado como esse valor do contexto.

a este valor é uma propriedade do contexto de execução, mas não uma propriedade do objeto variável.

Esse recurso é muito importante, porque, ao contrário das variáveis, esse valor nunca participa do processo de resolução do identificador. Ou seja, ao acessar isso em um código, seu valor é obtido diretamente do contexto de execução e sem nenhuma pesquisa na cadeia de escopo. O valor disso é determinado apenas uma vez ao entrar no contexto.

No contexto global, esse valor é o próprio objeto global (ou seja, esse valor aqui é igual ao objeto variável)

No caso de um contexto de função, esse valor em cada chamada de função pode ser diferente

Referência Javascript-the-core e Chapter-3-this

Damodaran
fonte
" No contexto global, esse valor é o próprio objeto global (ou seja, esse valor aqui é igual ao objeto variável) ". O objeto global faz parte do contexto de execução global, assim como o (es4) "objeto variável" e o registro de ambiente ES5. Mas elas são entidades diferentes do objeto global (por exemplo, um registro de ambiente não pode ser referenciado diretamente, é proibido pela especificação, mas o objeto global pode ser).
RobG