Heim  >  Artikel  >  Web-Frontend  >  Was ist Speicher in Javascript?

Was ist Speicher in Javascript?

青灯夜游
青灯夜游Original
2022-09-20 13:52:561882Durchsuche

In JavaScript bezieht sich Speicher normalerweise auf den Speicherplatz, der vom Betriebssystem vom Hauptspeicher geteilt (abstrahiert) wird. Der Speicher kann in zwei Kategorien unterteilt werden: 1. Stapelspeicher, ein kontinuierlicher Speicherplatz mit geringer Kapazität. Er wird hauptsächlich zum Speichern von Daten wie Funktionsaufrufinformationen und Variablen verwendet. Eine große Anzahl von Speicherzuweisungsvorgängen führt zu einem Stapelüberlauf 2. Heap-Speicher. Die Zuweisung von Heap-Speicher erfolgt dynamisch und diskontinuierlich. Programme können bei Bedarf Heap-Speicherplatz beantragen, die Zugriffsgeschwindigkeit ist jedoch viel langsamer.

Was ist Speicher in Javascript?

Die Betriebsumgebung dieses Tutorials: Windows 7-System, JavaScript-Version 1.8.5, Dell G3-Computer.

JavaScript wurde 1995 geboren und wurde ursprünglich für die Formularvalidierung innerhalb von Webseiten entwickelt.

JavaScript ist im Laufe der Jahre schnell gewachsen und sein Ökosystem ist von Tag zu Tag gewachsen und hat sich zu einer der beliebtesten Entwicklungssprachen unter Programmierern entwickelt. Und jetzt ist JavaScript nicht mehr auf die Webseite beschränkt, sondern wurde auf den Desktop, Mobilgeräte und Server ausgeweitet.

Mit dem Aufkommen der großen Front-End-Ära verwenden immer mehr Entwickler JavaScript, aber viele Entwickler bleiben nur auf dem Niveau „es nutzen können“, ohne mehr über diese Sprache zu wissen.

Wenn Sie ein besserer JavaScript-Entwickler werden möchten, ist das Verständnis des Gedächtnisses ein wichtiger Punkt, der nicht ignoriert werden darf.

Dieser Artikel besteht hauptsächlich aus zwei Teilen:

  • Detaillierte Erklärung des JavaScript-Speichers

  • Anleitung zur Analyse des JavaScript-Speichers

Ich glaube, dass Sie nach dem Lesen dieses Artikels ein umfassenderes Verständnis von JavaScript haben werden Gedächtnis Verstehen Sie und haben Sie die Fähigkeit, Gedächtnisanalysen selbstständig durchzuführen.

Erinnerung


Was ist Erinnerung? Ich glaube, jeder hat ein gewisses Verständnis von Erinnerung. Ich werde es nicht gleich am Anfang erwähnen.

Erstens kann keine Anwendung ohne Speicher ausgeführt werden.

Darüber hinaus hat die Erinnerung, die wir erwähnt haben, auf verschiedenen Ebenen unterschiedliche Bedeutungen.

Hardwareebene (Hardware)

  • Auf der Hardwareebene bezieht sich Speicher auf Arbeitsspeicher.

    Speicher ist ein wichtiger Teil des Computers. Er dient zum Speichern verschiedener für den Anwendungsbetrieb erforderlicher Daten. Die CPU kann Daten direkt mit dem Speicher austauschen, um sicherzustellen, dass die Anwendung reibungslos ausgeführt werden kann.
Im Allgemeinen gibt es zwei Haupttypen von Arbeitsspeicher in einem Computer: Cache und Hauptspeicher.

Der Cache ist meist direkt in der CPU integriert und relativ weit von uns entfernt, daher handelt es sich bei dem von uns genannten (Hardware-)Speicher häufiger um den Hauptspeicher.

?

Direktzugriffsspeicher (Random Access Memory, RAM)

Direktzugriffsspeicher ist in statischen Direktzugriffsspeicher (Static Random Access Memory, SRAM) und dynamischen Direktzugriffsspeicher (Dynamic Random Access Memory, DRAM) unterteilt Hauptkategorien.

In Bezug auf die Geschwindigkeit ist SRAM viel schneller als DRAM, und die Geschwindigkeit von SRAM übertrifft nur die Register in der CPU.

In modernen Computern wird SRAM für den Cache und DRAM für den Hauptspeicher verwendet.

?

Hauptspeicher (Hauptspeicher)

Obwohl der Cache sehr schnell ist, ist seine Speicherkapazität sehr gering und reicht von einigen KB bis zu maximal mehreren zehn MB, was nicht ausreicht, um Anwendungsvorgänge zu speichern . Daten.

Wir benötigen eine Speicherkomponente mit mäßiger Speicherkapazität und Geschwindigkeit, die es uns ermöglicht, Dutzende oder sogar Hunderte von Anwendungen gleichzeitig auszuführen und gleichzeitig die Leistung sicherzustellen. Dies ist die Rolle des Hauptspeichers.

Der Hauptspeicher im Computer ist eigentlich das, was wir normalerweise als Memory Stick (Hardware) bezeichnen.

Hardwarespeicher ist heute nicht unser Thema, das ist also alles. Wenn Sie mehr darüber erfahren möchten, können Sie nach den oben genannten Schlüsselwörtern suchen.

Softwareebene (Software)

  • Auf der Softwareebene bezieht sich Speicher normalerweise auf den Speicherplatz, der vom Betriebssystem vom Hauptspeicher geteilt (abstrahiert) wird.

    Derzeit kann der Speicher in zwei Kategorien unterteilt werden: Stapelspeicher und Heapspeicher.
Als nächstes erkläre ich das Gedächtnis rund um die JavaScript-Sprache.

Der in den folgenden Artikeln erwähnte Speicher bezieht sich auf den Speicher auf Softwareebene. „Stack & Heap“: Stapelspeicher , und alle Daten folgen dem Last-In First-Out (LIFO)-Prinzip.

Das treffendste Beispiel im wirklichen Leben ist der Badminton-Eimer. Normalerweise greifen wir nur auf eine Seite des Eimers zu. Mitnahme.

Der Grund, warum der Stapelspeicher Stapelspeicher genannt wird, liegt darin, dass der Stapelspeicher die Stapelstruktur verwendet.

Stapelspeicher ist ein kontinuierlicher Speicherplatz. Dank der Einfachheit und Direktheit der Stapelstruktur sind die Zugriffs- und Betriebsgeschwindigkeit des Stapelspeichers sehr hoch.

Der Stapelspeicher hat eine geringe Kapazität und wird hauptsächlich zum Speichern von Daten wie Funktionsaufrufinformationen und Variablen verwendet. Eine große Anzahl von Speicherzuweisungsvorgängen führt zu einem Stapelüberlauf.

