O JavaScript inclui os recursos para programação orientada a objetos (POO). Na POO, você quer criar objetos (instâncias) a partir de "modelos" (classes) para que eles incluam certos dados e funcionalidades. As propriedades de dados são chamadas de campos no contexto da POO, e as propriedades de função são chamadas de métodos.
O JavaScript não tinha classes antes de elas serem adicionadas à especificação da linguagem em 2015, mas permitia programação orientada a objetos usando herança baseada em protótipos.
E mesmo que a palavra-chave class esteja disponível hoje em dia, o JavaScript ainda é uma linguagem baseada em protótipos.
Para entender o que significa ser uma linguagem baseada em protótipos e como o JavaScript realmente funciona, vamos voltar ao tempo em que não existiam classes.
No JavaScript, o modelo (classe) é implementado com uma função comum.
Quando uma função deve ser usada como esse modelo, ela é chamada de função construtora, e a convenção é que o nome da função comece com uma letra maiúscula.
As instâncias (objetos) são derivadas do modelo usando a palavra-chave new ao invocar a função construtora.
function Car() {
// ...
}
const myCar = new Car();
const yourCar = new Car();
É importante notar que, no JavaScript, as instâncias e a função construtora mantêm uma relação entre si mesmo depois que as instâncias foram criadas.
Todo objeto de instância inclui uma propriedade oculta e interna chamada de [[prototype]] na especificação da linguagem.
Ela guarda uma referência ao valor da chave prototype da função construtora.
Sim, você leu certo: uma função do JavaScript pode ter pares de chave/valor porque, por trás dos panos, ela também é um objeto.
Para resumir:
prototype do construtor (da função) se torna o protótipo da instância.Muitas vezes, você quer que todos os objetos derivados (instâncias) incluam alguns campos e que alguns valores iniciais sejam passados para eles quando o objeto é construído.
Isso pode ser feito com a palavra-chave this.
Dentro da função construtora, this representa o novo objeto que será criado via new.
this é retornado automaticamente pela função construtora quando ela é chamada com new.
Isso significa que podemos adicionar campos à nova instância adicionando-os a this na função construtora.
function Car(color, weight) {
this.color = color;
this.weight = weight;
this.engineRunning = false;
}
const myCar = new Car('red', '2mt');
myCar.color;
// => 'red'
myCar.engineRunning;
// => false
Os métodos são adicionados por meio da propriedade prototype da função construtora.
Dentro de um método, você pode acessar os campos da instância via this.
Isso funciona por causa da seguinte regra geral.
Quando uma função é chamada como método de um objeto, seu
thisrecebe o objeto no qual o método foi chamado. 1
function Car() {
this.engineRunning = false;
// ...
}
Car.prototype.startEngine = function () {
this.engineRunning = true;
};
Car.prototype.addGas = function (litre) {
// ...
};
const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true
myCar no exemplo acima é um objeto comum do JavaScript e, se o inspecionássemos (por exemplo, no console do navegador), não encontraríamos uma propriedade startEngine com uma função como valor diretamente dentro do objeto myCar.
Então como o código acima funciona, afinal?
O segredo aqui é a cadeia de protótipos.
Quando você tenta acessar qualquer propriedade (campo ou método) de um objeto, o JavaScript primeiro verifica se a chave correspondente pode ser encontrada diretamente no próprio objeto.
Se não, ele continua procurando a chave no objeto referenciado pela propriedade [[prototype]] do objeto original.
Como mencionado antes, em nosso exemplo [[prototype]] aponta para a propriedade prototype da função construtora.
É aí que o JavaScript encontraria a função startEngine, porque nós a adicionamos lá.
function Car() {
// ...
}
Car.prototype.startEngine = function () {
// ...
};
E a cadeia não termina aí.
A propriedade [[prototype]] de Car.prototype (myCar.[[prototype]].[[prototype]]) referencia Object.prototype (a propriedade prototype da função construtora Object).
Ela contém métodos gerais que estão disponíveis para todos os objetos do JavaScript, como toString().
Concluindo, você pode chamar myCar.toString() e esse método existirá porque o JavaScript procura por ele em toda a cadeia de protótipos.
Repare que a cadeia de protótipos só é percorrida ao se obter um valor. Definir uma propriedade diretamente ou excluir uma propriedade de um objeto de instância afeta apenas aquela instância específica. Isso pode não ser o que você espera se estiver acostumado com uma linguagem de herança baseada em classes.
Hoje em dia, o JavaScript permite definir classes com a palavra-chave class.
Isso foi adicionado à especificação da linguagem em 2015.
Por um lado, isso oferece açúcar sintático que torna as classes mais fáceis de ler e escrever.
A nova sintaxe é mais parecida com a forma como as classes são escritas em linguagens como C++ ou Java.
Desenvolvedores que vêm dessas linguagens têm mais facilidade para se adaptar.
Por outro lado, a sintaxe de classes abre caminho para novos recursos da linguagem que não estão disponíveis na sintaxe de protótipos.
Com a nova sintaxe, as classes são definidas com a palavra-chave class, seguidas pelo nome da classe e pelo corpo da classe entre chaves.
O corpo contém a definição da função construtora, ou seja, um método especial com o nome constructor.
Essa função funciona exatamente como a função construtora na sintaxe de protótipos.
O corpo da classe também contém todas as definições de métodos.
A sintaxe para os métodos é parecida com a notação abreviada que vimos para adicionar funções como valores dentro de um objeto, veja Conceito Objetos.
class Car {
constructor(color, weight) {
this.color = color;
this.weight = weight;
this.engineRunning = false;
}
startEngine() {
this.engineRunning = true;
}
addGas(litre) {
// ...
}
}
const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true
Tenha em mente que, por trás dos panos, o JavaScript ainda é uma linguagem baseada em protótipos. Todos os mecanismos que aprendemos na seção "Sintaxe de protótipos" acima continuam valendo.
Por padrão, todos os campos de instância são públicos no JavaScript. Eles podem ser acessados e atribuídos diretamente.
No entanto, existe uma convenção estabelecida de que campos e métodos que começam com um sublinhado devem ser tratados como privados. Eles nunca devem ser acessados diretamente de fora da classe.
Campos privados às vezes são acompanhados de getters e setters.
Com as palavras-chave get e set, você pode definir funções que são executadas quando uma propriedade com o mesmo nome da função é acessada ou atribuída.
class Car {
constructor() {
this._mileage = 0;
}
get mileage() {
return this._mileage;
}
set mileage(value) {
throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`);
// Just an example, usually you would not provide a setter in this case.
}
}
const myCar = new Car();
myCar.mileage;
// => 0
myCar.mileage = 100;
// => Error: Mileage cannot be manipulated, 100 is ignored.
Exemplos de this - Como método de objeto, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method (consultado em 3 de dezembro de 2021) ↩
Neste exercício, você vai simular um sistema de computador baseado em janelas. Você vai criar algumas janelas que podem ser movidas e redimensionadas. A imagem a seguir é representativa dos valores com os quais você vai trabalhar abaixo.
<--------------------- screenSize.width --------------------->
^ ┌────────────────────────────────────────────────────────────┐
| │ │
| │ position.x, _ │
| │ position.y \ │
| │ \<----- size.width -----> │
| │ ^ *──────────────────────┐ │
| │ | │ title │ │
| │ | ├──────────────────────┤ │
screenSize.height │ | │ │ │
| │ size.height │ │ │
| │ | │ contents │ │
| │ | │ │ │
| │ | │ │ │
| │ v └──────────────────────┘ │
| │ │
| │ │
v └────────────────────────────────────────────────────────────┘
📣 Para praticar sua ampla variedade de habilidades em JavaScript, tente resolver as tarefas 1 e 2 com sintaxe de protótipo e as tarefas restantes com sintaxe de classe.
Size para armazenar as dimensões da janelaDefina uma classe (função construtora) chamada Size.
Ela deve ter dois campos, width e height, que armazenam as dimensões atuais da janela.
A função construtora deve aceitar valores iniciais para esses campos.
A largura é fornecida como primeiro parâmetro e a altura como segundo.
A largura e a altura padrão devem ser 80 e 60, respectivamente.
Além disso, defina um método resize(newWidth, newHeight) que recebe uma nova largura e uma nova altura como parâmetros e altera os campos para refletir o novo tamanho.
const size = new Size(1080, 764);
size.width;
// => 1080
size.height;
// => 764
size.resize(1920, 1080);
size.width;
// => 1920
size.height;
// => 1080
Position para armazenar a posição de uma janelaDefina uma classe (função construtora) chamada Position com dois campos, x e y, que armazenam a posição horizontal e vertical atuais, respectivamente, do canto superior esquerdo da janela.
A função construtora deve aceitar valores iniciais para esses campos.
O valor de x é fornecido como primeiro parâmetro e o valor de y como segundo.
O valor padrão deve ser 0 para ambos os campos.
A posição (0, 0) é o canto superior esquerdo da tela, com os valores de x aumentando conforme você se move para a direita e os valores de y aumentando conforme você se move para baixo.
Defina também um método move(newX, newY) que recebe novos parâmetros x e y e altera as propriedades para refletir a nova posição.
const point = new Position();
point.x;
// => 0
point.y;
// => 0
point.move(100, 200);
point.x;
// => 100
point.y;
// => 200
ProgramWindow
Defina uma classe ProgramWindow com os seguintes campos:
screenSize: guarda um valor fixo do tipo Size com width 800 e height 600size: guarda um valor do tipo Size; o valor inicial é o valor padrão da instância de Size
position: guarda um valor do tipo Position; o valor inicial é o valor padrão da instância de Position
Quando a janela é aberta (criada), ela sempre tem o tamanho e a posição padrão no início.
const programWindow = new ProgramWindow();
programWindow.screenSize.width;
// => 800
// Similar for the other fields.
Observação: o nome ProgramWindow é usado em vez de Window para diferenciar a classe da classe Window nativa que existe em ambientes de navegador.
A classe ProgramWindow deve incluir um método resize.
Ele deve aceitar um parâmetro do tipo Size como entrada e tentar redimensionar a janela para o tamanho especificado.
No entanto, o novo tamanho não pode ultrapassar certos limites.
x = 400, y = 300 e for solicitado um redimensionamento para height = 400, width = 300, a janela seria redimensionada para height = 300, width = 300, pois a tela não é grande o suficiente na direção y para acomodar totalmente a solicitação.const programWindow = new ProgramWindow();
const newSize = new Size(600, 400);
programWindow.resize(newSize);
programWindow.size.width;
// => 600
programWindow.size.height;
// => 400
Além da funcionalidade de redimensionamento, a classe ProgramWindow também deve incluir um método move.
Ele deve aceitar um parâmetro do tipo Position como entrada.
O método move é semelhante ao resize, mas ajusta a posição da janela para o valor solicitado, em vez do tamanho.
Assim como em resize, a nova posição não pode ultrapassar certos limites.
x quanto para y.x = 250, y = 100 e for solicitada uma movimentação para x = 600, y = 200, a janela seria movida para x = 550, y = 200, pois a tela não é grande o suficiente na direção x para acomodar totalmente a solicitação.const programWindow = new ProgramWindow();
const newPosition = new Position(50, 100);
programWindow.move(newPosition);
programWindow.position.x;
// => 50
programWindow.position.y;
// => 100
Implemente uma função changeWindow que aceita uma instância de ProgramWindow como entrada e altera a janela para o tamanho e a posição especificados.
A função deve retornar a instância de ProgramWindow que foi passada, depois de aplicadas as alterações.
A janela deve receber uma largura de 400, uma altura de 300 e ser posicionada em x = 100, y = 150.
const programWindow = new ProgramWindow();
changeWindow(programWindow);
programWindow.size.width;
// => 400
// Similar for the other fields.
Crie sua conta no Exercism para aprender e dominar JavaScript com 37 conceitos159 exercícios e mentoria humana de verdade, tudo de graça.