Herança prototipada
Com exceção de null
e undefined
, cada tipo de dados primitivo tem uma
prototype, um wrapper de objeto correspondente que fornece métodos para trabalhar
com valores. Quando uma pesquisa de método ou propriedade é invocada em um primitivo,
O JavaScript envolve o primitivo nos bastidores e chama o método ou
realiza a pesquisa de propriedades no objeto wrapper.
Por exemplo, um literal de string não tem métodos próprios, mas é possível chamar a função
O método .toUpperCase()
graças ao objeto String
correspondente
wrapper:
"this is a string literal".toUpperCase();
> THIS IS A STRING LITERAL
Isso é chamado de herança de protótipo, ou seja, herdar propriedades e métodos do construtor correspondente de um valor.
Number.prototype
> Number { 0 }
> constructor: function Number()
> toExponential: function toExponential()
> toFixed: function toFixed()
> toLocaleString: function toLocaleString()
> toPrecision: function toPrecision()
> toString: function toString()
> valueOf: function valueOf()
> <prototype>: Object { … }
Você pode criar primitivos usando esses construtores, em vez de apenas definir
pelo valor. Por exemplo, usar o construtor String
cria uma
objeto de string, não um literal de string: um objeto que não contém apenas a string
valor, mas todas as propriedades e métodos herdados do construtor.
const myString = new String( "I'm a string." );
myString;
> String { "I'm a string." }
typeof myString;
> "object"
myString.valueOf();
> "I'm a string."
Na maioria das vezes, os objetos resultantes se comportam como os valores que usamos para
defini-los. Por exemplo, mesmo que definir um valor numérico usando o método
O construtor new Number
resulta em um objeto que contém todos os métodos e
do protótipo Number
, é possível usar operadores matemáticos
esses objetos da mesma forma que você faria com literais de número:
const numberOne = new Number(1);
const numberTwo = new Number(2);
numberOne;
> Number { 1 }
typeof numberOne;
> "object"
numberTwo;
> Number { 2 }
typeof numberTwo;
> "object"
numberOne + numberTwo;
> 3
Muito raramente você precisará usar esses construtores, pois o JavaScript a herança prototipada significa que elas não oferecem benefício prático. Criando primitivos usando construtores também podem levar a resultados inesperados, pois o resultado é um objeto, não um literal simples:
let stringLiteral = "String literal."
typeof stringLiteral;
> "string"
let stringObject = new String( "String object." );
stringObject
> "object"
Isso pode complicar o uso de operadores de comparação rigorosos:
const myStringLiteral = "My string";
const myStringObject = new String( "My string" );
myStringLiteral === "My string";
> true
myStringObject === "My string";
> false
Inserção automática de ponto e vírgula (ASI)
Ao analisar um script, os intérpretes de JavaScript podem usar um recurso chamado a inserção automática de ponto e vírgula (ASI) para tentar corrigir instâncias de pontos e vírgulas. Se o analisador JavaScript encontrar um token não permitido, ele tenta adicionar um ponto e vírgula antes desse token para corrigir o possível erro de sintaxe, já que desde que uma ou mais das condições a seguir sejam verdadeiras:
- O token é separado do anterior por uma quebra de linha.
- O token é
}
. - O token anterior é
)
, e o ponto e vírgula inserido seria o final ponto e vírgula em uma instruçãodo
...while
.
Para mais informações, consulte as regras ASI.
Por exemplo, a omissão do ponto e vírgula após as seguintes instruções não causará uma erro de sintaxe devido ao ASI:
const myVariable = 2
myVariable + 3
> 5
No entanto, o ASI não pode contabilizar vários extratos na mesma linha. Se você escreva mais de uma instrução na mesma linha, separe-as com Ponto e vírgula:
const myVariable = 2 myVariable + 3
> Uncaught SyntaxError: unexpected token: identifier
const myVariable = 2; myVariable + 3;
> 5
A ASI é uma tentativa de correção de erros, não um tipo de flexibilidade sintática criada em JavaScript. Use ponto e vírgula quando apropriado para não depender para produzir o código correto.
Modo restrito
Os padrões que regem a forma como o JavaScript é escrito evoluíram muito além qualquer coisa considerada durante o início do design da linguagem. Cada nova alteração feita em O comportamento esperado do JavaScript precisa evitar erros em sites mais antigos.
O ES5 resolve alguns problemas antigos com a semântica do JavaScript sem
de quebrar implementações existentes introduzindo o "modo estrito", uma maneira de ativar
em um conjunto mais restritivo de regras de linguagem para um script inteiro ou
função individual. Para ativar o modo estrito, use o literal de string
"use strict"
, seguido por ponto e vírgula na primeira linha de um script ou
função:
"use strict";
function myFunction() {
"use strict";
}
O modo estrito impede que certos tipos de ações ou recursos descontinuados, gera
erros explícitos no lugar de "silenciosos" comuns, e proíbe o uso de
que podem colidir com futuros recursos de linguagem. Por exemplo, no início
decisões de design sobre escopo variável
aumentou a probabilidade de os desenvolvedores "poluirem" erroneamente o escopo global quando
declarar uma variável, independentemente do contexto, omitindo o
Palavra-chave var
:
(function() {
mySloppyGlobal = true;
}());
mySloppyGlobal;
> true
Os tempos de execução modernos em JavaScript não podem corrigir esse comportamento sem o risco de quebrar qualquer site que dependa dele, por engano ou deliberadamente. Em vez disso, o JavaScript moderno impede isso, permitindo que os desenvolvedores optem por para novos trabalhos e a ativação do modo estrito por padrão somente no contexto de novos recursos de linguagem que não corrompem as implementações legadas:
(function() {
"use strict";
mySloppyGlobal = true;
}());
> Uncaught ReferenceError: assignment to undeclared variable mySloppyGlobal
Escreva "use strict"
como um
literal de string.
Um literal de modelo
(use strict
) não vai funcionar. Você também precisa incluir "use strict"
antes de qualquer
código executável no contexto pretendido. Caso contrário, o intérprete a ignorará.
(function() {
"use strict";
let myVariable = "String.";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String."
> Uncaught ReferenceError: assignment to undeclared variable sloppyGlobal
(function() {
let myVariable = "String.";
"use strict";
console.log( myVariable );
sloppyGlobal = true;
}());
> "String." // Because there was code prior to "use strict", this variable still pollutes the global scope
Por referência, por valor
Qualquer variável, incluindo propriedades de um objeto, parâmetros de função e elementos em uma matriz set ou mapa, pode conter um primitivo ou um valor de referência.
Quando um valor primitivo é atribuído de uma variável a outra, o código mecanismo cria uma cópia desse valor e a atribui à variável.
Quando você atribui um objeto (instâncias de classe, matrizes e funções) a um em vez de criar uma nova cópia desse objeto, a variável contém uma referência à posição armazenada do objeto na memória. Por isso, mudar um objeto referenciado por uma variável muda o objeto que está sendo referenciado, não apenas um valor contido por essa variável. Por exemplo, se você inicializar um novo variável por uma variável contendo uma referência de objeto e, em seguida, use o novo para adicionar uma propriedade a esse objeto, a propriedade e seu valor são adicionados ao objeto original:
const myObject = {};
const myObjectReference = myObject;
myObjectReference.myProperty = true;
myObject;
> Object { myProperty: true }
Isso é importante não só para alterar objetos, mas também para executar
comparações, porque a igualdade estrita entre objetos exige que ambas as variáveis sejam
referenciar o mesmo objeto para ser avaliado como true
. Ele não pode fazer referência
objetos diferentes, mesmo que sejam estruturalmente idênticos:
const myObject = {};
const myReferencedObject = myObject;
const myNewObject = {};
myObject === myNewObject;
> false
myObject === myReferencedObject;
> true
Alocação de memória
O JavaScript usa o gerenciamento automático de memória, ou seja, a memória não precisa explicitamente alocada ou desalocada durante o desenvolvimento. os detalhes dos mecanismos JavaScript de gerenciamento de memória estão além escopo deste módulo, entender como a memória é alocada oferece informações contexto para trabalhar com valores de referência.
Existem duas "áreas" na memória: a "pilha" e a "pilha". A pilha armazena dados estáticos (valores primitivos e referências a objetos) porque quantidade fixa de espaço necessária para armazenar esses dados pode ser alocada antes que o o script é executado. A heap armazena objetos, que precisam de espaço alocado dinamicamente porque o tamanho delas pode mudar durante a execução. Um processo libera a memória chamada "coleta de lixo", que remove objetos sem referências memória.
A linha de execução principal
O JavaScript é uma linguagem basicamente com uma sequência modelo de execução, o que significa que ele pode executar apenas uma tarefa por vez. Esse contexto de execução sequencial é chamado de linha de execução principal.
A linha de execução principal é compartilhada por outras tarefas do navegador, como análise de HTML, renderizar e renderizar novamente partes da página, executar animações CSS e lidar com interações do usuário que vão do simples (como destacar texto) a complexo (como interagir com elementos de formulários). Os fornecedores do navegador encontraram maneiras de otimizar as tarefas realizadas pela linha de execução principal, mas mais complexas os scripts ainda podem usar muito dos recursos da linha de execução principal e afetar desempenho da página.
Algumas tarefas podem ser executadas linhas de execução em segundo plano chamadas Web Workers, com algumas limitações:
- As linhas de execução de worker só podem agir em arquivos JavaScript independentes.
- Eles reduziram significativamente ou não o acesso à janela e à interface do navegador.
- Eles estão limitados em como pode se comunicar com a linha de execução principal.
Essas limitações os tornam ideais para tarefas focadas e que consomem muitos recursos poderiam ocupar a linha de execução principal.
A pilha de chamadas
A estrutura de dados usada para gerenciar "contextos de execução", ou seja, o código ativamente executada, é uma lista chamada pilha de chamadas (geralmente apenas "a pilha"). Quando um script é executado pela primeira vez, o intérprete de JavaScript cria um "contexto de execução global" e o envia para a pilha de chamadas, dentro desse contexto global executadas uma de cada vez, de cima a fundo. Quando o intérprete encontra uma chamada de função enquanto executa o contexto global, ele envia um "contexto de execução de função" nessa chamada para o topo da pilha, pausa o contexto de execução global e executa a função contexto de execução.
Cada vez que uma função é chamada, o contexto de execução da função para essa chamada é empurrado para o topo da pilha, logo acima do contexto de execução atual. A pilha de chamadas opera em uma pilha o que significa que chamada de função recente, que é a mais alta na pilha, é executada e continua até que o problema seja resolvido. Quando essa função é concluída, o intérprete a remove da pilha de chamadas e o contexto de execução que contém essa chamada de função se torna o item mais alto na pilha novamente e retoma a execução.
Esses contextos de execução capturam todos os valores necessários para a execução. Eles
também estabelecem as variáveis e funções disponíveis dentro do escopo
baseada em seu contexto pai e determinar e definir o valor da
Palavra-chave this
no contexto da função.
O loop de eventos e a fila de callbacks
Essa execução sequencial significa que tarefas assíncronas que incluem chamadas de
funções, como buscar dados de um servidor, responder à interação do usuário,
ou aguardando timers definidos com setTimeout
ou setInterval
, bloquearia
linha de execução principal até que a tarefa seja concluída ou interromper inesperadamente o
contexto de execução atual no momento em que o contexto de execução da função de callback
é adicionado à pilha. Para resolver isso, o JavaScript gerencia tarefas assíncronas
usando um "modelo de simultaneidade" orientado por eventos composta pelo "loop de eventos", e o
"fila de callback" (às vezes chamada de "fila de mensagens").
Quando uma tarefa assíncrona é executada na linha de execução principal, o callback o contexto de execução da função é colocado na fila de callback, não na parte superior pilha de chamadas. O loop de eventos é um padrão às vezes chamado de reator, que continuamente pesquisa o status da pilha de chamadas e da fila de callbacks. Se houver tarefas a fila de callback e o loop de eventos determina que a pilha de chamadas está vazia. as tarefas da fila de callback são enviadas à pilha, uma de cada vez, para serem executada.