Apêndice

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ção do...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.