Говоря о памяти и закрытии js

Node.js внешний интерфейс JavaScript jQuery

Эта статья из моегоgithub

0. Предисловие

В основном он сочетает в себе концепцию памяти, чтобы рассказать о некоторых очень простых, но случайно допущенных ошибках в js. Вывод: порядок выполнения js, сначала определите, потом выполняйте, сверху вниз принцип близости. Замыкания обеспечивают внешний доступ к переменным внутри функции и могут привести к утечке памяти.

1. Сначала поговорим о типе

В типах данных ECMAscript есть базовые типы и ссылочные типы, базовые типы — Undefined, Null, Boolean, Number, String, ссылочные типы — Object, все значения будут одного из 6 типов (типы данных — динамические, нет необходимо определить другие типы данных) Значение ссылочного типа, то есть объект, объект является экземпляром ссылочного типа, который может быть создан оператором new или литеральным образом (объектное литеральное создание var obj = { }). В ECMA есть много нативных ссылочных типов, которые вы видите, просматривая документацию: Function, Number (ссылочный тип для примитивного типа Number), String (ссылочный тип для примитивного типа String), Date, Array. , Boolean (...), Math, RegExp и т.д. При работе программы всю память можно разделить на константный пул (хранит значения базовых типов), стек (хранит переменные), большую кучу (хранит объекты), среду выполнения (когда выполняются функции)

1

Значение базовых типов данных можно получить непосредственно в константном пуле, а ссылочный тип — получить ссылку на объект.

var a = 1;
var b = 'hello';
var c = a;

c = a, копия этого базового типа данных, просто повторно скопируйте независимую копию, создайте новое значение для объекта переменной, а затем скопируйте значение в место, где выделяется новая переменная, a, c их own Операция не повлияет на другую сторону.

a++;console.log(a);console.log(c)

Очевидно, вывод 2, 1

obj1 и obj2 получают ссылку на только что созданный объект (то есть ключ дома, каждый приносит один), при манипулировании объектом объект изменяется, а при доступе к другому объекту обнаруживается, что объект также будет изменить. . Например, один человек в семье вернулся, чтобы убраться, а другой пошел домой и обнаружил, что в доме чисто.

var obj1 = new Object();
obj1.name = 'obj1'
var obj2 = obj1
console.log(obj2)  //{name: "obj1"}

2

Для Vue, почему данные должны быть функцией, которая возвращает объект, также является причиной того, что все экземпляры Vue не могут совместно использовать набор данных. Так что для таких случаев мы можем обрабатывать это как vue

//data是一个对象的时候,共用一套data
function D(){}
D.prototype.data =  {a:1,b:2}
var a = new D()
var b = new D()
a.data.a = 666
b.data.a //666
//data是一个函数的时候,各自维护自己的data
function D(){
	this.data = this.data()
}
D.prototype.data = function () {
	return {
		a:1,b:2
	}
}
var a = new D()
var b = new D()
a.data.a = 666
b.data.a //1

То же самое верно для функций, которые являются ссылочными типами.

var a = function(){console.log(1)}
var b = a;
a = null;
b();a()
//b输出1,a报错:Uncaught TypeError: a is not a function
//a指向函数,b拿到和a一样的指针,然后让a指向空

Превращение a в null просто обрывает ссылочную связь между a и функцией и не влияет на b

2. Переосчитать

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

3

2.1 Почему значения ссылочного типа нужно размещать в куче, а значения примитивного типа — в стеке

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

2.2 Зачем сначала искать в стеке, а потом искать в куче

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

3. Затем идет функция

Сначала задайте вопрос

function a(){console.log(2)};
var a  = function(){console.log(1)};
a()

покрытие? Итак, каков результат обмена?

var a  = function(){console.log(1)};
function a(){console.log(2)};
a()

Все равно 1, а потом некоторые люди говорят, что var имеет приоритет. Итак, почему var имеет приоритет?

4

Сначала определите, а затем выполните, сначала перейдите в стек, чтобы найти

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

 a(); function a(){console.log(666)}//666

другая ситуация:

a(); var a = function (){console.log(666)}//a  is not a function

Несмотря на то, что первый метод имеет продвижение переменных, ошибки нет, обычно он пишется по порядку, а оператор определения помещается впереди. Если вы хотите быть строгим к себе, используйте строгий режим «use strict» вручную. Для разработки фреймворка необходимо строго соблюдать правила, поэтому обычно используется строгий режим.

4. Далее идет временное пространство

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

4.1 например 1:

var a = 10;
function() {
console.log(a);//undefined
var a = 1;
console.log(a)//1
}

Макроскопически есть только два шага 1 и 2. Когда второй шаг будет выполнен, он перейдет внутрь функции для выполнения ②-⑧

5
A = 10 вне функции совершенно не имеет значения. Неопределенность в основном вызвана продвижением переменной. На самом деле, точный порядок таков:

