Утечка памяти при использовании vue

внешний интерфейс JavaScript Chrome Vue.js

Сегодня увидел статью про утечки памяти при использовании js, и способ просмотра утечек памяти в браузере chrome, поэтому решил оставить. Эта статья перехватывает только те части, которые я считаю более важными.Для тех, кому нравится оригинальный текст, пожалуйста, нажмите на оригинальную ссылку внизу статьи.

Что такое утечка памяти? Утечка памяти относится к новой части памяти, которая не может быть освобождена или удалена сборщиком мусора. После того, как объект новый, он претендует на кусок памяти кучи.Когда указатель объекта установлен в ноль или покидает область видимости, он будет уничтожен, затем этот кусок памяти будет автоматически собран мусором в JS, если на него никто не ссылается . Однако, если указатель объекта не имеет значение null и нет возможности получить указатель объекта в коде, память, на которую он указывает, не может быть освобождена, что означает утечку памяти. Почему я не могу получить этот указатель объекта в коде?Например:

// module date.js
let date = null;
export default {
    init () {
        date = new Date();
    }
}
// main.js
import date from 'date.js';
date.init();

После инициализации даты в main.js переменная date будет существовать какое-то время, пока вы не закроете страницу, потому что ссылка на дату находится в другом модуле, который можно понимать как модуль, являющийся замыканием и невидимый для внешний мир. Итак, если вы хотите, чтобы объект даты существовал все время и его нужно было использовать все время, тогда нет проблем, но если вы хотите использовать его один раз, а затем не использовать, возникнет проблема. объект не был освобожден в памяти, и происходит утечка памяти.

Еще одна незаметная и распространенная утечка памяти — это связывание событий, которое формирует замыкание и приводит к сохранению некоторых переменных. Как показано в следующем примере:

// 一个图片懒惰加载引擎示例
class ImageLazyLoader {
    constructor ($photoList) {
        $(window).on('scroll', () => {
            this.showImage($photoList);
        });
    }
    showImage ($photoList) {
        $photoList.each(img => {
            // 通过位置判断图片滑出来了就加载
            img.src = $(img).attr('data-src');
        });
    }
}
// 点击分页的时候就初始化一个图片懒惰加载的
$('.page').on('click', function () {
    new ImageLazyLoader($('img.photo'));
});

Это модель ленивой загрузки изображений: каждый раз, когда вы нажимаете на пейджинг, данные предыдущей страницы будут очищаться, а DOM текущей страницы будет обновляться, а механизм ленивой загрузки будет повторно инициализироваться. Он прослушивает событие прокрутки и обрабатывает DOM входящего списка изображений. Каждый раз, когда щелкается страница, создается новая, и здесь происходит утечка памяти, в основном вызванная следующими 3 строками кода:

$(window).on('scroll', () => {
    this.showImage($photoList);
});

Поскольку привязка события здесь формирует замыкание, две переменные this/$photoList не освобождаются, это указывает на экземпляр ImageLazyLoader, а $photoList указывает на узел DOM, когда данные предыдущей страницы очищаются. время соответствующие узлы DOM были отделены от дерева DOM, но все еще есть $photoList, указывающий на них, так что эти узлы DOM не могут быть удалены сборщиком мусора и сохранены в памяти, и происходит утечка памяти. Поскольку переменная this также перехвачена замыканием и не была освобождена, также имеет место утечка памяти экземпляра ImageLazyLoader.

Решение этой проблемы относительно простое: отключить связанное событие при уничтожении экземпляра, как показано в следующем коде:

class ImageLazyLoader {
    constructor ($photoList) {
        this.scrollShow = () => {
            this.showImage($photoList);
        };
        $(window).on('scroll', this.scrollShow);
    }
    // 新增一个事件解绑                           
    clear () {                     
        $(window).off('scroll', this.scrollShow);
    }
    showImage ($photoList) {
        $photoList.each(img => {
            // 通过位置判断图片滑出来了就加载
            img.src = $(img).attr('data-src');
        });
        // 判断如果图片已全部显示,就把事件解绑了
        if (this.allShown) {
            this.clear();
        }
    }
}
// 点击分页的时候就初始化一个图片懒惰加载的
let lazyLoader = null;
$('.page').on('click', function () {
    lazyLoader && (lazyLoader.clear());
    lazyLoader = new ImageLazyLoader($('img.photo'));
});

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

Почему нет ссылки на закрытие, когда событие не привязано? Поскольку движок JS обнаруживает, что замыкание бесполезно, он уничтожает замыкание, и внешние переменные, на которые ссылается замыкание, естественно, будут пустыми.

