Зловещая утечка памяти во внешнем интерфейсе и хорошее решение

внешний интерфейс JavaScript
Зловещая утечка памяти во внешнем интерфейсе и хорошее решение

Эта статья включена в githubGitHub.com/Майкл-Ли Чжиган…

Недавно я получил отзывы от тестировщиков, что разработанная нами страница периодически подвисает, а клик не реагирует, особенно когда страница открыта долго, вероятность выше. Открываем диспетчер задач и видим, что показатель занятости памяти уже очень высокий.Сначала судят, что может быть утечка памяти. Проверьте причину утечки памяти ниже.

Память, которая больше не используется системными процессами и не освобождается вовремя, называется утечкой памяти. Когда использование памяти становится все выше и выше, это повлияет на производительность системы и приведет к сбою процесса. Chrome ограничивает объем памяти, который может использовать браузер (1,4 ГБ для 64-разрядной версии, 1,0 ГБ для 32-разрядной версии).

Причины утечек памяти

1. Неожиданная глобальная переменная

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

  • необъявленная переменная
function fn() {
  a = 'global variable'
}
fn()
  • Переменные, созданные с помощью этого (это относится к окну).
function fn() {
  this.a = 'global variable'
}
fn()

Решение:

  • Избегайте создания глобальных переменных
  • Чтобы использовать строгий режим, добавьте заголовок файла JavaScript или в начало функции.use strict.

2. Утечки памяти из-за замыканий

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

function fn () {
  var a = "I'm a";
  return function () {
    console.log(a);
  };
}

Решение: Определите обработчик событий снаружи, освободите замыкание или во внешней функции, где определен обработчик событий.

Например: выражение функции в цикле можно использовать повторно и лучше всего поместить его вне цикла.

// bad
for (var k = 0; k < 10; k++) {
  var t = function (a) {
    // 创建了10次  函数对象。
    console.log(a)
  }
  t(k)
}

// good
function t(a) {
  console.log(a)
}
for (var k = 0; k < 10; k++) {
  t(k)
}
t = null

3. Ссылки на элементы DOM, которые не очищаются

Причина: несмотря на то, что удалено в другом месте, в объекте все еще есть ссылка на dom.

// 在对象中引用DOM
var elements = {
  btn: document.getElementById('btn'),
}
function doSomeThing() {
  elements.btn.click()
}

function removeBtn() {
  // 将body中的btn移除, 也就是移除 DOM树中的btn
  document.body.removeChild(document.getElementById('button'))
  // 但是此时全局变量elements还是保留了对btn的引用, btn还是存在于内存中,不能被GC回收
}

Обходной путь: удалить вручную,elements.btn = null.

4. Забытый таймер или обратный вызов

В таймере есть ссылка на дом, даже если дом удалить, таймер все равно там, значит, в памяти все еще есть этот дом.

// 定时器
var serverData = loadData()
setInterval(function () {
  var renderer = document.getElementById('renderer')
  if (renderer) {
    renderer.innerHTML = JSON.stringify(serverData)
  }
}, 5000)

// 观察者模式
var btn = document.getElementById('btn')
function onClick(element) {
  element.innerHTMl = "I'm innerHTML"
}
btn.addEventListener('click', onClick)

Решение:

  • Вручную удалите таймеры и dom.
  • removeEventListener удалить прослушиватель событий

Несколько ситуаций, которые склонны к утечкам памяти в vue

При разработке приложений в Vue SPA следует более внимательно относиться к утечкам памяти. Поскольку SPA спроектированы таким образом, что пользователям не нужно обновлять свои браузеры, чтобы использовать их, приложения JavaScript должны сами очищать компоненты, чтобы гарантировать, что сборка мусора работает ожидаемым образом. Поэтому в процессе разработки нужно всегда быть начеку с проблемой утечек памяти.

1. Утечка памяти из-за глобальных переменных

Объявленная глобальная переменная не очищается при переключении страниц

<template>
  <div id="home">这里是首页</div>
</template>

<script>
  export default {
    mounted() {
      window.test = {
        // 此处在全局window对象中引用了本页面的dom对象
        name: 'home',
        node: document.getElementById('home'),
      }
    },
  }
</script>

Решение: Удаляйте ссылку кстати, когда страница выгружается.

destroyed () {
  window.test = null // 页面卸载的时候解除引用
 }

2. Отслеживайте события, такие как окно/тело, без отвязки

Обратите особое внимание на прослушиватели времени, такие как window.addEventListener.

<template>
<div id="home">这里是首页</div>
</template>

<script>
export default {
mounted () {
  window.addEventListener('resize', this.func) // window对象引用了home页面的方法
}
}
</script>

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

mounted () {
  window.addEventListener('resize', this.func)
},
beforeDestroy () {
  window.removeEventListener('resize', this.func)
}

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

Например

<template>
  <div id="home">这里是首页</div>
</template>

<script>
export default {
  mounted () {
   this.$EventBus.$on('homeTask', res => this.func(res))
  }
}
</script>

Решение. Вы также можете рассмотреть возможность разыменования, когда страница выгружается.

mounted () {
 this.$EventBus.$on('homeTask', res => this.func(res))
},
destroyed () {
 this.$EventBus.$off()
}

4. Электронные карты