Die Datenspeicherung im Stapelspeicher ist grundsätzlich temporär und die Daten werden sofort nach der Verwendung recycelt (z. B. werden lokale Variablen, die innerhalb einer Funktion erstellt wurden, nach der Rückkehr der Funktion recycelt).

Um es einfach auszudrücken: Stack-Speicher eignet sich zum Speichern von Daten mit kurzem Lebenszyklus, geringem Platzbedarf und festen Daten.

Was ist Speicher in Javascript?

? Die Größe des Stapelspeichers

Der Stapelspeicher wird direkt vom Betriebssystem verwaltet, sodass die Größe des Stapelspeichers auch vom Betriebssystem bestimmt wird.

Im Allgemeinen verfügt jeder Thread (Thread) über einen unabhängigen Stapelspeicherplatz. Die Standardgröße des von Windows jedem Thread zugewiesenen Stapelspeichers beträgt 1 MB.

  • Heap-Speicher

? Heap

Heap ist ebenfalls eine gängige Datenstruktur, liegt aber außerhalb des Rahmens dieses Artikels, daher werde ich nicht viel mehr sagen.

Obwohl der Name des Heapspeichers das Wort „Heap“ enthält, hat er nichts mit dem Heap in der Datenstruktur zu tun. Es ist nur ein Name.

Heap-Speicher ist ein großer Speicherplatz. Die Zuweisung von Heap-Speicher erfolgt dynamisch und diskontinuierlich. Programme können bei Bedarf Heap-Speicherplatz beantragen, aber die Zugriffsgeschwindigkeit ist viel langsamer als beim Stapelspeicher.

Daten im Heap-Speicher können über einen längeren Zeitraum vorhanden sein. Nutzlose Daten müssen vom Programm aktiv recycelt werden. Wenn eine große Menge nutzloser Daten Speicher beansprucht, führt dies zu einem Speicherverlust.

Einfach ausgedrückt: Der Heapspeicher eignet sich zum Speichern von Daten mit einem langen Lebenszyklus, die viel Platz beanspruchen oder unregelmäßigen Platz belegen.

Was ist Speicher in Javascript?

? Obergrenze des Heap-Speichers

In Node.js beträgt die Standardobergrenze des Heap-Speichers etwa 1,4 GB in 64-Bit-Systemen und 0,7 GB in 32-Bit-Systemen.

In Chrome beträgt das Speicherlimit pro Tab etwa 4 GB (64-Bit-Systeme) und 1 GB (32-Bit-Systeme).

? Prozess-, Thread- und Heap-Speicher

Im Allgemeinen verfügt ein Prozess (Prozess) nur über einen Heap-Speicher, und mehrere Threads unter demselben Prozess teilen sich denselben Heap-Speicher.

Im Chrome-Browser verfügt im Allgemeinen jeder Tab über einen separaten Prozess, aber in manchen Fällen teilen sich mehrere Tabs einen Prozess.

  • Funktionsaufruf

Nachdem wir verstanden haben, was Stapelspeicher und Heapspeicher sind, wollen wir nun sehen, was mit Stapelspeicher und Heapspeicher passiert, wenn eine Funktion aufgerufen wird.

Wenn eine Funktion aufgerufen wird, wird sie in den Stapelspeicher verschoben, um einen Stapelrahmen zu generieren, der aus der Rücksprungadresse, Parametern und lokalen Variablen der Funktion besteht, wenn die Funktion eine andere aufruft Wenn eine Funktion aufgerufen wird, wird eine andere Funktion in den Stapelspeicher verschoben und der Zyklus beginnt erneut. Bis die letzte Funktion zurückkehrt, werden die Elemente im Stapelspeicher nacheinander vom oberen Rand des Stapels abgelegt, bis sie vorhanden sind Es befinden sich keine Elemente mehr im Stapelspeicher. Der Aufruf wird beendet.

Was ist Speicher in Javascript?

Der Inhalt im obigen Bild wurde vereinfacht, wobei die Konzepte von Stapelrahmen und verschiedenen Zeigern entfernt wurden und hauptsächlich der allgemeine Prozess des Funktionsaufrufs und der Speicherzuweisung gezeigt wurde.

Im selben Thread (JavaScript ist Single-Threaded) werden alle ausgeführten Funktionen und Funktionsparameter sowie lokalen Variablen in denselben Stapelspeicher verschoben. Dies bedeutet, dass eine große Menge an Rekursion zu einem Stapelüberlauf führt.

Bitte lesen Sie weiter, um Einzelheiten zur Speicherzuordnung interner Variablen der im Bild beteiligten Funktionen zu erfahren.

  • Variablen speichern

Wenn das JavaScript-Programm ausgeführt wird, werden im nicht-globalen Bereich generierte lokale Variablen im Stapelspeicher gespeichert.

Allerdings speichern nur Variablen vom primitiven Typ tatsächlich Werte im Stapelspeicher.

Variablen vom Referenztyp speichern nur eine Referenz (Referenz) im Stapelspeicher, und diese Referenz verweist auf den tatsächlichen Wert im Heapspeicher.

? Primitivtyp (Primitivtyp)

Primitivtypen werden auch als Basistypen bezeichnet, einschließlich stringnumberbigintbooleanundefinednullsymbol (neu in ES6).

Werte primitiver Typen werden als primitive Werte bezeichnet.

Ergänzung: Obwohl typeof null 'object' zurückgibt, ist null tatsächlich kein Objekt, das durch JavaScript verursacht wird ~typeof null 返回的是 'object',但是 null 真的不是对象,会出现这样的结果其实是 JavaScript 的一个 Bug~

? 引用类型(Reference type)

除了原始类型外,其余类型都属于引用类型,包括 ObjectArrayFunctionDateRegExpStringNumberBoolean 等等…

实际上 Object 是最基本的引用类型,其他引用类型均继承自 Object。也就是说,所有引用类型的值实际上都是对象。

引用类型的值被称为引用值(Reference value)。

? 简单来说

在多数情况下,原始类型的数据储存在Was ist Speicher in Javascript?,而引用类型的数据(对象)则储存在Was ist Speicher in Javascript?。

Was ist Speicher in Javascript?

特别注意(Attention)

全局变量以及被闭包引用的变量(即使是原始类型)均储存在Was ist Speicher in Javascript?中。

? 全局变量(Global variables)

