По сравнению с C/C ++, обработка JavaScript в памяти в JavaScript, которую мы используем, заставила нас уделять больше внимания написанию бизнес -логики в разработке. Однако с непрерывной сложностью бизнеса разработка одностраничных приложений, мобильных приложений HTML5, программ Node.js и т. Д., Задержка и переполнение памяти, вызванные проблемами памяти в JavaScript, больше не стали незнакомыми.
В этой статье будет обсуждаться использование памяти и оптимизация с языкового уровня JavaScript. От аспектов, с которыми все знакомы или немного услышаны, до вещей, которые каждый не заметит большую часть времени, мы проанализируем их один за другим.
1. Управление памятью на уровне языка
1.1 Область
Область - очень важный операционный механизм в программировании JavaScript. Это не привлекает внимания начинающих в синхронном программировании JavaScript, но при асинхронном программировании хорошие навыки управления объемом стали необходимым навыком для разработчиков JavaScript. Кроме того, Scope играет решающую роль в управлении памятью JavaScript.
В JavaScript могут быть вызваны функции, с утверждениями и глобальными областями, которые можно охватить.
Как показано в следующем коде в качестве примера:
Кода -копия выглядит следующим образом:
var foo = function () {
var local = {};
};
foo ();
Console.log (Local); // => не определен
var bar = function () {
local = {};
};
бар();
Console.log (Local); // => {}
Здесь мы определяем функцию foo () и функцию bar (). Их намерение состоит в том, чтобы определить переменную с именем локальной. Но конечный результат совершенно другой.
В функции Foo () мы используем оператор VAR, чтобы заявить, что локальная переменная определена. Поскольку в корпусе функции образуется область прицела, эта переменная определяется в области области. Более того, в функции Foo () нет обработки расширения сферы, поэтому после выполнения функции локальная переменная также уничтожается. Тем не менее, к этой переменной нельзя получить доступ во внешней области.
В функции Bar () локальная переменная не объявляется с использованием оператора VAR, а вместо этого она напрямую определяет локальную как глобальную переменную. Следовательно, эта переменная может быть доступна внешней областью.
Кода -копия выглядит следующим образом:
local = {};
// определение здесь эквивалентно
Global.local = {};
1.2 цепочка применения
В программировании JavaScript вы определенно столкнетесь с сценариями гнездования многослойной функции, что является типичным представлением цепочек объемов.
Как показано в следующем коде:
Кода -копия выглядит следующим образом:
функция foo () {
var val = 'hello';
функция bar () {
function baz () {
Global.val = 'world;'
}
баз ();
console.log (val); // => Привет
}
бар();
}
foo ();
Основываясь на предыдущем объяснении о объеме, вы можете подумать, что результат, показанный в коде, здесь, это мир, но фактический результат - привет. Многие новички начнут чувствовать себя смущенными здесь, поэтому давайте посмотрим, как работает этот код.
Со времен JavaScript поиск переменных идентификаторов начинается с текущей области и смотрит наружу до глобальной области. Следовательно, доступ к переменным в коде JavaScript может быть выполнен только наружу, но не наоборот.
Выполнение функции BAZ () определяет глобальную переменную Val в глобальной области. В функции BAR () при доступе к идентификатору Val принцип поиска снаружи на внешней стороне: если он не найден в объеме функции стержня, он переходит к предыдущему слою, то есть сферу функции foo ().
Тем не менее, ключом к запуску всех является: этот доступ к идентификатору находит соответствующую переменную в области функции foo () и не будет продолжать смотреть наружу, поэтому глобальная переменная, определенная в функции Baz (), не влияет на эту переменную.
1.3 Закрытие
Мы знаем, что поиск идентификатора в JavaScript следует принципу. Тем не менее, с сложностью бизнес -логики, один заказ о доставке далеко не удовлетворяет все больше новых потребностей.
Давайте посмотрим на следующий код:
Кода -копия выглядит следующим образом:
функция foo () {
var local = 'hello';
return function () {
вернуть местный;
};
}
var bar = foo ();
console.log (bar ()); // => Привет
Технология, которая позволяет внешней областям, получать доступ к внутренней областям, как показано здесь, является закрытие. Благодаря применению функций высшего порядка, область функции Foo () «расширен».
Функция foo () возвращает анонимную функцию, которая существует в рамках функции foo (), поэтому вы можете получить доступ к локальной переменной в рамках функции foo () и сохранить ее ссылку. Поскольку эта функция непосредственно возвращает локальную переменную, функция bar () может быть непосредственно выполнена во внешней области, чтобы получить локальную переменную.
Закрытие является высокоуровневым функцией JavaScript, и мы можем использовать их для достижения все более и более сложных эффектов для удовлетворения различных потребностей. Тем не менее, следует отметить, что, поскольку функция с внутренней ссылкой переменной выводится из функции, переменные в этой области не будут разрушены после выполнения функции, пока все ссылки на внутренние переменные не будут отменены. Следовательно, применение закрытия может легко привести к тому, что память не будет разобраться.
2. Механизм переработки памяти JavaScript
Здесь я возьму двигатель V8, используемый Chrome и Node.js, и запустил Google в качестве примера, чтобы кратко ввести механизм переработки памяти JavaScript. Для получения более подробного контента вы можете приобрести книгу моего хорошего друга Парка Лина «Углубленную и простую для понимания Node.js» для обучения, что является очень подробным введением в главе «Контроль памяти».
В V8 все объекты JavaScript выделяются через «кучу».
Когда мы объявляем и назначаем значения в коде, V8 выделяет часть этой переменной в памяти кучи. Если запрашиваемая память недостаточно для хранения этой переменной, V8 будет продолжать применяться к памяти, пока размер кучи не достигнет предела памяти V8. По умолчанию верхний предел памяти кучи V8 составляет 1464 МБ на 64-битных системах и 732 МБ на 32-битных системах, что составляет около 1,4 ГБ и 0,7 ГБ.
Кроме того, V8 управляет объектами JavaScript в памяти кучи в поколениях: новое поколение и старое поколение. Новое поколение представляет собой объекты JavaScript с короткими циклами выживания, такими как временные переменные, строки и т. Д.; В то время как старое поколение представляет собой объекты с длинными циклами выживания после нескольких коллекций мусора, таких как основные контроллеры, объекты сервера и т. Д.
Алгоритмы утилизации мусора всегда были важной частью разработки языков программирования, а алгоритмы утилизации мусора, используемые в V8, в основном следующие:
1. Алгоритм Scavange: управление пространством памяти выполняется с помощью копирования, в основном используемого в пространстве памяти нового поколения;
2. Алгоритм Mark-Sweep и алгоритм маркировки: память кучи отсортируется и перерабатывается посредством маркировки, в основном используется для проверки и переработки объектов старого поколения.
PS: более подробные реализации сбора мусора V8 могут быть изучены, читая связанные книги, документы и исходный код.
Давайте посмотрим, какие обстоятельства будут переработать двигатель JavaScript, какие объекты.
2.1 Область и ссылка
Новички часто ошибочно считают, что когда функция выполняется, объект, объявленный внутри функции, будет уничтожен. Но на самом деле, это понимание не является строгим и всеобъемлющим, и его легко смутить.
Ссылка является очень важным механизмом в программировании JavaScript, но, как ни странно, большинство разработчиков не будут обращать на это внимание или даже поймут его. Ссылка относится к абстрактным отношениям «Доступ к коду к объектам». Это несколько похоже на указатели C/C ++, но не одно и то же. Ссылка также является наиболее важным механизмом двигателя JavaScript в сборе мусора.
Следующий код является примером:
Кода -копия выглядит следующим образом:
// ......
var val = 'hello world';
функция foo () {
return function () {
вернуть Вэл;
};
}
Global.bar = foo ();
// ......
После прочтения этого кода, можете ли вы сказать, какие объекты все еще выживают после выполнения этой части кода?
В соответствии с соответствующими принципами, объекты, которые не переработаны и выпущены в этом коде, включают Val и Bar (). Что именно делает их неспособными быть переработанными?
Как двигатель JavaScript выполняет сборку мусора? Алгоритм сбора мусора, упомянутый выше, используется только во время переработки. Итак, как он узнает, какие объекты могут быть переработаны, а какие объекты должны продолжать выжить? Ответ является ссылкой на объект JavaScript.
В коде JavaScript, даже если вы просто записываете имя переменной как одну строку, ничего не делая, двигатель JavaScript будет думать, что это поведение доступа к объекту, и есть ссылка на объект. Чтобы гарантировать, что поведение сбора мусора не влияет на работу логики программы, двигатель JavaScript не должен перерабатывать используемые объекты, в противном случае он будет грязным. Следовательно, стандартом для оценки того, используется ли объект, является то, есть ли еще ссылка на объект. Но на самом деле, это компромисс, потому что ссылки на JavaScript могут быть переданы, поэтому могут быть приведены некоторые ссылки на глобальную масштаб, но на самом деле больше не необходимо получить к ним доступ к бизнес -логике и следует перерабатывать, но двигатель JavaScript все еще будет жестко верить, что программа все еще нуждается в ней.
Как использовать переменные и ссылки в правильной осанке является ключом к оптимизации JavaScript с языкового уровня.
3. Оптимизируйте свой JavaScript
Наконец добрался до сути. Большое спасибо за то, что увидели это с терпением. После стольких представлений выше, я считаю, что у вас есть хорошее понимание механизма управления памятью JavaScript. Тогда следующие навыки заставят вас чувствовать себя лучше.
3.1. Хорошо использовать функции
Если у вас есть привычка читать отличные проекты JavaScript, вы обнаружите, что при разработке кода JavaScript Front-End многие крупные парни часто используют анонимную функцию, чтобы обернуть ее на самый внешний слой кода.
Кода -копия выглядит следующим образом:
(function () {
// Основной бизнес -код
}) ();
Некоторые еще более продвинуты:
Кода -копия выглядит следующим образом:
; (функция (Win, Doc, $, не определен) {
// Основной бизнес -код
}) (окно, документ, jQuery);
Даже передние модульные решения для модульной нагрузки, такие как requirejs, seajs, ozjs и т. Д. Все применяют аналогичную форму:
Кода -копия выглядит следующим образом:
// requirejs
определить (['jQuery'], function ($) {
// Основной бизнес -код
});
// seajs
DEFINE ('MODULE', ['DEP', 'UnderScore'], function ($, _) {
// Основной бизнес -код
});
Если вы говорите, что многие коды Node.js проекты с открытым исходным кодом не обрабатываются таким образом, то вы ошибаетесь. Прежде чем фактически запустить код, node.js завершит каждый файл .js в следующую форму:
Кода -копия выглядит следующим образом:
(Функция (экспорт, требование, модуль, __dirname, __filename) {
// Основной бизнес -код
});
Каковы преимущества этого? Мы все знаем, что в начале статьи мы сказали, что JavaScript может иметь функции с высоте, с заявлениями и глобальными масштабами. Мы также знаем, что объекты, определенные в глобальном объеме, могут выжить до выхода процесса. Если это большой объект, это будет хлопотно. Например, некоторым людям нравится отображать шаблоны в JavaScript:
Кода -копия выглядит следующим образом:
<? Php
$ db = mysqli_connect (сервер, пользователь, пароль, «myApp»);
$ themics = mysqli_query ($ db, "select * from Teaps;");
?>
<! Doctype html>
<html lang = "en">
<голова>
<meta charset = "utf-8">
<название> Вы забавный парень, приглашенный обезьянами? </title>
</head>
<тело>
<ul id = "Темы"> </ul>
<script type = "text/tmpl" id = "topic-tmpl">
<li>
<h1> <%= title%> </h1>
<p> <%= content%> </p>
</li>
</script>
<script type = "text/javascript">
var data = <? Php echo json_encode ($ thepics); ?>;
var topictmpl = document.queryselector ('#topic-tmpl'). innerhtml;
var render = function (tmlp, view) {
var compliced = tmlp
.replace (// n/g, '// n')
.replace (/<%= ([/s/s]+?)%>/g, function (match, code) {
вернуть '" + rescep (' + code + ') +"';
});
Скомпилировано = [
'var res = "";',
'с (view || {}) {',
'res = "' + скомпилирован + '";',
'}',
'return res;'
] .join ('/n');
var fn = new Function ('view', скомпилирован);
вернуть fn (view);
};
var Teapics = document.queryselector ('#Темы');
функция init ()
data.foreach (function (тема) {
Темы.innerhtml += render (topictmpl, тема);
});
}
init ();
</script>
</body>
</html>
Этот вид кода часто можно увидеть в работах новичков. Какие проблемы здесь? Если объем данных, полученных из базы данных, очень велик, переменная данных будет холостое после того, как передний конец завершит рендеринг шаблона. Однако, поскольку эта переменная определена в глобальном объеме, двигатель JavaScript не будет перерабатывать и разрушать ее. Это будет продолжать существовать в памяти кучи старого поколения, пока страница не будет закрыта.
Но если мы внесем некоторые очень простые модификации и завершим слой функций вне логического кода, эффект будет очень другим. После того, как рендеринг пользовательского интерфейса завершен, ссылка на данные также отменяется. Когда выполняется самая внешняя функция, двигатель JavaScript начинает проверять объекты в нем, и данные могут быть переработаны.
3.2 Никогда не определяйте глобальные переменные
Мы только что говорили об этом, когда переменная определена в глобальной области, двигатель JavaScript не будет перерабатывать и разрушать ее по умолчанию. Это будет продолжать существовать в памяти кучи старого поколения, пока страница не будет закрыта.
Тогда мы всегда следовали принципу: никогда не используйте глобальные переменные. Хотя глобальные переменные действительно очень просты в разработке, проблемы, вызванные глобальными переменными, гораздо более серьезны, чем удобство, которое он приносит.
Сделать переменные реже переработаны;
1. Смущение легко вызвано, когда несколько человек сотрудничают;
2. Легко вмешиваться в цепочку применения.
3. В сочетании с вышеуказанной функцией обертывания мы также можем обрабатывать «глобальные переменные» с помощью функций обертывания.
3.3 Переменные вручную не ссылки
Если переменная не требуется в бизнес -коде, то переменная может быть направлена вручную, чтобы сделать ее переработкой.
Кода -копия выглядит следующим образом:
var Data = { / * Некоторые большие данные * /};
// blah blah blah
data = null;
3.4
В дополнение к использованию закрытия для внутреннего доступа к переменной, мы также можем использовать очень популярную функцию обратного вызова для бизнес -обработки.
Кода -копия выглядит следующим образом:
функция getData (обратный вызов) {
var data = 'некоторые большие данные';
обратный вызов (null, data);
}
getData (function (err, data) {
console.log (data);
Функции обратного вызова - это технология стиля прохождения продолжения (CPS). Этот стиль программирования передает бизнес функции функции от возврата значения к функции обратного вызова. И у него много преимуществ по сравнению с закрытием:
1. Если пропущенные параметры являются основным типом (например, строки, числовые значения), формальные параметры, передаваемые в функции обратного вызова, будут скопированными значениями, и будет проще переработать после использования бизнес -кода;
2. Благодаря обратным вызовам, в дополнение к выполнению синхронных запросов, мы также можем использовать их в асинхронном программировании, который сейчас является очень популярным стилем письма;
3. Сама функция обратного вызова обычно является временной анонимной функцией. Как только функция запроса будет выполнена, ссылка на саму функцию обратного вызова будет отменена и будет переработана.
3.5 Хорошее управление закрытием
Когда наш бизнес нуждается в (например, связывание круговых событий, частные атрибуты, обратные вызовы с аргументами и т. Д.) Должны использовать закрытие, пожалуйста, будьте осторожны с деталями.
Можно сказать, что события связывания петли являются обязательным курсом для начала работы с закрытием JavaScript. Предположим, что сценарий: есть шесть кнопок, соответствующих шести событиям. Когда пользователь нажимает кнопку, соответствующие события выводятся в указанном месте.
Кода -копия выглядит следующим образом:
var btns = document.queriselectorall ('. Btn'); // 6 элементов
var output = document.queryselector ('#output');
var events = [1, 2, 3, 4, 5, 6];
// случай 1
for (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = function (evt) {
output.innerText + = 'clicked' + events [i];
};
}
// случай 2
for (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = (function (index) {
возврат функции (evt) {
output.innerText + = 'clicked' + events [index];
};
})(я);
}
// случай 3
for (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = (function (event) {
возврат функции (evt) {
output.innerText + = 'clicked' + event;
};
}) (Events [i]);
}
Первое решение здесь, очевидно, является типичной ошибкой события привязки цикла. Я не буду объяснять это подробно здесь. Вы можете подробно рассмотреть мой ответ в пользовательском сети; Разница между вторым и третьим решением заключается в параметрах, передаваемых в закрытии.
Параметры, передаваемые во второй схеме, являются индексом текущего цикла, в то время как последний непосредственно передается в соответствующий объект события. Фактически, последнее более подходит для больших объемов приложений данных, поскольку в функциональном программировании JavaScript параметры, передаваемые в функциональных вызовах, являются основными объектами типов, поэтому формальные параметры, полученные в органе функции, будут значением копирования, так что это значение определяется как локальная переменная в области области функции. После того, как привязка события завершена, переменная событий может быть направлена вручную, чтобы уменьшить использование памяти во внешней области. Более того, когда элемент удаляется, соответствующая функция прослушивания событий, объект события и функция закрытия также уничтожается и переработана.
3.6 Память не кеш
Роль кэширования в развитии бизнеса играет важную роль и может снизить бремя космических временных ресурсов. Но следует отметить, что вы не должны легко использовать память в качестве кэша. Память - это вещь каждый дюйм земли для любой разработки программы. Если это не очень важный ресурс, пожалуйста, не помещайте его непосредственно в память и не сформулируйте механизм истечения срока действия для автоматического уничтожения кэша срока годности.
4. Проверьте использование памяти JavaScript
В повседневной разработке мы также можем использовать некоторые инструменты для анализа и устранения неполадок в памяти в JavaScript.
4.1 Blink/Webkit Browser
В браузере Blink/Webkit (Chrome, Safari, Opera и т. Д.) Мы можем использовать инструмент профилей инструментов разработчика для выполнения проверки памяти в наших программах.
4.2 Проверка памяти в node.js
В node.js мы можем использовать модули узла-Heapdump и Node-Memwatch для проверки памяти.
Кода -копия выглядит следующим образом:
var heapdump = require ('heapdump');
var fs = require ('fs');
var path = require ('path');
fs.writefilesync (path.join (__ dirname, 'app.pid'), process.pid);
// ...
Кода-копия заключается в следующем: <span style = "font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, Serif; Font-Size: 14px; Line-Height: 1.5em;"> После введения узла-heapdump в бизнес-код, нам нужно отправить сигнал SIGUSR2 в процесс. Хиповая память. </span>
Скопируйте код следующим образом: $ kill -Usr2 (cat app.pid)
Таким образом, в формате Heapdump- <ec> будет файл снимка, указанный в формате. Мы можем открыть его, используя инструмент профилей в инструментах разработчика браузера и проверить его.
5. Резюме
Статья снова наступит. Этот обмен в основном показывает вам следующий контент:
1. Javascript тесно связан с использованием памяти на языковом уровне;
2. Механизмы управления памятью и утилизации в JavaScript;
3. Как использовать память более эффективно, чтобы произведенный JavaScript может быть более расширенным и энергичным;
4. Как выполнить проверки памяти при столкновении с проблемами памяти.
Я надеюсь, что, изучая эту статью, вы можете создать лучший код JavaScript, чтобы ваша мама чувствовала себя непринужденно, и ваш начальник чувствует себя непринужденно.