Анализ экземпляра утечки памяти JS

JavaScript

Сегодня вдруг задумался над вопросом, очень интересна блочная область видимости let и переменная ссылочная функция замыканий (с ней связана эта мозговая дыра, 囧). . Использование замыканий влияет на процесс сборки мусора в браузере. Так:

  • Когда будут объекты JSАвтоматическая переработка?
  • Как правильно использовать замыкания и избежать утечек памяти?

Давайте сначала взглянем на классический пример, проблему асинхронной печати в цикле (нетерпеливо перейдем сразу к последнему экземпляру (^▽^))

// 想异步打印1到5
for(var i=1; i<=5;i++) {
    setTimeout(function(){
        console.log("print: " + i);
    }, i*1000)
}
// 结果
print: 6
print: 6
print: 6
print: 6
print: 6

Поскольку функция печати вызывается асинхронно, при вызове этой функции цикл завершается и i становится равным 6, поэтому подряд печатается 5 6.

Во втором случае, если для объявления i используется let, то let имеет как минимум следующие характеристики по сравнению с var:

  • Переменная, объявленная let, имеетобласть действия блока
  • Цикл формы for (let x...) создает новую привязку для x на каждой итерации (глубокая копия)
// 1到5
for(let i=1; i<=5;i++) {
    setTimeout(function(){
        console.log("print: " + i);
    }, i*1000)
}
// 结果
print: 1
print: 2
print: 3
print: 4
print: 5

В этом случае непосредственно через let фактически создается замыкание для каждой регистрации callback-функции, поэтому печать идет нормально.

В третьем случае аналогичного эффекта можно добиться, создав замыкание вручную. В каждом цикле возвращайтеФункция ссылается на переменную в это времяя, это на самом делеперераспределенная памятьчтобы сохранить значение i, а не просто ссылаться на адрес памяти. Память Нимы увеличивается, но не стоит беспокоиться о таком небольшом количестве данных

// 1到5
for(var i=1; i<=5;i++) {
    setTimeout((function(){
        var b = i; //install timer的时候引用 i 并且return 一个函数
        return function(){
            console.log("print: " + b);
        }
    })(), i*1000)
}

Этот пример является хорошим примером того, как замыкание сохраняет адрес памяти внутренней переменной (1w глубоких копий i делается 1w раз в цикле). Однако неправильное использование замыканий и глобальных переменных может привести к утечке памяти, переполнению памяти или даже к прямому зависанию вкладки.

Будет ли переменная JS восстановлена ​​GC в памяти браузера, зависит от жизненного цикла области, в которой находится переменная, и от того, ссылаются ли на эту переменную другие:

  • Если это переменная, объявленная внутри функции, и нет внешней ссылки на переменную, функция будет уничтожена после выполнения. Если есть ссылка, то внутренняя переменная всегда будет свободна в памяти

Объекты JS (ссылочные типы) хранятся вкуча памяти, вы можете просмотреть его, создав моментальный снимок кучи с помощью инструмента «Профиль» инструмента отладки Chrome.

Наконец-то посмотрите на живом примере, не случайноИспользование памяти 1G в минуту:

function Test()  
{  
    this.obj= {};
    this.index = 1;
    this.timer = null;
    var cache = []; // 内部变量,内存隐患...
    this.timer = window.setInterval(() =>{
        this.index += 1; 
        this.obj = {
            val: '_timerxxxxxbbbbxx_' + this.index,
            junk: [...cache]
        };
        cache.push(this.obj);
    }, 1);  
    console.warn("create Test instance..");
}  
test = new Test(); // JS对象开启定时器不断分配内存

Несколько слов, ключ к этому примеру в том, что внутренний кеш переменныхвнешняя асинхронная функция(таймер) ссылка. Если вы не очистите таймер и просто вручную установите экземпляр класса Test в null, это не поможет, и кеш продолжит занимать память.

在清除定时器,并且把Test类的实例设为null后才成功回收垃圾

Test.prototype.destroy = function(){
    clearInterval(this.timer);
}
function d() {
    // 取消定时器并销毁Test 实例
    test.destroy();
    test = null;
    console.warn("destroyed test instance..");
}

Сразу после очистки ссылки на внутренний кэш переменных размер кучи памяти уменьшился на 40 МБ.

Суммировать:

  • Локальные переменные, которые не используются внутри функции, вовремя очищаются, и при очистке следует учитывать все ссылочные функции ta.
  • Если вам нужно обратиться к локальным переменным, используйте неанонимные функции, иначе ссылку будет сложно уничтожить.

Личное мнение, приветствуются комментарии и исправления, если я не прав :)

Справочная статья:

es6-in-depth-let-and-const

Разговор об освобождении памяти Javascript

JS-управление памятью