Comparado com C/C ++, o processamento de JavaScript na memória no JavaScript que usamos nos fez prestar mais atenção à escrita da lógica de negócios no desenvolvimento. No entanto, com a complexidade contínua dos negócios, o desenvolvimento de aplicativos de página única, aplicativos móveis HTML5, programas Node.js etc., o transbordamento de atraso e memória causados por problemas de memória no JavaScript não se tornaram mais desconhecidos.
Este artigo discutirá o uso e otimização da memória a partir do nível de idioma do JavaScript. Desde os aspectos com os quais todos estão familiarizados ou um pouco ouvidos, às coisas que todos não notarão na maioria das vezes, nós os analisamos uma a uma.
1. Gerenciamento de memória no nível do idioma
1.1 Escopo
O escopo é um mecanismo operacional muito importante na programação JavaScript. Não atrai a atenção dos iniciantes na programação JavaScript síncrona, mas na programação assíncrona, boas habilidades de controle de escopo se tornaram uma habilidade necessária para os desenvolvedores de JavaScript. Além disso, o escopo desempenha um papel crucial no gerenciamento de memória JavaScript.
No JavaScript, as funções podem ser chamadas, com declarações e escopos globais que podem ser escovos.
Conforme mostrado no código a seguir como exemplo:
A cópia do código é a seguinte:
var foo = function () {
var local = {};
};
foo ();
console.log (local); // => indefinido
var bar = function () {
local = {};
};
bar();
console.log (local); // => {}
Aqui, definimos a função foo () e a função bar (). Sua intenção é definir uma variável chamada local. Mas o resultado final é completamente diferente.
Na função Foo (), usamos a instrução VAR para declarar que uma variável local é definida. Como um escopo é formado dentro do corpo da função, essa variável é definida no escopo. Além disso, não há processamento de extensão de escopo na função Foo (); portanto, após a execução da função, a variável local também é destruída. No entanto, essa variável não pode ser acessada no escopo externo.
Na função bar (), a variável local não é declarada usando uma instrução VAR, mas, em vez disso, define diretamente o local como uma variável global. Portanto, essa variável pode ser acessada pelo escopo externo.
A cópia do código é a seguinte:
local = {};
// A definição aqui é equivalente a
global.local = {};
1.2 Cadeia de escopo
Na programação JavaScript, você definitivamente encontrará cenários de ninho de funções de várias camadas, que é uma representação típica de cadeias de escopo.
Conforme mostrado no código a seguir:
A cópia do código é a seguinte:
function foo () {
var val = 'hello';
barra de função () {
function baz () {
global.val = 'mundo;'
}
baz ();
console.log (val); // => Olá
}
bar();
}
foo ();
Com base na explicação anterior sobre o escopo, você pode pensar que o resultado mostrado no código aqui é o mundo, mas o resultado real é olá. Muitos iniciantes começarão a se sentir confusos aqui, então vamos dar uma olhada em como esse código funciona.
Desde o JavaScript, a busca por identificadores variáveis começa no escopo atual e parece externa até o escopo global. Portanto, o acesso a variáveis no código JavaScript só pode ser executado para fora, mas não ao contrário.
A execução da função Baz () define uma variável global Val no escopo global. Na função bar (), ao acessar o identificador Val, o princípio de pesquisar de dentro para fora é: se não for encontrado no escopo da função da barra, ele vai para a camada anterior, ou seja, o escopo da função Foo ().
No entanto, a chave para confundir todo mundo é: esse acesso de identificador encontra uma variável correspondente no escopo da função Foo () e não continuará a parecer para fora; portanto, a variável global Val definida na função BAZ () não tem efeito nesse acesso variável.
1.3 Fechamento
Sabemos que a pesquisa de identificador no JavaScript segue o princípio de dentro para fora. No entanto, com a complexidade da lógica de negócios, uma única ordem de entrega está longe de atender às crescentes novas necessidades.
Vamos dar uma olhada no seguinte código:
A cópia do código é a seguinte:
function foo () {
var local = 'hello';
Return function () {
retornar local;
};
}
var bar = foo ();
console.log (bar ()); // => Olá
A tecnologia que permite que o escopo externo acesse o escopo interno, como mostrado aqui, é o fechamento. Graças à aplicação de funções de ordem superior, o escopo da função Foo () é "estendido".
A função Foo () retorna uma função anônima, que existe dentro do escopo da função Foo (), para que você possa acessar a variável local dentro do escopo da função Foo () e salvar sua referência. Como essa função retorna diretamente a variável local, a função bar () pode ser executada diretamente no escopo externo para obter a variável local.
Os fechamentos são uma característica de alto nível do JavaScript, e podemos usá-los para obter efeitos cada vez mais complexos para atender às diferentes necessidades. No entanto, deve -se notar que, como a função com referência de variável interna é retirada da função, as variáveis nesse escopo não serão destruídas após a execução da função até que todas as referências às variáveis internas sejam canceladas. Portanto, a aplicação de fechamentos pode facilmente fazer com que a memória não seja livre.
2. Mecanismo de reciclagem de memória do JavaScript
Aqui vou pegar o mecanismo V8 usado pelo Chrome e Node.js e lançado pelo Google como um exemplo para introduzir brevemente o mecanismo de reciclagem de memória do JavaScript. Para um conteúdo mais detalhado, você pode comprar o livro do meu bom amigo Park Ling, "Node.js, profundo e fácil de entender" para o aprendizado, que é uma introdução muito detalhada no capítulo "Controle de memória".
No V8, todos os objetos JavaScript são alocados através do "heap".
Quando declaramos e atribuímos valores no código, o V8 alocará uma parte dessa variável na memória da heap. Se a memória solicitada for insuficiente para armazenar essa variável, o V8 continuará solicitando memória até que o tamanho da pilha atinja o limite de memória do V8. Por padrão, o limite superior da memória de heap de V8 é de 1464 MB em sistemas de 64 bits e 732 MB em sistemas de 32 bits, que é de cerca de 1,4 GB e 0,7 GB.
Além disso, o V8 gerencia objetos JavaScript na memória da heap em gerações: a nova geração e a geração antiga. A nova geração são objetos JavaScript com ciclos de sobrevivência curtos, como variáveis temporárias, cordas, etc.; Enquanto a geração antiga são objetos com longos ciclos de sobrevivência após várias coleções de lixo, como controladores principais, objetos de servidor etc.
Os algoritmos de reciclagem de lixo sempre foram uma parte importante do desenvolvimento de linguagens de programação, e os algoritmos de reciclagem de lixo usados em V8 são principalmente os seguintes:
1. Algoritmo de limpeza: o gerenciamento do espaço da memória é realizado através da cópia, usada principalmente no espaço de memória da nova geração;
2. Algoritmo de varredura de marca e algoritmo de compacto de marca: a memória da pilha é classificada e reciclada através da marcação, usada principalmente para inspeção e reciclagem de objetos de antiga geração.
PS: As implementações mais detalhadas de coleta de lixo V8 podem ser aprendidas lendo livros, documentos e código fonte relacionados.
Vamos dar uma olhada em que circunstâncias o mecanismo JavaScript reciclará quais objetos.
2.1 Escopo e referência
Os iniciantes frequentemente acreditam erroneamente que, quando a função é executada, o objeto declarado dentro da função será destruído. Mas, de fato, esse entendimento não é rigoroso e abrangente, e é fácil ficar confuso com isso.
A referência é um mecanismo muito importante na programação de JavaScript, mas, estranhamente, a maioria dos desenvolvedores não prestará atenção a ele ou mesmo a entenderá. Referência refere -se ao relacionamento abstrato "Acesso ao código aos objetos". É um pouco semelhante aos ponteiros de C/C ++, mas não a mesma coisa. A referência também é o mecanismo mais crítico do mecanismo JavaScript na coleta de lixo.
O código a seguir é um exemplo:
A cópia do código é a seguinte:
// ......
var val = 'Hello World';
function foo () {
Return function () {
retornar val;
};
}
global.bar = foo ();
// ......
Depois de ler este código, você pode saber quais objetos ainda sobrevivem após a execução desta parte do código?
De acordo com os princípios relevantes, os objetos que não são reciclados e liberados neste código incluem Val e Bar (). O que exatamente os torna incapazes de serem reciclados?
Como o mecanismo JavaScript realiza a coleta de lixo? O algoritmo de coleta de lixo mencionado acima é usado apenas durante a reciclagem. Então, como sabe quais objetos podem ser reciclados e quais objetos precisam continuar a sobreviver? A resposta é uma referência a um objeto JavaScript.
No código JavaScript, mesmo se você simplesmente anotar um nome de variável como uma única linha sem fazer nada, o mecanismo JavaScript pensará que esse é um comportamento de acesso ao objeto e há uma referência ao objeto. Para garantir que o comportamento da coleta de lixo não afete a operação da lógica do programa, o mecanismo JavaScript não deve reciclar os objetos em uso, caso contrário, será confuso. Portanto, o padrão para julgar se um objeto está em uso é se ainda existe uma referência ao objeto. Mas, de fato, isso é um compromisso, porque as referências de JavaScript podem ser transferidas; portanto, pode haver algumas referências sendo trazidas ao escopo global, mas, de fato, não é mais necessário acessá -las na lógica de negócios e deve ser reciclado, mas o mecanismo JavaScript ainda acreditará rigidamente que o programa ainda precisa.
Como usar variáveis e referências na postura correta é a chave para otimizar o JavaScript do nível do idioma.
3. Otimize seu JavaScript
Finalmente chegou ao ponto. Muito obrigado por ver isso com paciência. Depois de tantas apresentações acima, acredito que você tem um bom entendimento do mecanismo de gerenciamento de memória do JavaScript. Então as seguintes habilidades farão você se sentir melhor.
3.1 Faça bom uso de funções
Se você tem o hábito de ler excelentes projetos de JavaScript, descobrirá que, ao desenvolver código JavaScript de front-end, muitos caras grandes geralmente usam uma função anônima para envolvê-lo na camada mais externa do código.
A cópia do código é a seguinte:
(function () {
// Código comercial principal
}) ();
Alguns são ainda mais avançados:
A cópia do código é a seguinte:
; (função (win, doc, $, indefinido) {
// Código comercial principal
}) (janela, documento, jQuery);
Mesmo soluções de carregamento modular de front-end, como requerjs, Seajs, Ozjs, etc. Todos adotam uma forma semelhante:
A cópia do código é a seguinte:
// requerjs
define (['jQuery'], function ($) {
// Código comercial principal
});
// SEAJS
define ('módulo', ['dep', 'subscore'], função ($, _) {
// Código comercial principal
});
Se você disser que muitos códigos de projetos de código aberto Node.js não são processados dessa maneira, então você está errado. Antes de realmente executar o código, o Node.js envolverá cada arquivo .js no seguinte formulário:
A cópia do código é a seguinte:
(função (exporta, requer, módulo, __dirname, __filename) {
// Código comercial principal
});
Quais são os benefícios de fazer isso? Todos sabemos que, no início do artigo, dissemos que o JavaScript pode ter funções escopo, com declarações e escopo global. Também sabemos que os objetos definidos no escopo global podem sobreviver até que o processo saia. Se for um objeto grande, será problemático. Por exemplo, algumas pessoas gostam de renderizar modelos em JavaScript:
A cópia do código é a seguinte:
<? php
$ db = mysqli_connect (servidor, usuário, senha, 'myApp');
$ tópicos = mysqli_query ($ db, "selecione * de tópicos;");
?>
<! doctype html>
<html lang = "en">
<head>
<meta charset = "utf-8">
<título> Você é um cara engraçado convidado por macacos? </title>
</head>
<Body>
<ul id = "tópicos"> </ul>
<script type = "text/tmpl" id = "tópico-tmpl">
<li>
<H1> <%= title%> </h1>
<p> <%= content%> </p>
</li>
</script>
<script type = "text/javascript">
var dados = <? php echo json_encode ($ tópicos); ?>;
var topictMpl = document.QuerySelector ('#tópico-tmpl'). Innerhtml;
var render = function (tmlp, view) {
var compilado = tmlp
.place (// n/g, '// n')
.place (/<%= ([/s/s]+?)%>/g, função (correspondência, código) {
return '" + Escape (' + code + ') +"';
});
compilado = [
'var res = "";',
'com (View || {}) {',
'res = "' + compilado + '";',,
'}',
'Return res;'
] .Join ('/n');
var fn = new function ('View', compilado);
retornar fn (visualização);
};
var tópicos = document.QuerySelector ('#tópicos');
função init ()
data.ForEach (função (tópico) {
tópicos.innerhtml += render (topictmpl, tópico);
});
}
init ();
</script>
</body>
</html>
Esse tipo de código geralmente pode ser visto nas obras dos novatos. Quais são os problemas aqui? Se a quantidade de dados obtidos no banco de dados for muito grande, a variável de dados ficará ociosa após o preenchimento do front-end a renderização do modelo. No entanto, como essa variável é definida no escopo global, o mecanismo JavaScript não o reciclará e a destruirá. Isso continuará a existir na memória da antiga geração até que a página seja fechada.
Mas se fizermos algumas modificações muito simples e embrulhar uma camada de funções fora do código lógico, o efeito será muito diferente. Após a conclusão da renderização da interface do usuário, a referência do código aos dados também é cancelada. Quando a função mais externa é executada, o mecanismo JavaScript começa a verificar os objetos e os dados podem ser reciclados.
3.2 Nunca defina variáveis globais
Acabamos de conversar sobre isso quando uma variável é definida no escopo global, o mecanismo JavaScript não o recicla e a destruirá por padrão. Isso continuará a existir na memória da antiga geração até que a página seja fechada.
Então sempre seguimos um princípio: nunca use variáveis globais. Embora as variáveis globais sejam realmente muito fáceis de desenvolver, os problemas causados pelas variáveis globais são muito mais graves do que a conveniência que ela traz.
Tornar as variáveis menos propensas a serem recicladas;
1. A confusão é facilmente causada quando várias pessoas colaboram;
2. É fácil ser interferido na cadeia de escopo.
3. Em conjunto com a função de embalagem acima, também podemos lidar com "variáveis globais" através de funções de embalagem.
3.3 variáveis manualmente não referenciadas
Se uma variável não for necessária no código comercial, a variável poderá ser desreferenciada manualmente para torná -la reciclada.
A cópia do código é a seguinte:
var dados = { / * alguns big data * /};
// blá blá blá
dados = nulo;
3.4 Faça bom uso de retornos de chamada
Além de usar o fechamento para acesso variável interno, também podemos usar a função de retorno de chamada agora muito popular para processamento de negócios.
A cópia do código é a seguinte:
função getData (retorno de chamada) {
var dados = 'Alguns big data';
retorno de chamada (nulo, dados);
}
getData (function (err, dados) {
console.log (dados);
As funções de retorno de chamada são uma tecnologia de estilo de passagem de continuação (CPS). Esse estilo de programação transfere o foco comercial da função do valor de retorno para a função de retorno de chamada. E tem muitos benefícios sobre o fechamento:
1. Se os parâmetros aprovados forem o tipo básico (como seqüências de caracteres, valores numéricos), os parâmetros formais passados na função de retorno de chamada serão copiados valores e será mais fácil ser reciclado após o uso do código comercial;
2. Através de retornos de chamada, além de concluir solicitações síncronas, também podemos usá -las em programação assíncrona, que é um estilo de escrita muito popular agora;
3. A função de retorno de chamada em si é geralmente uma função anônima temporária. Depois que a função de solicitação for executada, a referência à própria função de retorno de chamada será cancelada e ela será reciclada.
3.5 Bom gerenciamento de fechamento
Quando nossas necessidades de negócios (como ligação circular de eventos, atributos privados, retornos de chamada com argumentos etc.) devem usar o fechamento, tenha cuidado com os detalhes.
Pode -se dizer que os eventos de ligação ao loop são um curso obrigatório para começar com o fechamento de JavaScript. Vamos assumir um cenário: existem seis botões, correspondendo a seis eventos. Quando o usuário clica no botão, os eventos correspondentes são emitidos no local especificado.
A cópia do código é a seguinte:
var Btns = document.QuerySelectorAll ('. Btn'); // 6 elementos
var output = document.QuerySelector ('#output');
var events = [1, 2, 3, 4, 5, 6];
// Caso 1
for (var i = 0; i <btns.length; i ++) {
btns [i] .OnClick = function (evt) {
output.InnerText + = 'clicou' + eventos [i];
};
}
// Caso 2
for (var i = 0; i <btns.length; i ++) {
btns [i] .OnClick = (function (index) {
Função de retorno (EVT) {
output.InnerText + = 'Clicou' + events [index];
};
})(eu);
}
// Caso 3
for (var i = 0; i <btns.length; i ++) {
btns [i] .OnClick = (function (event) {
Função de retorno (EVT) {
output.innerText + = 'clicou' + evento;
};
}) (eventos [i]);
}
A primeira solução aqui é obviamente um erro de evento de ligação ao loop típico. Não vou explicar em detalhes aqui. Você pode se referir à minha resposta a um internúmulo em detalhes; A diferença entre a segunda e a terceira soluções está nos parâmetros passados no fechamento.
Os parâmetros aprovados no segundo esquema são o subscrito atual do loop, enquanto o último é passado diretamente para o objeto de evento correspondente. De fato, o último é mais adequado para grandes quantidades de aplicações de dados, porque, na programação funcional de JavaScript, os parâmetros passados em chamadas de função são objetos de tipos básicos; portanto, os parâmetros formais obtidos no corpo da função serão um valor de cópia, para que esse valor seja definido como uma variável local dentro do escopo do corpo da função. Após a conclusão da ligação do evento, a variável de eventos pode ser desreferenciada manualmente para reduzir o uso da memória no escopo externo. Além disso, quando um elemento é excluído, a função de escuta de eventos correspondente, o objeto de eventos e a função de fechamento também são destruídos e reciclados.
3.6 A memória não é um cache
O papel do cache no desenvolvimento de negócios desempenha um papel importante e pode reduzir o ônus dos recursos do espaço-tempo. Mas deve -se notar que você não deve usar a memória como cache facilmente. A memória é uma coisa de cada centímetro de terra para qualquer desenvolvimento do programa. Se não for um recurso muito importante, não o coloque diretamente na memória ou formule um mecanismo de expiração para destruir automaticamente o cache de expiração.
4. Verifique o uso da memória do JavaScript
No desenvolvimento diário, também podemos usar algumas ferramentas para analisar e solucionar o uso da memória no JavaScript.
4.1 Blink/WebKit navegador
No navegador Blink/Webkit (Chrome, Safari, Opera etc.), podemos usar a ferramenta de perfis de ferramentas de desenvolvedor para executar verificações de memória em nossos programas.
4.2 Verificação de memória em Node.js
No Node.js, podemos usar módulos Node-HeapDump e Node-Memwatch para verificação da memória.
A cópia do código é a seguinte:
var heapdump = requer ('heapdump');
var fs = requer ('fs');
var caminho = requer ('caminho');
fs.writefilesync (path.join (__ dirname, 'app.pid'), process.pid);
// ...
A cópia de código é a seguinte: <span style = "font-family: Georgia, 'Times New Roman', 'BitStream Charter', Times, serif; tamanho da fonte: 14px; altura de linha: 1.5em;"> Após a introdução do node-heapdump no código comercial, precisamos enviar um sinuto e o sigusr2 para o nó. Memória da pilha. </span>
Copie o código da seguinte
Dessa forma, haverá um arquivo instantâneo nomeado no formato heapdump- <sec>. Podemos abri -lo usando a ferramenta de perfis nas ferramentas de desenvolvedor do navegador e verificá -lo.
5. Resumo
O artigo está chegando em breve novamente. Este compartilhamento mostra principalmente o seguinte conteúdo:
1. JavaScript está intimamente relacionado ao uso da memória no nível do idioma;
2. Mecanismos de gerenciamento e reciclagem de memória em JavaScript;
3. Como usar a memória com mais eficiência para que o JavaScript produzido possa ser mais expandido e energético;
4. Como executar verificações de memória ao encontrar problemas de memória.
Espero que, ao aprender este artigo, você possa produzir um código JavaScript melhor para fazer sua mãe se sentir à vontade e seu chefe se sentir à vontade.