在全局作用域下创建的所有变量都会成为全局对象(如 window

?

Referenztyp (Referenztyp)

Mit Ausnahme primitiver Typen sind alle anderen Typen Referenztypen, einschließlich Object, Array, Funktion, Datum, RegExp, String, Nummer, Boolean code> etc...

Tatsächlich ist Object der grundlegendste Referenztyp, und andere Referenztypen erben von Object. Das heißt, alle Referenztypwerte sind tatsächlich Objekte.

Werte von Referenztypen werden Referenzwerte genannt.

? Um es einfach auszudrücken:

In den meisten Fällen werden Daten vom primitiven Typ im Stapelspeicher gespeichert, während Daten vom Referenztyp (Objekte) im Heapspeicher gespeichert werden.

Speicherung von Variablen

Besondere Aufmerksamkeit (Achtung )

Globale Variablen und Variablen, auf die durch Abschlüsse verwiesen wird (sogar primitive Typen), werden im Heap-Speicher gespeichert.

? Globale Variablen

Alle im globalen Bereich erstellten Variablen werden zu Attributen globaler Objekte (z. B. window-Objekte), also globale Variablen.

Globale Objekte werden im Heap-Speicher gespeichert, daher müssen auch globale Variablen im Heap-Speicher gespeichert werden.

Fragen Sie mich nicht, warum globale Objekte im Heap-Speicher gespeichert werden, ich werde nach einer Weile rausfallen!

? Abschlüsse

Variablen, die innerhalb einer Funktion (lokaler Bereich) erstellt werden, sind alle lokale Variablen. Wenn eine lokale Variable von anderen Funktionen als der aktuellen Funktion referenziert wird (d. h.
Escape

auftritt), kann die lokale Variable bei der Rückgabe der aktuellen Funktion nicht recycelt werden, dann muss die Variable im Heap gespeichert werden Erinnerung. Die „anderen Funktionen“ hier sind das, was wir Abschlüsse nennen, genau wie im folgenden Beispiel:

function getCounter() {
  let count = 0;
  function counter() {
    return ++count;
  }
  return counter;
}
// closure 是一个闭包函数
// 变量 count 发生了逃逸
let closure = getCounter();
closure(); // 1
closure(); // 2
closure(); // 3

Abschluss ist ein sehr wichtiges und häufig verwendetes Konzept, das in vielen Programmiersprachen vorkommt. Ich werde es hier nicht im Detail vorstellen, aber ich werde einen Artikel von Ruan Yifeng veröffentlichen.

JavaScript-Verschlüsse lernen: http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

? Escape-Analyse (Escape-Analyse)

Tatsächlich werden JavaScript-Engines verwendet Escape-Analyse, um zu entscheiden, ob die Variable im Stapelspeicher oder im Heapspeicher gespeichert werden soll. Einfach ausgedrückt ist die Escape-Analyse ein Mechanismus zur Analyse des Umfangs von Variablen.

Unveränderlich und veränderlich

Was ist Speicher in Javascript?

Zwei Arten von variablen Daten werden im Stapelspeicher gespeichert: Originalwert und Objektreferenz.

Nicht nur die Typen sind unterschiedlich, auch ihre spezifischen Leistungen im Stapelspeicher sind unterschiedlich.

Primitive Werte
?

Primitive Werte sind unveränderlich!

Wie bereits erwähnt: Primitive Daten (primitive Werte) werden direkt im Stapelspeicher gespeichert.
Wenn wir eine Variable vom primitiven Typ definieren,

aktiviert JavaScript einen Speicher im Stapelspeicher, um den Wert der Variablen (Originalwert) zu speichern.

Wenn wir den Wert einer Variablen vom primitiven Typ ändern, aktiviert

tatsächlich einen neuen Speicher, um den neuen Wert zu speichern und die Variable auf den neuen Speicherbereich zu verweisen , anstatt den ursprünglichen Speicher zu ändern. Wenn wir eine Variable vom primitiven Typ einer anderen neuen Variablen (d. h. einer Kopiervariablen) zuweisen, wird ein neuer Speicher aktiviert und eine Kopie des Werts im Quellvariablenspeicher wird in die neue Variable kopiert. in der Erinnerung .

? Kurz gesagt:

Der ursprüngliche Wert im Stapelspeicher kann nicht mehr geändert (unveränderlich) werden, sobald er ermittelt wurde.

Vergleich der ursprünglichen Werte (Vergleich)🎜🎜🎜🎜Wenn wir Variablen vom primitiven Typ vergleichen, vergleichen wir die Werte im Stapelspeicher direkt sind gleich. 🎜🎜
let a = '123';
let b = '123';
let c = '110';
let d = 123;
console.log(a === b); // true
console.log(a === c); // false
console.log(a === d); // false
🎜🎜Objektreferenzen🎜🎜🎜? 🎜Objektreferenzen sind veränderbar!🎜🎜🎜Wie bereits erwähnt: Referenztypvariablen speichern nur eine Referenz auf den Heapspeicher im Stapelspeicher. 🎜🎜🎜⑴🎜 Wenn wir eine Variable vom Referenztyp definieren, findet JavaScript zunächst 🎜einen geeigneten Platz im Heap-Speicher zum Speichern des Objekts🎜 und 🎜aktiviert einen Teil des Stapelspeichers zum Speichern der Referenz des Objekts (Heap-Speicher). Adresse)🎜 und schließlich 🎜die Variable auf diesen Stapelspeicher verweisen🎜. 🎜🎜🎜 Wenn wir also über eine Variable auf ein Objekt zugreifen, sollte der tatsächliche Zugriffsprozess wie folgt aussehen: 🎜🎜🎜Variable -> Wert im Heapspeicher🎜🎜

当我们把引用类型变量赋值给另一个变量时,会将源变量指向的Was ist Speicher in Javascript?中的对象引用复制到新变量的Was ist Speicher in Javascript?中,所以实际上只是复制了个对象引用,并没有在Was ist Speicher in Javascript?中生成一份新的对象。

而当我们给引用类型变量分配为一个新的对象时,则会直接修改变量指向的Was ist Speicher in Javascript?中的引用,新的引用指向Was ist Speicher in Javascript?中新的对象。

Was ist Speicher in Javascript?

? 总之就是:Was ist Speicher in Javascript?中的对象引用是可以被更改的(可变的)。

对象的比较(Comparison)

所有引用类型的值实际上都是对象。

当我们比较引用类型的变量时,实际上是在比较Was ist Speicher in Javascript?中的引用,只有引用相同时变量才相等。

即使是看起来完全一样的两个引用类型变量,只要他们的引用的不是同一个值,那么他们就是不一样。

// 两个变量指向的是两个不同的引用
// 虽然这两个对象看起来完全一样
// 但它们确确实实是不同的对象实例
let a = { name: 'pp' }
let b = { name: 'pp' }
console.log(a === b); // false
// 直接赋值的方式复制的是对象的引用
let c = a;
console.log(a === c); // true
对象的深拷贝(Deep copy)

当我们搞明白引用类型变量在内存中的表现时,就能清楚地理解为什么浅拷贝对象是不可靠的

