Os objetos geralmente são criados para representar entidades do mundo real, como usuários, pedidos e assim por diante:
deixe usuário = { nome: "João", idade: 30 };
E, no mundo real, um usuário pode agir : selecionar algo no carrinho de compras, fazer login, sair etc.
As ações são representadas em JavaScript por funções nas propriedades.
Para começar, vamos ensinar o user
a dizer olá:
deixe usuário = { nome: "João", idade: 30 }; user.sayHi = function() { alerta("Olá!"); }; user.sayHi(); // Olá!
Aqui acabamos de usar uma expressão de função para criar uma função e atribuí-la à propriedade user.sayHi
do objeto.
Então podemos chamá-lo de user.sayHi()
. O usuário agora pode falar!
Uma função que é propriedade de um objeto é chamada de método .
Então, aqui temos um método sayHi
do objeto user
.
Claro, poderíamos usar uma função pré-declarada como método, assim:
deixe usuário = { // ... }; // primeiro, declare function digaOi() { alerta("Olá!"); } // então adiciona como um método user.sayHi = digaOi; user.sayHi(); // Olá!
Programação orientada a objetos
Quando escrevemos nosso código usando objetos para representar entidades, isso é chamado de programação orientada a objetos, resumindo: “OOP”.
OOP é algo grande, uma ciência interessante por si só. Como escolher as entidades certas? Como organizar a interação entre eles? Isso é arquitetura, e existem ótimos livros sobre esse assunto, como “Design Patterns: Elements of Reusable Object-Oriented Software” de E. Gamma, R. Helm, R. Johnson, J. Vissides ou “Object-Oriented Analysis and Design with Aplicações” por G. Booch e muito mais.
Existe uma sintaxe mais curta para métodos em um objeto literal:
// esses objetos fazem o mesmo usuário = { digaOi: function() { alerta("Olá"); } }; // a abreviação do método parece melhor, certo? usuário = { sayHi() { // igual a "sayHi: function(){...}" alerta("Olá"); } };
Conforme demonstrado, podemos omitir "function"
e apenas escrever sayHi()
.
Para dizer a verdade, as notações não são totalmente idênticas. Existem diferenças sutis relacionadas à herança de objetos (a serem abordadas posteriormente), mas por enquanto elas não importam. Em quase todos os casos, a sintaxe mais curta é preferida.
É comum que um método de objeto precise acessar as informações armazenadas no objeto para realizar seu trabalho.
Por exemplo, o código dentro de user.sayHi()
pode precisar do nome do user
.
Para acessar o objeto, um método pode usar a palavra-chave this
.
O valor this
é o objeto “antes do ponto”, aquele usado para chamar o método.
Por exemplo:
deixe usuário = { nome: "João", idade: 30, digaOi() { // "este" é o "objeto atual" alerta(este.nome); } }; user.sayHi(); // John
Aqui durante a execução de user.sayHi()
, o valor this
será user
.
Tecnicamente, também é possível acessar o objeto sem this
, referenciando-o através da variável externa:
deixe usuário = { nome: "João", idade: 30, digaOi() { alerta(usuário.nome); // "usuário" em vez de "este" } };
…Mas esse código não é confiável. Se decidirmos copiar user
para outra variável, por exemplo, admin = user
e substituir user
por outra coisa, então ele acessará o objeto errado.
Isso é demonstrado abaixo:
deixe usuário = { nome: "João", idade: 30, digaOi() { alerta(usuário.nome); // leva a um erro } }; deixe admin = usuário; usuário = nulo; // sobrescrever para tornar as coisas óbvias admin.sayHi(); // TypeError: Não é possível ler a propriedade 'nome' de null
Se usássemos this.name
em vez de user.name
dentro do alert
, o código funcionaria.
Em JavaScript, a palavra-chave this
se comporta de maneira diferente da maioria das outras linguagens de programação. Pode ser usado em qualquer função, mesmo que não seja um método de um objeto.
Não há erro de sintaxe no exemplo a seguir:
function digaOi() { alerta(este.nome); }
O valor this
é avaliado durante o tempo de execução, dependendo do contexto.
Por exemplo, aqui a mesma função é atribuída a dois objetos diferentes e tem “isto” diferente nas chamadas:
deixe usuário = {nome: "John" }; deixe admin = {nome: "Admin" }; function digaOi() { alerta(este.nome); } // usa a mesma função em dois objetos usuário.f = digaOi; admin.f = digaOi; // essas chamadas têm isso diferente // "this" dentro da função é o objeto "antes do ponto" usuário.f(); // John (este == usuário) administrador.f(); // Admin (isto == administrador) administrador['f'](); // Admin (ponto ou colchetes acessam o método – não importa)
A regra é simples: se obj.f()
for chamado, então this
é obj
durante a chamada de f
. Portanto, é user
ou admin
no exemplo acima.
Chamando sem objeto: this == undefined
Podemos até chamar a função sem nenhum objeto:
function digaOi() { alerta(este); } digaOi(); // indefinido
Neste caso, this
é undefined
no modo estrito. Se tentarmos acessar this.name
, ocorrerá um erro.
No modo não estrito, o valor de this
nesse caso será o objeto global ( window
em um navegador, falaremos disso mais tarde no capítulo Objeto global). Este é um comportamento histórico que "use strict"
.
Geralmente tal chamada é um erro de programação. Se houver this
dentro de uma função, ela espera ser chamada em um contexto de objeto.
As consequências da desvinculação this
Se você vem de outra linguagem de programação, provavelmente está acostumado com a ideia de um “bound this
”, onde os métodos definidos em um objeto sempre têm this
referenciando aquele objeto.
Em JavaScript this
é “gratuito”, seu valor é avaliado em tempo de chamada e não depende de onde o método foi declarado, mas sim de qual objeto está “antes do ponto”.
O conceito de tempo de execução avaliado this
vantagens e desvantagens. Por um lado, uma função pode ser reutilizada para diferentes objetos. Por outro lado, a maior flexibilidade cria mais possibilidades de erros.
Aqui nossa posição não é julgar se esta decisão de design de linguagem é boa ou ruim. Vamos entender como trabalhar com isso, como obter benefícios e evitar problemas.
As funções de seta são especiais: elas não têm o seu “próprio” this
. Se fizermos referência this
a partir de tal função, ele será retirado da função “normal” externa.
Por exemplo, aqui arrow()
usa this
do método user.sayHi()
externo:
deixe usuário = { primeiroNome: "Ilya", digaOi() { deixe seta = () => alert(this.firstName); seta(); } }; user.sayHi(); //Ilya
Esse é um recurso especial das funções de seta, é útil quando na verdade não queremos ter um this
separado, mas sim retirá-lo do contexto externo. Mais adiante no capítulo Funções de seta revisitadas, iremos mais profundamente nas funções de seta.
As funções armazenadas nas propriedades do objeto são chamadas de “métodos”.
Os métodos permitem que os objetos “ajam” como object.doSomething()
.
Os métodos podem referenciar o objeto como this
.
O valor this
é definido em tempo de execução.
Quando uma função é declarada, ela pode usar this
, mas this
não tem valor até que a função seja chamada.
Uma função pode ser copiada entre objetos.
Quando uma função é chamada na sintaxe do “método”: object.method()
, o valor this
durante a chamada é object
.
Observe que as funções de seta são especiais: elas não possuem this
. Quando this
é acessado dentro de uma função de seta, ele é obtido de fora.
importância: 5
Aqui a função makeUser
retorna um objeto.
Qual é o resultado de acessar seu ref
? Por que?
function makeUser() { retornar { nome: "João", ref: isto }; } deixe usuário = makeUser(); alerta(usuário.ref.nome); // Qual é o resultado?
Resposta: um erro.
Experimente:
function makeUser() { retornar { nome: "João", ref: isto }; } deixe usuário = makeUser(); alerta(usuário.ref.nome); // Erro: Não é possível ler a propriedade 'nome' de indefinido
Isso ocorre porque as regras que definem this
não consideram a definição do objeto. Apenas o momento da ligação importa.
Aqui o valor this
dentro de makeUser()
é undefined
, porque é chamado como uma função, não como um método com sintaxe de “ponto”.
O valor this
é um para toda a função, blocos de código e literais de objeto não a afetam.
Então ref: this
realmente leva this
atual da função.
Podemos reescrever a função e retornar a this
com valor undefined
:
função makeUser(){ devolva isso; // desta vez não há objeto literal } alerta(makeUser().nome); // Erro: Não é possível ler a propriedade 'nome' de indefinido
Como você pode ver, o resultado de alert( makeUser().name )
é igual ao resultado de alert( user.ref.name )
do exemplo anterior.
Aqui está o caso oposto:
function makeUser() { retornar { nome: "João", referência() { devolva isso; } }; } deixe usuário = makeUser(); alerta(usuário.ref().nome); // John
Agora funciona, porque user.ref()
é um método. E o valor this
é definido para o objeto antes de dot .
.
importância: 5
Crie uma calculator
de objetos com três métodos:
read()
solicita dois valores e os salva como propriedades b
a
.
sum()
retorna a soma dos valores salvos.
mul()
multiplica os valores salvos e retorna o resultado.
deixe calculadora = { // ... seu código ... }; calculadora.read(); alerta(calculadora.sum()); alerta(calculadora.mul());
Execute a demonstração
Abra uma sandbox com testes.
deixe calculadora = { soma() { retorne isto.a + isto.b; }, mul() { retorne isto.a * isto.b; }, ler() { isto.a = +prompt('a?', 0); isto.b = +prompt('b?', 0); } }; calculadora.read(); alerta(calculadora.sum()); alerta(calculadora.mul());
Abra a solução com testes em uma sandbox.
importância: 2
Há um objeto ladder
que permite subir e descer:
deixe escada = { passo: 0, acima() { este.passo++; }, abaixo() { esta.etapa--; }, showStep: function() { // mostra a etapa atual alerta(este.passo); } };
Agora, se precisarmos fazer diversas chamadas em sequência, podemos fazer assim:
escada.up(); escada.up(); escada.down(); escada.showStep(); //1 escada.down(); escada.showStep(); //0
Modifique o código de up
, down
e showStep
para tornar as chamadas encadeáveis, assim:
escada.up().up().down().showStep().down().showStep(); //mostra 1 e depois 0
Essa abordagem é amplamente utilizada em bibliotecas JavaScript.
Abra uma sandbox com testes.
A solução é retornar o próprio objeto a cada chamada.
deixe escada = { passo: 0, acima() { este.passo++; devolva isso; }, abaixo() { esta.etapa--; devolva isso; }, showStep() { alerta(este.passo); devolva isso; } }; escada.up().up().down().showStep().down().showStep(); //mostra 1 e depois 0
Também podemos escrever uma única chamada por linha. Para cadeias longas é mais legível:
escada .acima() .acima() .abaixo() .showStep() // 1 .abaixo() .showStep(); //0
Abra a solução com testes em uma sandbox.