Когда у каждой легенды нет данных, он создает таймер для рендеринга пузырька.После переключения страницы легенда echarts уничтожается, но экземпляр echarts все еще находится в памяти, и его таймер рендеринга пузырька все еще работает. Это приводит к тому, что Echarts сильно загружает ЦП, вызывая зависание браузера и даже сбой браузера при большом объеме данных.

Решение: добавьте метод beforeDestroy() для освобождения ресурсов диаграммы страницы, я также пытался использовать метод dispose(), но dispose уничтожает легенду, легенда не существует, но метод resize() легенды будет start, он будет В отчете нет метода изменения размера, но метод clear() очищает данные легенды, не влияет на изменение размера легенды и может освободить память, поэтому переключение происходит очень плавно.

beforeDestroy () {
  this.chart.clear()
}

5. Утечка памяти, вызванная инструкцией v-if

v-if привязано к значению false, но на самом деле элемент dom фактически не освобождается, когда он скрыт.

Например, в приведенном ниже примере мы загружаем поле выбора с множеством параметров, а затем используем кнопку «Показать/скрыть», чтобы добавить или удалить его из виртуального DOM с помощью директивы v-if. Проблема с этим примером заключается в том, что директива v-if удаляет родительский элемент из DOM, но мы не очищаем вновь добавленный фрагмент DOM с помощью Choices.js, вызывая утечку памяти.

<div id="app">
  <button v-if="showChoices" @click="hide">Hide</button>
  <button v-if="!showChoices" @click="show">Show</button>
  <div v-if="showChoices">
    <select id="choices-single-default"></select>
  </div>
</div>

<script>
  export default {
    data() {
      return {
        showChoices: true,
      }
    },
    mounted: function () {
      this.initializeChoices()
    },
    methods: {
      initializeChoices: function () {
        let list = []
        // 我们来为选择框载入很多选项,这样的话它会占用大量的内存
        for (let i = 0; i < 1000; i++) {
          list.push({
            label: 'Item ' + i,
            value: i,
          })
        }
        new Choices('#choices-single-default', {
          searchEnabled: true,
          removeItemButton: true,
          choices: list,
        })
      },
      show: function () {
        this.showChoices = true
        this.$nextTick(() => {
          this.initializeChoices()
        })
      },
      hide: function () {
        this.showChoices = false
      },
    },
  }
</script>

В приведенном выше примере мы можем использовать метод hide(), чтобы выполнить некоторую очистку перед удалением поля выбора из DOM, чтобы устранить утечку памяти. Для этого мы сохраним свойство в объекте данных экземпляра Vue и будем использовать метод destroy() в Choices API, чтобы очистить его.

<div id="app">
  <button v-if="showChoices" @click="hide">Hide</button>
  <button v-if="!showChoices" @click="show">Show</button>
  <div v-if="showChoices">
    <select id="choices-single-default"></select>
  </div>
</div>

<script>
  export default {
    data() {
      return {
        showChoices: true,
        choicesSelect: null
      }
    },
    mounted: function () {
      this.initializeChoices()
    },
    methods: {
      initializeChoices: function () {
        let list = []
        for (let i = 0; i < 1000; i++) {
          list.push({
            label: 'Item ' + i,
            value: i,
          })
        }
         // 在我们的 Vue 实例的数据对象中设置一个 `choicesSelect` 的引用
        this.choicesSelect = new Choices("#choices-single-default", {
          searchEnabled: true,
          removeItemButton: true,
          choices: list,
        })
      },
      show: function () {
        this.showChoices = true
        this.$nextTick(() => {
          this.initializeChoices()
        })
      },
      hide: function () {
        // 现在我们可以让 Choices 使用这个引用,从 DOM 中移除这些元素之前进行清理工作
        this.choicesSelect.destroy()
        this.showChoices = false
      },
    },
  }
</script>

ES6 предотвращает утечки памяти

Как упоминалось ранее, очень важно своевременно очищать ссылки. Однако вы не можете помнить так много, а иногда и забываете, поэтому происходит так много утечек памяти.

ES6 учитывает это и вводит две новые структуры данных: weakset и weakmap. Их ссылки на значения не включаются в механизм сборки мусора, то есть, если другие объекты больше не ссылаются на объект, механизм сборки мусора автоматически высвободит память, занимаемую объектом.

const wm = new WeakMap()
const element = document.getElementById('example')
vm.set(element, 'something')
vm.get(element)

В приведенном выше коде сначала создайте экземпляр Weakmap. Затем сохраните узел DOM в качестве имени ключа в экземпляре и сохраните некоторую дополнительную информацию в качестве значения ключа в WeakMap. В настоящее время ссылка на элемент в WeakMap является слабой ссылкой и не будет учитываться в механизме сборки мусора.

Объект прослушивателя, который регистрируется для прослушивания событий, хорошо подходит для реализации WeakMap.

// 代码1
ele.addEventListener('click', handler, false)

// 代码2
const listener = new WeakMap()
listener.set(ele, handler)
ele.addEventListener('click', listener.get(ele), false)

Преимущество кода 2 по сравнению с кодом 1 заключается в том, что, поскольку функция слушателя помещается в WeakMap, как только объект dom исчезает, связанный с ним обработчик функции слушателя также автоматически исчезает.

рекомендуемая статья