在浅拷贝中,简单的赋值只会复制对象的引用,实际上新变量和源变量引用的都是同一个对象,修改时也是修改的同一个对象,这显然不是我们想要的。

想要真正的复制一个对象,就必须新建一个对象,将源对象的属性复制过去;如果遇到引用类型的属性,那就再新建一个对象,继续复制…

此时我们就需要借助递归来实现多层次对象的复制,这也就是我们说的深拷贝。

对于任何引用类型的变量,都应该使用深拷贝来复制,除非你很确定你的目的就是复制一个引用。

内存生命周期(Memory life cycle)

通常来说,所有应用程序的内存生命周期都是基本一致的:

分配 -> 使用 -> 释放

当我们使用高级语言编写程序时,往往不会涉及到内存的分配与释放操作,因为分配与释放均已经在底层语言中实现了。

对于 JavaScript 程序来说,内存的分配与释放是由 JavaScript 引擎自动完成的(目前的 JavaScript 引擎基本都是使用 C++ 或 C 编写的)。

但是这不意味着我们就不需要在乎内存管理,了解内存的更多细节可以帮助我们写出性能更好,稳定性更高的代码。

垃圾回收(Garbage collection)

垃圾回收即我们常说的 GC(Garbage collection),也就是清除内存中不再需要的数据,释放内存空间。

由于Was ist Speicher in Javascript?由操作系统直接管理,所以当我们提到 GC 时指的都是Was ist Speicher in Javascript?的垃圾回收。

基本上现在的浏览器的 JavaScript 引擎(如 V8 和 SpiderMonkey)都实现了垃圾回收机制,引擎中的垃圾回收器(Garbage collector)会定期进行垃圾回收。

? 紧急补课

在我们继续之前,必须先了解“可达性”和“内存泄露”这两个概念:

? 可达性(Reachability)

在 JavaScript 中,可达性指的是一个变量是否能够直接或间接通过全局对象访问到,如果可以那么该变量就是可达的(Reachable),否则就是不可达的(Unreachable)。

Was ist Speicher in Javascript?

上图中的节点 9 和节点 10 均无法通过节点 1(根节点)直接或间接访问,所以它们都是不可达的,可以被安全地回收。

? 内存泄漏(Memory leak)

内存泄露指的是程序运行时由于某种原因未能释放那些不再使用的内存,造成内存空间的浪费。

轻微的内存泄漏或许不太会对程序造成什么影响,但是一旦泄露变严重,就会开始影响程序的性能,甚至导致程序的崩溃。

垃圾回收算法(Algorithms)

垃圾回收的基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。

实际上,在回收过程中想要确定一个变量是否还有用并不简单。

直到现在也还没有一个真正完美的垃圾回收算法,接下来介绍 3 种最广为人知的垃圾回收算法。

标记-清除(Mark-and-Sweep)

标记清除算法是目前最常用的垃圾收集算法之一。

从该算法的名字上就可以看出,算法的关键就是标记清除

标记指的是标记变量的状态的过程,标记变量的具体方法有很多种,但是基本理念是相似的。

对于标记算法我们不需要知道所有细节,只需明白标记的基本原理即可。

需要注意的是,这个算法的效率不算高,同时会引起内存碎片化的问题。

? 举个栗子

当一个变量进入执行上下文时,它就会被标记为“处于上下文中”;而当变量离开执行上下文时,则会被标记为“已离开上下文”。

? 执行上下文(Execution context)

执行上下文是 JavaScript 中非常重要的概念,简单来说的是代码执行的环境。

如果你现在对于执行上下文还不是很了解,我强烈建议你抽空专门去学习下!!!

垃圾回收器将定期扫描内存中的所有变量,将处于上下文中以及被处于上下文中的变量引用的变量的标记去除,将其余变量标记为“待删除”。

随后,垃圾回收器会清除所有带有“待删除”标记的变量,并释放它们所占用的内存。

标记-整理(Mark-Compact)

准确来说,Compact 应译为紧凑、压缩,但是在这里我觉得用“整理”更为贴切。

标记整理算法也是常用的垃圾收集算法之一。

使用标记整理算法可以解决内存碎片化的问题(通过整理),提高内存空间的可用性。

但是,该算法的标记阶段比较耗时,可能会堵塞主线程,导致程序长时间处于无响应状态。

虽然算法的名字上只有标记和整理,但这个算法通常有 3 个阶段,即标记整理清除

? 以 V8 的标记整理算法为例

首先,在标记阶段,垃圾回收器会从全局对象(根)开始,一层一层往下查询,直到标记完所有活跃的对象,那么剩下的未被标记的对象就是不可达的了。

V8 的标记阶段

然后是整理阶段(碎片整理),垃圾回收器会将活跃的(被标记了的)对象往内存空间的一端移动,这个过程可能会改变内存中的对象的内存地址。

最后来到清除阶段,垃圾回收器会将边界后面(也就是最后一个活跃的对象后面)的对象清除,并释放它们占用的内存空间。

V8 的标记整理算法

引用计数(Reference counting)

引用计数算法是基于“引用计数”实现的垃圾回收算法,这是最初级但已经被弃用的垃圾回收算法。

引用计数算法需要 JavaScript 引擎在程序运行时记录每个变量被引用的次数,随后根据引用的次数来判断变量是否能够被回收。

虽然垃圾回收已不再使用引用计数算法,但是引用计数技术仍非常有用!

? 举个栗子

注意:垃圾回收不是即使生效的!但是在下面的例子中我们将假设回收是立即生效的,这样会更好理解~

// 下面我将 name 属性为 ππ 的对象简称为 ππ
// 而 name 属性为 pp 的对象则简称为 pp
// ππ 的引用:1,pp 的引用:1
let a = {
  name: 'ππ',
  z: {
    name: 'pp'
  }
}

// b 和 a 都指向 ππ
// ππ 的引用:2,pp 的引用:1
let b = a;

// x 和 a.z 都指向 pp
// ππ 的引用:2,pp 的引用:2
let x = a.z;

// 现在只有 b 还指向 ππ
// ππ 的引用:1,pp 的引用:2
a = null;

// 现在 ππ 没有任何引用了,可以被回收了
// 在 ππ 被回收后,pp 的引用也会相应减少
// ππ 的引用:0,pp 的引用:1
b = null;

// 现在 pp 也可以被回收了
// ππ 的引用:0,pp 的引用:0
x = null;

// 哦豁,这下全完了!

? 循环引用(Circular references)

引用计数算法看似很美好,但是它有一个致命的缺点,就是无法处理循环引用的情况。

在下方的例子中,当 foo() 函数执行完毕之后,对象 ab 都已经离开了作用域,理论上它们都应该能够被回收才对。