Что ж, основы объясняются здесь Теперь используйте инструмент обнаружения памяти Chrome devtools, чтобы снова запустить его, чтобы найти некоторые утечки памяти на странице. Чтобы избежать влияния некоторых подключаемых модулей, установленных в браузере, используйте страницу режима инкогнито Chome, которая отключит все подключаемые модули.

Затем откройте devtools, перейдите на вкладку «Память» и выберите моментальный снимок кучи, как показано ниже:



Что такое моментальный снимок кучи? Перевод представляет собой моментальный снимок кучи, который делает снимок текущей кучи памяти. Поскольку динамически применяемая память находится в куче, а локальные переменные — в стеке памяти, они выделяются и управляются операционной системой, поэтому утечек памяти не будет. Так что хорошо позаботиться о ситуации с кучей.

Затем выполните некоторые операции по добавлению, удалению и изменению DOM, например:

(1) Откройте окно, а затем закройте всплывающее окно.

(2) Нажмите на одну страницу, чтобы перейти к другому маршруту, а затем нажмите назад, чтобы вернуться

(3) Нажмите на пейджинг, чтобы вызвать динамическое изменение DOM.

Сначала нужно добавить DOM, затем удалить эти DOM и посмотреть, есть ли в этих удаленных DOM объекты, ссылающиеся на них.

Вот сценарий второго метода, определяющий, есть ли утечка памяти на странице маршрутизации одностраничного приложения. Сначала откройте домашнюю страницу, перейдите на другую страницу, затем нажмите «Назад», а затем нажмите кнопку «Сборка мусора»:


Запустите сборку мусора, чтобы избежать ненужных помех.

Затем снова нажмите кнопку фото:


Он просканирует кучу памяти текущей страницы и снова отобразит ее, как показано на следующем рисунке:


Затем найдите detached в поле поиска Class Filter посередине выше:


Он отобразит все узлы DOM, которые разделили дерево DOM, сосредоточив внимание на значении расстояния, которое не является пустым, это расстояние представляет собой расстояние от корневого узла DOM. Что именно представляют собой эти элементы div, показанные на картинке выше? Мы наводим на него мышь и ждем 2 с, он отобразит информацию DOM этого div:


Благодаря className и другой информации вы можете узнать, что это DOM-узел проверяемой страницы.В следующем окне объекта разверните его родительский узел по очереди, и вы увидите, что его самый внешний родительский узел является экземпляром VueComponent:


Желтый шрифт native_bind ниже указывает на то, что на него указывает событие, а желтый цвет указывает на то, что ссылка все еще действует.Наведите мышь на native_bind и задержитесь на 2 секунды:



Вам будет предложено указать функцию getScale, привязанную к окну в файле homework-web.vue.Проверьте, что этот файл имеет привязку:

mounted () {
    window.addEventListener('resize', this.getScale);
}

Таким образом, хотя компонент Vue удаляет DOM, все еще есть ссылка, из-за которой экземпляр компонента не освобождается, а другой $el в компоненте указывает на DOM, поэтому DOM не освобождается.

Чтобы быть отвязанным в beforeDestroyed

beforeDestroyed () {
    window.removeEventListener('resize', this.getScale);
}

Поэтому, исходя из приведенного выше анализа, следующие ситуации могут вызвать утечку памяти:

(1) Мониторинг таких событий, как окно/тело, без отвязки

(2) События, привязанные к EventBus, не являются несвязанными.

(3) Нет unwatch после просмотра $store of Vuex

(4) Внутренние переменные замыкания, формируемого модулем, не обнуляются после использования.

(5) Создан с использованием сторонней библиотеки без вызова правильной функции уничтожения

И вы можете использовать инструмент анализа памяти Chrome для быстрого устранения неполадок.В этой статье в основном используется базовая функция снимков кучи памяти.Читатели могут попытаться проанализировать, есть ли утечки памяти на своих собственных страницах.Снимок кучи, поиск отсоединенных, сортировка по расстоянию , разверните родителя непустых узлов и найдите слова, отмеченные желтым цветом, чтобы указать, что есть ссылки, которые не были освобождены. То есть этот метод в основном анализирует свободные узлы DOM, на которые еще есть ссылки. Поскольку утечка памяти страницы обычно связана с DOM, обычные переменные JS обычно не имеют проблем из-за сборки мусора, если только переменные не перехватываются замыканиями и не используются, а не пусты.

Утечки памяти, связанные с DOM, часто также вызываются замыканиями и привязками событий. После привязки (глобального) события вам нужно отвязать его, когда оно вам не нужно. Конечно, если вы напрямую привязаны к div, вы можете удалить div напрямую, и события, привязанные к нему, естественно не будут привязаны.

исходный адрес