var a
console.log(a);//undefined
a = 1;
console.log(a)//1

Почему бы не поискать глобальное a? Принцип близости. Почему принцип близости? Удостоверьтесь, что внутри функции есть определение, чтобы снаружи не было напрасной траты усилий. На самом деле, если функция найдена в своей собственной области видимости, она не будет продолжать ее искать.Подобно цепочке прототипов, если свойство найдено в конструкторе, оно не будет обращаться к прототипу, чтобы найти его. Функции также ищут цепочку областей действия. Аналогичный пример, мы используем объявление функции для определения функции f, а затем используем переменную g для получения ссылки на эту функцию, а затем используем f снаружи для доступа к функции, но имя f можно найти внутри функции:

var g = function f(){
   console.log(f)
    }
g()//打印整个函数
f()//报错

4.2 eg2

function f(){
return function f1(){
       console.log(1)
   }
};
var res = f();
res();
f1()

res(), возвращает функцию внутри, если вы сразу f1(), она сообщит об ошибке, потому что это window.f1()

6

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

5. Сбор мусора

При фронтенд-разработке почти не нужно заботиться о проблемах с памятью, память, ограниченная V8, почти никогда не используется, и пока мы закрываем браузер, все кончено. Если это нодовый бэкенд, то бэкенд-программа часто выполняет более сложные операции, а если сервер работает долго без перезапуска, если не обращать внимание на управление памятью, накопление приведет к утечкам памяти. Первая часть памяти в узле такая же, как и выше, со стеком, кучей, средой выполнения и буфером для хранения буфера. ты можешь пройтиprocess.memoryUsage()Проверьте использование памяти процессом в узле. Объекты в куче делятся на молодое поколение и старое поколение, и они будут очищаться разными механизмами сборки мусора.

5.1 Новое поколение

Новое поколение использует алгоритм Scavenge для сборки мусора и использует метод репликации для реализации алгоритма восстановления памяти. Его процесс:

  • Разделите общее пространство нового поколения на два, используйте только один из них, а другой - простаистый, ожидая использования мусора для использования. Пространство в использовании вызывается, а неиспользуемое пространство называется
  • Когда запускается сборка мусора, V8 копирует все уцелевшие объекты из пространства From в пространство To.
  • После того, как все объекты, которые должны сохраниться в пространстве From, будут скопированы, исходное пространство From будет освобождено и станет свободным пространством, а исходное пространство To станет используемым пространством, то есть обменом функциями.
  • Если объект подвергся сборке мусора нового поколения и все еще выживает во второй раз, или если пространство To было использовано на 25%, он будет переведен в старое поколение.

1

5.2 Старое поколение

Старое поколение использует метку-очистку (а затем метку-очистку) для сборки мусора. В фазе маркировки (цикл относительно большой) просматриваются все объекты в куче, помечая живые объекты, а в последующей фазе очистки очищаются только объекты, которые не были отмечены. Каждая страница памяти имеет растровое изображение, используемое для маркировки объектов. Этот битмап имеет еще два состояния, используемые для пометки объекта, всего три состояния: не обнаружен сборщиком мусора, обнаружен сборщиком мусора, но соседние объекты еще не обработаны, не обнаружен сборщиком мусора, но все соседние объекты обрабатываются. Соответствует трем цветам: белый, серый, черный.

При обходе в основном используется DFS. В начале все объекты белые. При переходе от корневого объекта пройденные объекты будут выделены серым цветом и помещены в дополнительную открытую очередь. В каждом цикле фазы маркировки сборщик мусора берет объект из очереди, окрашивает его в черный цвет, а соседний объект окрашивает в серый цвет и помещает соседний объект в очередь. Он продолжает зацикливаться, и, наконец, все объекты будут только черными и белыми, а белые будут очищены. Предполагая, что глобальный корневой объект является корневым, тогда живой объект должен быть подключен к дереву объектов, если это мертвый объект, напримерvar a = {};a=nullМы создаем объект, но отделяем его от дерева объектов. Таким образом, DFS его не найдет, он всегда будет белым. Кроме того, после удаления мусорных объектов в процессе пространство памяти разбрасывается по частям.Если вы столкнетесь с объектом, который требует большого объема памяти и должен храниться в большом куске памяти, возникает проблема. Так что есть еще этап сортировки, сортировки объектов в непрерывное распределение в памяти.

5.3 Сравнение

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

6. IIFE и замыкания

6.1 IIFE

Немедленно выполните функцию для формирования среды песочницы, чтобы переменные не загрязняли внутреннюю часть, что является хорошим способом создания различных фреймворков. Начните с написания поддельного jQuery от руки

(function(root){
 var $ = function(){
//代码
}
root.$ = $
})(this)

Таким образом, если мы пишем связанные выражения во внутренней функции, мы можем использовать jQuery со знаком доллара (на самом деле первая скобка jQuery — это глобальная оценка среды, а реальное тело функции помещается во вторую скобку, что известен как самый крепкий в мире.Шипение селектора тоже внутри)