但是由于它们互相引用了对方,所以垃圾回收器就认为他们都还在被引用着,导致它们哥俩永远都不会被回收,这就造成了内存泄露

function foo() {
  let a = { o: null };
  let b = { o: null };
  a.o = b;
  b.o = a;
}
foo();
// 即使 foo 函数已经执行完毕
// 对象 a 和 b 均已离开函数作用域
// 但是 a 和 b 还在互相引用
// 那么它们这辈子都不会被回收了
// Oops!内存泄露了!

V8 中的垃圾回收(GC in V8)

8️⃣ V8

V8 是一个由 Google 开源的用 C++ 编写的高性能 JavaScript 引擎。

V8 是目前最流行的 JavaScript 引擎之一,我们熟知的 Chrome 浏览器和 Node.js 等软件都在使用 V8。

在 V8 的内存管理机制中,把Was ist Speicher in Javascript?(Heap memory)划分成了多个区域。

V8 常驻集

这里我们只关注这两个区域:

  • New Space(新空间):又称 Young generation(新世代),用于储存新生成的对象,由 Minor GC 进行管理。
  • Old Space(旧空间):又称 Old generation(旧世代),用于储存那些在两次 GC 后仍然存活的对象,由 Major GC 进行管理。

也就是说,只要 New Space 里的对象熬过了两次 GC,就会被转移到 Old Space,变成老油条。

? 双管齐下

V8 内部实现了两个垃圾回收器:

  • Minor GC(副 GC):它还有个名字叫做 Scavenger(清道夫),具体使用的是 Cheney’s Algorithm(Cheney 算法)。
  • Major GC(主 GC):使用的是文章前面提到的 Mark-Compact Algorithm(标记-整理算法)。

储存在 New Space 里的新生对象大多都只是临时使用的,而且 New Space 的容量比较小,为了保持内存的可用率,Minor GC 会频繁地运行。

而 Old Space 里的对象存活时间都比较长,所以 Major GC 没那么勤快,这一定程度地降低了频繁 GC 带来的性能损耗。

? 加点魔法

我们在上方的“标记整理算法”中有提到这个算法的标记过程非常耗时,所以很容易导致应用长时间无响应。

为了提升用户体验,V8 还实现了一个名为Was ist Speicher in Javascript?(Incremental marking)的特性。

Was ist Speicher in Javascript?的要点就是把标记工作分成多个小段,夹杂在主线程(Main thread)的 JavaScript 逻辑中,这样就不会长时间阻塞主线程了。

Was ist Speicher in Javascript?

当然Was ist Speicher in Javascript?也有代价的,在Was ist Speicher in Javascript?过程中所有对象的变化都需要通知垃圾回收器,好让垃圾回收器能够正确地标记那些对象,这里的“通知”也是需要成本的。

另外 V8 中还有使用工作线程(Worker thread)实现的平行标记(Parallel marking)和并行标记(Concurrent marking),这里我就不再细说了~

? 总结一下

为了提升性能和用户体验,V8 内部做了非常非常多的“骚操作”,本文提到的都只是冰山一角,但足以让我五体投地佩服连连!

总之就是非常 Amazing 啊~

内存管理(Memory management)

或者说是:内存优化(Memory optimization)?

虽然我们写代码的时候一般不会直接接触内存管理,但是有一些注意事项可以让我们避免引起内存问题,甚至提升代码的性能。

全局变量(Global variable)

全局变量的访问速度远不及局部变量,应尽量避免定义非必要的全局变量。

在我们实际的项目开发中,难免会需要去定义一些全局变量,但是我们必须谨慎使用全局变量。

因为全局变量永远都是可达的,所以全局变量永远不会被回收。

? 还记得“可达性”这个概念吗?

因为全局变量直接挂载在全局对象上,也就是说全局变量永远都可以通过全局对象直接访问。

所以全局变量永远都是可达的,而可达的变量永远都不会被回收。

? 应该怎么做?

当一个全局变量不再需要用到时,记得解除其引用(置空),好让垃圾回收器可以释放这部分内存。

// 全局变量不会被回收
window.me = {
  name: '吴彦祖',
  speak: function() {
    console.log(`我是${this.name}`);
  }
};
window.me.speak();
// 解除引用后才可以被回收
window.me = null;

隐藏类(HiddenClass)

实际上的隐藏类远比本文所提到的复杂,但是今天的主角不是它,所以我们点到为止。

在 V8 内部有一个叫做“隐藏类”的机制,主要用于提升对象(Object)的性能。

V8 里的每一个 JS 对象(JS Objects)都会关联一个隐藏类,隐藏类里面储存了对象的形状(特征)和属性名称到属性的映射等信息。

隐藏类内记录了每个属性的内存偏移(Memory offset),后续访问属性的时候就可以快速定位到对应属性的内存位置,从而提升对象属性的访问速度。

在我们创建对象时,拥有完全相同的特征(相同属性且相同顺序)的对象可以共享同一个隐藏类。

? 再想象一下

我们可以把隐藏类想象成工业生产中使用的模具,有了模具之后,产品的生产效率得到了很大的提升。

但是如果我们更改了产品的形状,那么原来的模具就不能用了,又需要制作新的模具才行。

? 举个栗子

在 Chrome 浏览器 Devtools 的 Console 面板中执行以下代码:

// 对象 A
let objectA = {
  id: 'A',
  name: '吴彦祖'
};
// 对象 B
let objectB = {
  id: 'B',
  name: '彭于晏'
};
// 对象 C
let objectC = {
  id: 'C',
  name: '刘德华',
  gender: '男'
};
// 对象 A 和 B 拥有完全相同的特征
// 所以它们可以使用同一个隐藏类
// good!

随后在 Memory 面板打一个堆快照,通过堆快照中的 Comparison 视图可以快速找到上面创建的 3 个对象:

注:关于如何查看内存中的对象将会在文章的第二大部分中进行讲解,现在让我们专注于隐藏类。

Was ist Speicher in Javascript?

在上图中可以很清楚地看到对象 A 和 B 确实使用了同一个隐藏类。

而对象 C 因为多了一个 gender 属性,所以不能和前面两个对象共享隐藏类。

? 动态增删对象属性

一般情况下,当我们动态修改对象的特征(增删属性)时,V8 会为该对象分配一个能用的隐藏类或者创建一个新的隐藏类(新的分支)。

例如动态地给对象增加一个新的属性:

注:这种操作被称为“先创建再补充(ready-fire-aim)”。

// 增加 gender 属性
objectB.gender = '男';
// 对象 B 的特征发生了变化
// 多了一个原本没有的 gender 属性
// 导致对象 B 不能再与 A 共享隐藏类
// bad!

