предисловие
Недавно я сделал обзор основных знаний, необходимых для современного интерфейса в целом, и организую его в серию анализов интерфейса и сводных статей. Это второй, в основном, чтобы обобщить базовую модель памяти JS. (Кроме того, эту серию статей можно также найти в колонке Yuque——Серия Hardcore Front EndПроверить).
Эта статья была впервые опубликована сДино Заметки, пожалуйста, укажите источник для перепечатки 😁
1. Типы данных и память
Классификация типов данных
В основном делятся на две категории:Базовые типы данных, сложные типы данных, детализированный следующим образом.
| базовый тип данных | Строка, число, логическое значение, нуль, неопределенное значение, символ |
|---|---|
| сложные типы данных | Object и все типы, которые наследуются от Object |
Для разных типов данных существуют разные области памяти для хранения данных.Основные типы данных хранятся непосредственно в памяти стека, а сложные типы данных хранятся в памяти кучи.
классификация памяти
Классификация памяти в JS связана с движком JS, который обычно является движком V8 при просмотре; классификация памяти в основном предназначена для сборки мусора.Например, в механизме сборки мусора V8 используются разные алгоритмы утилизации в зависимости от объема памяти. нового поколения и старого поколения Гарантированная эффективность сбора мусора.
Пространство памяти JS разделено накучапамять икучаПамять, стековая память - это хранилище структуры стекабазовый тип данныхи указатель на память кучи, память кучиХранение сложных типов данных.
2. Объявление и присвоение переменных
Резюме основных моментов
-
Суть объявления переменной заключается в том, что имя переменной привязывается к адресу памяти стека, а не напрямую к памяти кучи.
-
Объявленные примитивные типы данных будут хранить значения в памяти стека, а объявленные сложные типы данных будут хранить значения в памяти кучи и хранить их адреса памяти в куче как значения в памяти стека.
-
Const Декларация по существу постоянное имя переменной относится к объявленным точкам, а адрес памяти стопки не может быть изменен, но соответствующее значение в стеке может варьироваться.
-
Основное назначение типа данных заключается в применении новой области памяти в памяти стека для сохранения значения и привязки адреса памяти, на который оно указывает, к исходной переменной.
-
Назначение сложных типов данных заключается в применении новой области памяти в памяти кучи для сохранения значения и использовании адреса памяти, на который он указывает, в качестве значения для применения к новой области памяти в памяти стека для ее сохранения и привязки. адрес памяти в стеке в переменную.
Подробно объясните объявление и назначение переменных
базовый тип данных
let index = 23
Примитивные типы данных хранят значения непосредственно в памяти стека, а переменные привязываются к адресу, соответствующему значению в стеке.
let _index = index
Объявление другой переменной _index и назначение ее в качестве индекса фактически привязывает переменные _index и index к адресу памяти, на который указывает index.
index = 45
Изменение значения индекса переменной до базового типа данных фактически означает выделение памяти для хранения значения в памяти стека, а затем привязку полученного адреса памяти к индексу переменной.
Память на рисунке относится к памяти стека, которая совпадает со стеком на следующем рисунке.
сложные типы данных
let students = []
Когда объявляется сложный тип данных, в памяти кучи выделяется пространство памяти для хранения его значения, а адрес выделенного пространства памяти кучи сохраняется как значение в памяти стека, а переменная напрямую привязывается к адресу памяти. в стеке.
Изменение сложных данных по ссылке
let _students = students
_students.push({ name: '小明' })
_status = studentsОператор присваивания просто указывает две переменные на один и тот же адрес памяти стека,push()Оператор выделит новое пространство в памяти кучи для хранения нового массива и сохранит его адрес в памяти кучи в стеке.
Более сложный пример
let obj = { name: '小明' }
let arr = []
arr = [ obj ]
obj = null
Схематическая диаграмма модели памяти выглядит следующим образом:
[obj]Принадлежность к сложным типам, ссылающимся на сложные типы, обрабатывается ссылкой на указатель, хотяobj=nullочиститьobjдля объекта{index:'小明'}обязательный, ноarrЕсть еще ссылки на этот объект.
Подробно объясните объявление и назначение констант
Процедура объявления примитивного типа данных как константы такая же, как и для объявления примитивных типов данных.
const index = 1
index = 100 // TypeError: Assignment to constant variable.
Присвоение константе, объявленной как примитивный тип данных, приводит к
в волеindexОшибка возникает, когда переменная привязана к вновь сгенерированному адресу памяти: изменение адреса памяти, привязанного к константе, не разрешено.
Процедура объявления сложных типов данных в качестве констант такая же, как и для примитивных типов данных.
const students = []
students = [{ name: '小红' }]
Присвоение константе, объявленной как сложный тип данных, приводит к следующему результату
Ошибка при привязке переменной student к вновь сгенерированному адресу памяти: изменение адреса памяти, привязанного к константе, не допускается.
глубокая копия против поверхностной копии
Сложные типы данных, упомянутые выше, указывают на одно и то же пространство кучи через указатель.Основное различие между глубоким и неглубоким копированием заключается в том, следует ли заново выделять пространство кучи памяти при копировании значения для сохранения копии исходного значения.
Для типов объектов или массивов, когда мы присваиваем a значению b, а затем меняем свойства в b, также изменяется значение a. Другими словами, a и b указывают на один и тот же фрагмент памяти, поэтому измените любое из значений, и другое значение изменится соответственно.Мелкая копия (копия).
глубокая копия(копирование) заключается в выделении нового пространства кучи памяти для хранения скопированного значения в вышеупомянутом процессе присваивания b, и в то же время существуют вложенные атрибуты сложных типов данных (Рекурсивный обход) также обрабатывается таким же образом, и любой уровень сложных объектов под новым объектом данных, который в конечном итоге скопирован, будет иметь новую память кучи для хранения соответствующего значения.
Я не буду вдаваться в подробности здесь, вы можете обратиться кэта статья.
3. Переработка мусора
Должна быть памятьвывоз мусора(GC), стековая память в JS в основном используется при выполнении функции (также вызывается в соответствии с последовательностью вызова функциистек вызовов), сборка мусора из стековой памяти начинается после выполнения функции. Память кучи должна обрабатываться с помощью определенного алгоритма сборки мусора из-за существования нескольких указателей в памяти стека, указывающих на нее, и большой памяти кучи.
Ключ к сборке мусора заключается в том, как определить, что память больше не используется, а затем освободить ее.
алгоритм подсчета ссылок
В основном используется старыми браузерами, такими как IE.Количество ссылок на переменные анализируется с помощью счетчиков, а переменные, на которые нет ссылок, очищаются. дляНевозможно обработать ситуацию, когда представлена ссылка на цикл.,Например:
function cycle() {
var o1 = {}
var o2 = {}
o1.a = o2
o2.a = o1
return "Cycle reference!"
}
cycle()
Среди них o1 ссылается на o2, а o2 ссылается на o1.После того как функция цикла выполняет o1, o2 больше не упоминается, но алгоритм подсчета ссылок определяет, что есть ссылки на оба.
Алгоритм очистки
Он используется для памяти нового поколения в V8, а память нового поколения делится на две части: From и To, и сборка мусора завершается во время преобразования между From и To.
Алгоритм развертки пометки
Алгоритм очистки, используемый в памяти кучи раннего V8. Глобальное сканирование памяти кучи, чтобы найти неиспользуемые объекты, пометить их и очистить. Будет фрагментация памяти из-за отсутствия сортировки памяти.
Алгоритм сопоставления меток
Память кучи глобального сканирования находит неиспользуемые объекты и одновременно очищает их, что решает проблему фрагментации памяти, вызванную алгоритмом очистки меток.
Инкрементальная очистка и сортировка
Размер памяти кучи, как правило, велик, и необходимо сканировать всю кучу, когда для сборки мусора используются предыдущие алгоритмы.Заставляет логику выполнения JS приостанавливаться на долгое время. Инкрементная очистка, очистка — это разделение очистки разметки или очистки разметки нашаг, по очереди выполняя логику JS и степпер, максимизируяМеньше времени паузы логики выполнения JS.
🌰 Кейс
В качестве примера возьмем запуск следующего кода в консоли браузера (на данный момент без учета совместимости синтаксиса ES6).
const fn = (arr) => {
const _arr = arr
return _arr.join()
}
const index = 0
const array = new Array(20).fill(1)
fn(array)
Объявления переменных (выполняются последовательно)
Выполнять по порядку: функция fn хранится в куче, константный целочисленный индекс хранится в памяти стека, а массив array хранится в памяти кучи.
Вызов функции (стек вызовов функций)
Выполнять последовательно: переменные arr и _arr указывают на массив array и сохраняют строку, возвращенную методом соединения, в память стека.
Очистить стек вызовов функций
Переменные arr и _arr в стеке вызова функции относятся к области видимости функции, они недоступны и в это время будут очищены.
очистить память стека
После выполнения всей логики JS функция fn, индекс константы, массив массивов и строка, возвращаемая функцией fn, будут очищены.
В консоли браузера, если вышеуказанная логика JS выполняется без выхода из консоли, описанная выше очистка памяти стека будет выполнена после закрытия консоли.
4. Анализ общих проблем
🌰 Замыкания вызывают утечки памяти
Закрытие — это косвенное предоставление внешнего доступа к переменным внутри функции путем возврата функции, что расширяет область действия функции в JS.
Способ создания замыкания следующий:
const generateFn = () => {
const obj = { index: 1 }
return () => {
return obj
}
}
const obj2 = generateFn()() // 此时obj2就指向了上面定义的obj
Следующее использование вызовет утечку памяти: я
window.fn = generateFn() // 返回的函数绑定至了全局,没有主动清除
generateFnсгенерировал ссылкуobjфункцию и привязать ее к глобальному объектуwindow, Привести кfnне подлежит переработке иfnцитируетсяobjСделатьobjОн не будет переработан, поэтому происходит утечка памяти.
🌰 Слабая ссылка WeakSet и WeakMap
WeakSet и WeakMap — две новые структуры данных в ES6, и обе их ссылки на значенияНе засчитывается в сбор мусора. WeakSet может хранить только неповторяющиеся объекты, а WeakMap может хранить только пары ключ-значение с объектами в качестве ключей. Когда соответствующий объект становится недоступным извне, соответствующая ему запись в хранилище также будет потеряна.
Анализ слабой карты
Проанализировав следующий код, чтобы проиллюстрировать характеристики его слабой ссылки.
let obj = { index: 0 }
let vs = new WeakSet()
vs.add(obj)
obj = null // 清除obj使在进行GC时清除{ index: 0 }对象
Добавьте объект obj в экземпляр WeakSet. В это время вы можете получить доступ к объекту obj через переменные obj и vs и очистить объект через obj=null. В это время ссылка на объект obj в vs также будетавтоочистка.
После завершения GC (объект obj все еще можно увидеть в прямой печати console.log здесь, потому что GC не завершен), вы можете видеть, что элементы vs пусты.
Кроме того, в процессе изучения исходного кода React я обнаружил, чтоDOMEventListenerMap.jsЕсть практическое применение WeakMap.В следующей статье мы подробно изучим практическое применение WeakMap и WeakSet.
напиши в конце
Теперь, когда вы это видите, пожалуйста, поставьте палец вверх, чтобы поощрить автора :)
Блог автора:blog.lessing.online
Автор гитхаб:github.com/johniexu
[Всесторонний анализ и краткое изложение серии интерфейсов]
Справочная статья
- Механизм памяти JavaScript (требуется для продвинутых студентов, изучающих интерфейс)
- [Перевод] Помощь в смене работы и повышении заработной платы после года, а также глубокое понимание модели памяти JavaScript
- Ruan Yifeng - Учебное пособие по утечке памяти в JavaScript
- Модель памяти Node.js
- Глубокое понимание концепций контекста выполнения JavaScript, стека функций, подъема
- Глубокое понимание замыканий в JavaScript