7

6.2 Закрытие

Понятие замыкания имеет свою интерпретацию.Обычно, когда люди спрашивают, что такое замыкание, большинство людей говорят, что функция возвращается в функции, а к переменным внутри функции можно получить доступ вне функции.Эти очевидные явления, или перемещение какие-то многословные статьи. Проще говоря, это доступ к внутренним переменным извне, и пространство памяти, временно открытое внутри, не будет собирать мусор. При поиске значения просмотрите цепочку областей действия и остановитесь, когда найдете ее. Для различных js библиотек он завернут в огромный IIFE, если он будет сборщиком мусора, мы точно не сможем его использовать. И мы действительно можем его использовать, потому что он предоставляет интерфейс, так что глобальная среда сохраняет ссылку на функции и переменные внутри IIFE, и мы можем его использовать.

Различные книги объясняют замыкания:

  • «Полное руководство»: объекты-функции связаны друг с другом через цепочку областей видимости. Переменные внутри функции могут храниться в области действия функции и иметь доступ к переменным в области действия другой функции.
  • «Обманы ниндзя»: когда функция создается, она позволяет себе получать доступ и управлять областью действия, созданной переменными, отличными от ее собственной функции.
  • "JS You Don't Know": это результат написания кода на основе лексической области видимости. Когда функция запоминает и получает доступ к лексической области, в которой она находится, генерируется замыкание. Генерация замыканий может привести к утечкам памяти. Как было сказано ранее, в js есть механизм сборки мусора, если обнаружится, что переменная не используется, то она будет переработана, а замыкания ссылаются друг на друга, чтобы их не перерабатывали, они всегда занимают кусок памяти и удерживать ссылку на часть памяти в течение длительного времени, что может привести к утечке памяти.
var b = 10
function a(){
	var b = 1
	return function c(){//暴露内部函数的接口
		console.log(b)
	}
}
a()()//1,外部拿到内部的引用,临时开辟的内存空间不会被回收

//改写成IIFE形式
var b = 10
var a = (function(){
	var b = 1
	return function c(){
		console.log(b)
	}
})()
a()//1

//改成window对象的一个引用
var b = 10
(function(){
	var b = 1
	window.c =  function(){
		console.log(b)
	}
})()
c()//1

//多个闭包
function a(){
	var s = 1
	return function count(){
		s++
		console.log(s)
	}
}
var b = a()//相当于赋值
var c = a()
b()//2
b()//3
c()//2,各自保持各自的”赋值结果”,互相不干扰

Точка останова, вы можете видеть все это

//r被垃圾回收
function a(){
    var r = 1
	var s = 1
	var q = 666
	return function count(){
		s++
		debugger
		console.log(s, q)
	}
}
//我们可以打个断点,在谷歌浏览器看他的调用栈,发现闭包closure里面没有r了
var b = a()
b()

В последнем примере r и s не такие, как думают некоторые. Если произойдет замыкание, останутся и r, и s. На самом деле, r был переработан. При выполнении функции для этой функции будет создан контекст ctx.В начале этот ctx пустой.При выполнении сверху вниз до объявления закрытия b функции a, так как функция b зависит от переменной s, это будет s Присоединиться к ctx b -- ctx2. Все замыкания внутри a будут содержать этот ctx2. (Итак, причина, по которой закрытие закрыто, заключается в том, что оно содержит этот ctx) Каждое замыкание будет ссылаться на ctx своей внешней функции (здесь ctx2 из b), когда переменная s читается, она захватывается замыканием, добавляется к переменной в ctx, а затем размещается в куче. Настоящая локальная переменная — это r, которая хранится в стеке, а при выполнении b она извлекается из стека и собирается мусор. Закрытие ссылается на ctx a. Если какое-либо замыкание выживет, соответствующий ему ctx выживет, и переменная не будет уничтожена.

image

Мы также слышали поговорку: старайтесь избегать глобальных переменных. На самом деле то же самое, функция возвращает другую функцию, то есть две функции последовательно помещаются в стек вызовов. Мы знаем, что стек работает по принципу «первым пришел — последним вышел», и глобальная переменная (также в самом низу стека), чем больше она не может быть собрана мусором, тем дольше она будет существовать. Но, возможно, глобальная переменная в какой-то момент становится бесполезной и не может быть переработана, вызывая утечку памяти. Таким образом, возникает еще одно общее предостережение: не злоупотребляйте замыканиями. Чем больше она используется, тем глубже стек и тем меньше переменная может быть переработана.

Глобальным объектом браузера является окно, и закрытие браузера, естественно, завершается. Глобальный объект в Node является глобальным.Если в глобале есть атрибут, который больше не нужен, он должен быть установлен в нуль, потому что он не будет уничтожен, пока программа не прекратит работу. Конечно, наши сервера долго не выключаются, и утечки памяти накапливаются.

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