动态删除(delete)对象的属性也会导致同样的结果:

// 删除 name 属性
delete objectB.name;
// A:我们不一样!
// bad!

不过,添加数组索引属性(Array-indexed properties)并不会有影响:

其实就是用整数作为属性名,此时 V8 会另外处理。

// 增加 1 属性
objectB[1] = '数字组引属性';
// 不影响共享隐藏类
// so far so good!

? 那问题来了

说了这么多,隐藏类看起来确实可以提升性能,那它和内存又有什么关系呢?

实际上,隐藏类也需要占用内存空间,这其实就是一种用空间换时间的机制。

如果由于动态增删对象属性而创建了大量隐藏类和分支,结果就是会浪费不少内存空间。

? 举个栗子

创建 1000 个拥有相同属性的对象,内存中只会多出 1 个隐藏类。

而创建 1000 个属性信息完全不同的对象,内存中就会多出 1000 个隐藏类。

? 应该怎么做?

所以,我们要尽量避免动态增删对象属性操作,应该在构造函数内就一次性声明所有需要用到的属性。

如果确实不再需要某个属性,我们可以将属性的值设为 null,如下:

// 将 age 属性置空
objectB.age = null;
// still good!

另外,相同名称的属性尽量按照相同的顺序来声明,可以尽可能地让更多对象共享相同的隐藏类。

即使遇到不能共享隐藏类的情况,也至少可以减少隐藏类分支的产生。

其实动态增删对象属性所引起的性能问题更为关键,但因本文篇幅有限,就不再展开了。

闭包(Closure)

前面有提到:被闭包引用的变量储存在Was ist Speicher in Javascript?中。

这里我们再重点关注一下闭包中的内存问题,还是前面的例子:

function getCounter() {
  let count = 0;
  function counter() {
    return ++count;
  }
  return counter;
}
// closure 是一个闭包函数
let closure = getCounter();
closure(); // 1
closure(); // 2
closure(); // 3

现在只要我们一直持有变量(函数) closure,那么变量 count 就不会被释放。

或许你还没有发现风险所在,不如让我们试想变量 count 不是一个数字,而是一个巨大的数组,一但这样的闭包多了,那对于内存来说就是灾难。

// 我将这个作品称为:闭包炸弹
function closureBomb() {
  const handsomeBoys = [];
  setInterval(() => {
    for (let i = 0; i < 100; i++) {
      handsomeBoys.push(
        { name: &#39;陈皮皮&#39;, rank: 0 },
        { name: &#39; 你 &#39;, rank: 1 },
        { name: &#39;吴彦祖&#39;, rank: 2 },
        { name: &#39;彭于晏&#39;, rank: 3 },
        { name: &#39;刘德华&#39;, rank: 4 },
        { name: &#39;郭富城&#39;, rank: 5 }
      );
    }
  }, 100);
}
closureBomb();
// 即将毁灭世界
// ? ? ? ?

? 应该怎么做?

所以,我们必须避免滥用闭包,并且谨慎使用闭包!

当不再需要时记得解除闭包函数的引用,让闭包函数以及引用的变量能够被回收。

closure = null;
// 变量 count 终于得救了

如何分析内存(Analyze)


说了这么多,那我们应该如何查看并分析程序运行时的内存情况呢?

“工欲善其事,必先利其器。”

对于 Web 前端项目来说,分析内存的最佳工具非 Memory 莫属!

这里的 Memory 指的是 DevTools 中的一个工具,为了避免混淆,下面我会用“Memory 面板”或”内存面板“代称。

? DevTools(开发者工具)

DevTools 是浏览器里内置的一套用于 Web 开发和调试的工具。

使用 Chromuim 内核的浏览器都带有 DevTools,个人推荐使用 Chrome 或者 Edge(新)。

Memory in Devtools(内存面板)

在我们切换到 Memory 面板后,会看到以下界面(注意标注):

Memory 面板

在这个面板中,我们可以通过 3 种方式来记录内存情况:

  • Heap snapshot:堆快照
  • Allocation instrumentation on timeline:内存分配时间轴
  • Allocation sampling:内存分配采样

小贴士:点击面板左上角的 Collect garbage 按钮(垃圾桶图标)可以主动触发垃圾回收。

? 在正式开始分析内存之前,让我们先学习几个重要的概念:

? Shallow Size(浅层大小)

浅层大小指的是当前对象自身占用的内存大小。

浅层大小不包含自身引用的对象。

? Retained Size(保留大小)

保留大小指的是当前对象被 GC 回收后总共能够释放的内存大小。

换句话说,也就是当前对象自身大小加上对象直接或间接引用的其他对象的大小总和。

需要注意的是,保留大小不包含那些除了被当前对象引用之外还被全局对象直接或间接引用的对象。

Heap snapshot(堆快照)

Was ist Speicher in Javascript?

堆快照可以记录页面当前时刻的 JS 对象以及 DOM 节点的内存分配情况。

? 如何开始

点击页面底部的 Take snapshot 按钮或者左上角的 ⚫ 按钮即可打一个堆快照,片刻之后就会自动展示结果。

Was ist Speicher in Javascript?

在堆快照结果页面中,我们可以使用 4 种不同的视图来观察内存情况:

  • Summary:摘要视图
  • Comparison:比较视图
  • Containment:包含视图
  • Statistics:统计视图

默认显示 Summary 视图。

Summary(摘要视图)

摘要视图根据 Constructor(构造函数)来将对象进行分组,我们可以在 Class filter(类过滤器)中输入构造函数名称来快速筛选对象。

Was ist Speicher in Javascript?

页面中的几个关键词:

  • Constructor:构造函数。
  • Distance:(根)距离,对象与 GC 根之间的最短距离。
  • Shallow Size:浅层大小,单位:Bytes(字节)。
  • Retained Size:保留大小,单位:Bytes(字节)。
  • Retainers:持有者,也就是直接引用目标对象的变量。

? Retainers(持有者)

Retainers 栏在旧版的 Devtools 里叫做 Object’s retaining tree(对象保留树)。

Retainers 下的对象也展开为树形结构,方便我们进行引用溯源。

在视图中的构造函数列表中,有一些用“()”包裹的条目:

  • (compiled code):已编译的代码。
  • (closure):闭包函数。
  • (array, string, number, symbol, regexp):对应类型(ArrayStringNumberSymbolRegExp)的数据。
  • (concatenated string):使用 concat() 函数拼接而成的字符串。
  • (sliced string):使用 slice()substring() 等函数进行边缘切割的字符串。
  • (system):系统(引擎)产生的对象,如 V8 创建的 HiddenClasses(隐藏类)和 DescriptorArrays(描述符数组)等数据。

? DescriptorArrays(描述符数组)

描述符数组主要包含对象的属性名信息,是隐藏类的重要组成部分。

不过描述符数组内不会包含整数索引属性。

而其余没有用“()”包裹的则为全局属性和 GC 根。

另外,每个对象后面都会有一串“@”开头的数字,这是对象在内存中的唯一 ID。

小贴士:按下快捷键 Ctrl/Command + F 展示搜索栏,输入名称或 ID 即可快速查找目标对象。

? 实践一下:Was ist Speicher in Javascript?

切换到 Console 面板,执行以下代码来Was ist Speicher in Javascript?:

function TestClass() {
  this.number = 123;
  this.string = &#39;abc&#39;;
  this.boolean = true;
  this.symbol = Symbol(&#39;test&#39;);
  this.undefined = undefined;
  this.null = null;
  this.object = { name: &#39;pp&#39; };
  this.array = [1, 2, 3];
  this.getSet = {
    _value: 0,
    get value() {
      return this._value;
    },
    set value(v) {
      this._value = v;
    }
  };
}
let testObject = new TestClass();

Was ist Speicher in Javascript?

回到 Memory 面板,打一个堆快照,在 Class filter 中输入“TestClass”:

可以看到内存中有一个 TestClass 的实例,该实例的浅层大小为 80 字节,保留大小为 876 字节。

Was ist Speicher in Javascript?

? 注意到了吗?

堆快照中的 TestClass 实例的属性中少了一个名为 number 属性,这是因为堆快照不会捕捉数字属性。

? 实践一下:创建一个字符串

切换到 Console 面板,执行以下代码来创建一个字符串:

// 这是一个全局变量
let testString = &#39;我是吴彦祖&#39;;

回到 Memory 面板,打一个堆快照,打开搜索栏(Ctrl/Command + F)并输入“我是吴彦祖”:

Was ist Speicher in Javascript?

Comparison(比较视图)

只有同时存在 2 个或以上的堆快照时才会出现 Comparison 选项。

比较视图用于展示两个堆快照之间的差异。

使用比较视图可以让我们快速得知在执行某个操作后的内存变化情况(如新增或减少对象)。

通过多个快照的对比还可以让我们快速判断并定位内存泄漏。

文章前面提到隐藏类的时候,就是使用了比较视图来快速查找新创建的对象。

? 实践一下

新建一个无痕(匿名)标签页并切换到 Memory 面板,打一个堆快照 Snapshot 1。

? 为什么是无痕标签页?

普通标签页会受到浏览器扩展或者其他脚本影响,内存占用不稳定。

使用无痕窗口的标签页可以保证页面的内存相对纯净且稳定,有利于我们进行对比。

另外,建议打开窗口一段之间之后再开始测试,这样内存会比较稳定(控制变量)。

切换到 Console 面板,执行以下代码来实例化一个 Foo 对象:

function Foo() {
  this.name = &#39;pp&#39;;
  this.age = 18;
}
let foo = new Foo();

回到 Memory 面板,再打一个堆快照 Snapshot 2,切换到 Comparison 视图,选择 Snapshot 1 作为 Base snapshot(基本快照),在 Class filter 中输入“Foo”:

可以看到内存中新增了一个 Foo 对象实例,分配了 52 字节内存空间,该实例的引用持有者为变量 foo

Was ist Speicher in Javascript?

再次切换到 Console 面板,执行以下代码来解除变量 foo 的引用:

// 解除对象的引用
foo = null;

再回到 Memory 面板,打一个堆快照 Snapshot 3,选择 Snapshot 2 作为 Base snapshot,在 Class filter 中输入“Foo”:

内存中的 Foo 对象实例已经被删除,释放了 52 字节的内存空间。

Was ist Speicher in Javascript?

Containment(包含视图)

包含视图就是程序对象结构的“鸟瞰图(Bird’s eye view)”,允许我们通过全局对象出发,一层一层往下探索,从而了解内存的详细情况。

Was ist Speicher in Javascript?

包含视图中有以下几种全局对象:

GC roots(GC 根)

GC roots 就是 JavaScript 虚拟机的垃圾回收中实际使用的根节点。

GC 根可以由 Built-in object maps(内置对象映射)、Symbol tables(符号表)、VM thread stacks(VM 线程堆栈)、Compilation caches(编译缓存)、Handle scopes(句柄作用域)和 Global handles(全局句柄)等组成。

DOMWindow objects(DOMWindow 对象)

DOMWindow objects 指的是由宿主环境(浏览器)提供的顶级对象,也就是 JavaScript 代码中的全局对象 window,每个标签页都有自己的 window 对象(即使是同一窗口)。

Native objects(原生对象)

Native objects 指的是那些基于 ECMAScript 标准实现的内置对象,包括 ObjectFunctionArrayStringBooleanNumberDateRegExpMath 等对象。

? 实践一下

切换到 Console 面板,执行以下代码来创建一个构造函数 $ABC

构造函数命名前面加个 $ 是因为这样排序的时候可以排在前面,方便找。

function $ABC() {
  this.name = 'pp';
}

切换到 Memory 面板,打一个堆快照,切换为 Containment 视图:

在当前标签页的全局对象下就可以找到我们刚刚创建的构造函数 $ABC

Was ist Speicher in Javascript?

Statistics(统计视图)

统计视图可以很直观地展示内存整体分配情况。

Was ist Speicher in Javascript?

在该视图里的空心饼图中共有 6 种颜色,各含义分别为:

  • 红色:Code(代码)
  • 绿色:Strings(字符串)
  • 蓝色:JS arrays(数组)
  • 橙色:Typed arrays(类型化数组)
  • 紫色:System objects(系统对象)
  • 白色:空闲内存

Allocation instrumentation on timeline(分配时间轴)

Was ist Speicher in Javascript?

在一段时间内持续地记录内存分配(约每 50 毫秒打一张堆快照),记录完成后可以选择查看任意时间段的内存分配详情。

另外还可以勾选同时记录分配堆栈(Allocation stacks),也就是记录调用堆栈,不过这会产生额外的性能消耗。

? 如何开始

点击页面底部的 Start 按钮或者左上角的 ⚫ 按钮即可开始记录,记录过程中点击左上角的 ? 按钮来结束记录,片刻之后就会自动展示结果。

? 操作一下

打开 Memory 面板,开始记录分配时间轴。

切换到 Console 面板,执行以下代码:

代码效果:每隔 1 秒钟创建 100 个对象,共创建 1000 个对象。

console.log(&#39;测试开始&#39;);
let objects = [];
let handler = setInterval(() => {
  // 每秒创建 100 个对象
  for (let i = 0; i < 100; i++) {
    const name = `n${objects.length}`;
    const value = `v${objects.length}`;
    objects.push({ [name]: value});
  }
  console.log(`对象数量:${objects.length}`);
  // 达到 1000 个后停止
  if (objects.length >= 1000) {
    clearInterval(handler);
    console.log(&#39;测试结束&#39;);
  }
}, 1000);

? 又是一个细节

不知道你有没有发现,在上面的代码中,我干了一件坏事。

在 for 循环创建对象时,会根据对象数组当前长度生成一个唯一的属性名和属性值。

这样一来 V8 就无法对这些对象进行优化,方便我们进行测试。

另外,如果直接使用对象数组的长度作为属性名会有惊喜~

静静等待 10 秒钟,控制台会打印出“测试结束”。

切换回 Memory 面板,停止记录,片刻之后会自动进入结果页面。

Was ist Speicher in Javascript?

分配时间轴结果页有 4 种视图:

  • Summary:摘要视图
  • Containment:包含视图
  • Allocation:分配视图
  • Statistics:统计视图

默认显示 Summary 视图。

Summary(摘要视图)

看起来和堆快照的摘要视图很相似,主要是页面上方多了一条横向的时间轴(Timeline)。

Was ist Speicher in Javascript?

? 时间轴

时间轴中主要的 3 种线:

  • 细横线:内存分配大小刻度线
  • 蓝色竖线:表示内存在对应时刻被分配,最后仍然活跃
  • 灰色竖线:表示内存在对应时刻被分配,但最后被回收

时间轴的几个操作:

  • 鼠标移动到时间轴内任意位置,点击左键或长按左键并拖动即可选择一段时间
  • 鼠标拖动时间段框上方的方块可以对已选择的时间段进行调整
  • 鼠标移到已选择的时间段框内部,滑动滚轮可以调整时间范围
  • 鼠标移到已选择的时间段框两旁,滑动滚轮即可调整时间段
  • 双击鼠标左键即可取消选择

Was ist Speicher in Javascript?

在时间轴中选择要查看的时间段,即可得到该段时间的内存分配详情。

Was ist Speicher in Javascript?

Containment(包含视图)

分配时间轴的包含视图与堆快照的包含视图是一样的,这里就不再重复介绍了。

Was ist Speicher in Javascript?

Allocation(分配视图)

对不起各位,这玩意儿我也不知道有啥用…

打开就直接报错,我:喵喵喵?

Was ist Speicher in Javascript?

是不是因为没人用这玩意儿,所以没人发现有问题…

Statistics(统计视图)

分配时间轴的统计视图与堆快照的统计视图也是一样的,不再赘述。

Was ist Speicher in Javascript?

Allocation sampling(分配采样)

Was ist Speicher in Javascript?

Memory 面板上的简介:使用采样方法记录内存分配。这种分析方式的性能开销最小,可以用于长时间的记录。

好家伙,这个简介有够模糊,说了跟没说似的,很有精神!

我在官方文档里没有找到任何关于分配采样的介绍,Google 上也几乎没有与之有关的信息。所以以下内容仅为个人实践得出的结果,如有不对的地方欢迎各位指出!

简单来说,通过分配采样我们可以很直观地看到代码中的每个函数(API)所分配的内存大小。

由于是采样的方式,所以结果并非百分百准确,即使每次执行相同的操作也可能会有不同的结果,但是足以让我们了解内存分配的大体情况。

✍ 如何开始

点击页面底部的 Start 按钮或者左上角的 ⚫ 按钮即可开始记录,记录过程中点击左上角的 ? 按钮来结束记录,片刻之后就会自动展示结果。

? 操作一下

打开 Memory 面板,开始记录分配采样。

切换到 Console 面板,执行以下代码:

代码看起来有点长,其实就是 4 个函数分别以不同的方式往数组里面添加对象。

// 普通单层调用
let array_a = [];
function aoo1() {
  for (let i = 0; i < 10000; i++) {
    array_a.push({ a: &#39;pp&#39; });
  }
}
aoo1();
// 两层嵌套调用
let array_b = [];
function boo1() {
  function boo2() {
    for (let i = 0; i < 20000; i++) {
      array_b.push({ b: &#39;pp&#39; });
    }
  }
  boo2();
}
boo1();
// 三层嵌套调用
let array_c = [];
function coo1() {
  function coo2() {
    function coo3() {
      for (let i = 0; i < 30000; i++) {
        array_c.push({ c: &#39;pp&#39; });
      }
    }
    coo3();
  }
  coo2();
}
coo1();
// 两层嵌套多个调用
let array_d = [];
function doo1() {
  function doo2_1() {
    for (let i = 0; i < 20000; i++) {
      array_d.push({ d: &#39;pp&#39; });
    }
  }
  doo2_1();
  function doo2_2() {
    for (let i = 0; i < 20000; i++) {
      array_d.push({ d: &#39;pp&#39; });
    }
  }
  doo2_2();
}
doo1();

切换回 Memory 面板,停止记录,片刻之后会自动进入结果页面。

Was ist Speicher in Javascript?

分配采样结果页有 3 种视图可选:

  • Chart:图表视图
  • Heavy (Bottom Up):扁平视图(调用层级自下而上)
  • Tree (Top Down):树状视图(调用层级自上而下)

这个 Heavy 我真的不知道该怎么翻译,所以我就按照具体表现来命名了。

默认会显示 Chart 视图。

Chart(图表视图)

Chart 视图以图形化的表格形式展现各个函数的内存分配详情,可以选择精确到内存分配的不同阶段(以内存分配的大小为轴)。

Was ist Speicher in Javascript?

鼠标左键点击、拖动和双击以操作内存分配阶段轴(和时间轴一样),选择要查看的阶段范围。

Was ist Speicher in Javascript?

将鼠标移动到函数方块上会显示函数的内存分配详情。

Was ist Speicher in Javascript?

鼠标左键点击函数方块可以Was ist Speicher in Javascript?。

Was ist Speicher in Javascript?

Heavy(扁平视图)

Heavy 视图将函数调用层级压平,函数将以独立的个体形式展现。另外也可以展开调用层级,不过是自下而上的结构,也就是一个反向的Was ist Speicher in Javascript?。

Was ist Speicher in Javascript?

视图中的两种 Size(大小):

  • Self Size:自身大小,指的是在函数内部直接分配的内存空间大小。
  • Total Size:总大小,指的是函数总共分配的内存空间大小,也就是包括函数内部嵌套调用的其他函数所分配的大小。
Tree(树状视图)

Tree 视图以树形结构展现函数调用层级。我们可以从代码执行的源头开始自上而下逐层展开,呈现一个完整的正向的Was ist Speicher in Javascript?。

Was ist Speicher in Javascript?

【相关推荐:javascript视频教程编程基础视频

Das obige ist der detaillierte Inhalt vonWas ist Speicher in Javascript?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn