メモリの原則に関する詳細な説明: JS では変数はヒープに保存されるのか、スタックに保存されるのか?

メモリの原則に関する詳細な説明: JS では変数はヒープに保存されるのか、スタックに保存されるのか?

JavaScriptではプリミティブ型はヒープに保存されるのでしょうか、それともスタックに保存されるのでしょうか?

---- 非基本型の基本型

この質問を見ると、この質問はあまりにも基本的なものであり、これ以上基本的なことはできないと誰もが感じると思います。 Baidu で検索すると、基本型はスタックに格納され、参照型はヒープに格納されると言っている人がたくさんいることがわかります。

本当にそんなに簡単なのでしょうか?

1. 冷蔵庫に入りきらない象

次のコードを見てみましょう:

ここでは 67MiB の文字列を宣言していますが、文字列が実際にスタック上に存在していた場合、これを説明するのは困難です。結局のところ、v8 のデフォルトのスタック サイズは 984KiB です。絶対に救えない。

注: V8 では、オペレーティング システムによって、また時期によって、文字列サイズの制限が異なります。おおよその範囲は256MiB~1GiBです

node --v8-options | grep -B0 -A1 スタックサイズ


この時点で、疑問に思い始めていますか? Baidu からの回答が間違っていて、Google で検索する必要がある可能性はありますか?

何が起こっているのか見てみましょう。

2. シャドウクローン文字列

const BasicVarGen = 関数(){
    this.s1 = 'IAmString'
    this.s2 = 'IAmString'
}


a = 新しい BasicVarGen() とする
b = 新しい BasicVarGen() とする

ここでは、それぞれ 2 つの同一の文字列を含む 2 つの同一のオブジェクトを宣言します。

開発者ツールを使用すると、4 つの文字列を宣言したにもかかわらず、それらのメモリが同じアドレスを指していることがわかります。

注: chrome実際のアドレスを表示できません。ここでは抽象的なアドレスを示します。

これはどういう意味ですか? 4 つの文字列に参照アドレスが含まれていることがわかります。

なので、上記の記事の冷蔵庫に入りきらない象は簡単に説明できます。文字列はスタックに格納されるのではなく、別の場所に格納され、その場所のアドレスがスタックに格納されます。

そこで、文字列の 1 つの内容を変更してみましょう。

const BasicVarGen = 関数(){
    this.s0 = 'IAmString'
    this.s1 = 'IAmString'
}


a = 新しい BasicVarGen() とする
b = 新しい BasicVarGen() とする
デバッガ
a.s0 = '異なる文字列'
a.s2 = 'IAmString'

debugger前のメモリスナップショット

debugger後のメモリスナップショット

a.s0 の初期コンテンツは ' IAmString'であり、そのコンテンツを変更するとアドレスが変わることがわかります。

新しく追加された a.s2 の内容は ' IAmString'であり、そのアドレスは値が ' IAmString'である他の変数と一致しています。

文字列を宣言する場合:

  1. v8 には、すべての文字列をキャッシュするstringTableというhashmapがあります。V8 がコードを読み取って抽象構文木を変換する際、文字列に遭遇するたびに、その特性に基づいてhash値に変換し、 hashmapに挿入します。後で同じハッシュ値を持つ文字列に遭遇した場合、最初に比較のために取り出されます。一貫性がある場合は、新しい文字列クラスは生成されません。
  2. 文字列をキャッシュする場合、文字列に応じて異なるhash方法が使用されます。

では、整理してみましょう。文字列を作成すると、V8 はまずメモリ (ハッシュ テーブル) を検索して、すでに作成されている同一の文字列があるかどうかを確認します。存在する場合は、直接再利用されます。存在しない場合は、文字列を格納するための新しいメモリ領域が開かれ、そのアドレスが変数に割り当てられます。これが、添え字を使用して文字列を直接変更できない理由です。V8 の文字列は不変です。

jsの基本型コピーを例に、v8の実装ロジックと誰もが理解している従来のロジックを説明します(Yawen)

例:

var a = "刘潇洒"; // V8 は文字列を読み込んだ後、stringTable にアクセスして文字列が存在するかどうかを確認します。存在しない場合は、'刘潇洒' を hashTable に挿入し、'刘潇洒' の参照を
var b = a; // '刘潇洒' の参照を直接コピーします b = "谭雅文"; // stringTable にエントリがないか検索します


質問:

const BasicVarGen = 関数(){
    this.s0 = 'IAmString'
    this.s1 = 'IAmString'
}


a = 新しい BasicVarGen() とする
b = 新しい BasicVarGen() とする
デバッガ
a.s0 = '異なる文字列'
a.s2 = 'IAmString'


a.s3 = a.s2+a.s0; // 質問: 文字列の連結ではどのような操作が実行されますか?
a.s4 = a.s2+as

同じ内容の連結された文字列を 2 つ同時に適用します。

ご覧の通り内容は同じです。しかし、住所は同じではありません。さらに、住所の前の地図の説明も変更されました。

文字列が従来の方法 ( SeqStringなど) で連結される場合、連結操作の時間計算量は O(n) です。 Rope Structure (つまり、 ConsStringで使用されるデータ構造) を使用すると、連結にかかる時間を短縮できます。

これが文字列に当てはまる場合、他のプリミティブ型にも当てはまるでしょうか?

3. 実際に見た「奇妙なボール」

文字列について説明した後、V8 のもう 1 つの典型的な「基本型」であるoddBallについて見てみましょう。

oddBall typeから拡張

もう一つの小さな実験をしてみましょう:

上図に基本的なタイプがリストされており、アドレスは同じであることがわかります。値を割り当てると、その値もその場で再利用されます。 (そして、 oddBallから拡張されたこれらの基本型のアドレスは固定されています。つまり、V8 が初めて実行されるとき、これらの基本型を宣言したかどうかに関係なく、それらは作成されています。オブジェクトを宣言すると、その参照が割り当てられます。これは、基本型がスタックに割り当てられると言われる理由も説明しています。V8 では、@73 に格納される値は常に空の文字列であるため、v8 はこれらのアドレスを値自体として扱うことができます。)

確認するためにソースコードを見てみましょう。

さまざまなoddBallタイプのメソッドを生成すると、戻り値がアドレスであることがわかります

undefined変数に割り当てられると、実際にはアドレスが割り当てられます

getRootメソッド

オフセットが定義される場所

4. 紛らわしい数字

紛らわしい番号と言われる理由は、割り当て時と変更時のメモリ割り当ての仕組みがまだ解明されていないためです。 (メモリは動的です)

V8 では、数値はsmiheapNumberに分割されます。

smi -2³¹から2³¹-1(2³¹≈2*10⁹)の範囲の整数を直接保存します。

heapNumberは文字列に似ており、不変です。その範囲は、すべての非SMI数値です。

最下位ビットは、それがポインターであるかどうかを示すために使用されます。最下位ビットが 1 の場合、それはポインターです。

定数o = {
  x: 42, // スミ
  y: 4.2, // ヒープ番号
};


ox の 42 は Smi として扱われ、オブジェクト自体に直接格納されますが、oy の 4.2 は追加のメモリ エンティティに格納する必要があり、oy のオブジェクト ポインターはメモリ エンティティを指します。

32 ビット オペレーティング システムの場合は、32 ビットを使用して smi を表すのは理解できますが、64 ビット オペレーティング システムでは、smi の範囲も -2³¹ から 2³¹-1 (2³¹≈2*10⁹) になるのはなぜでしょうか。

ECMAScript標準では、 numberは 64 ビットの倍精度浮動小数点数として扱う必要があると規定されていますが、実際には、任意の数値を格納するために常に 64 ビットを使用するのは非常に非効率的です (スペースの非効率性、計算時間の非効率性smiが多くのビット操作を使用する)。そのため、 JavaScriptエンジンは、数値を格納するために常に 64 ビットを使用するわけではありません。監視できる数値のすべての外部機能が 64 ビット表現と一致している限り、エンジンは内部的に他のメモリ表現 (32 ビットなど) を使用できます。

定数サイクル制限 = 50000
console.time('ヒープ番号')
定数 foo = { x: 1.1 };
(i = 0; i < cycleLimit; ++i とします) {
// 追加の heapNumber インスタンスを作成します foo.x += 1; 
}
console.timeEnd('heapNumber') // 遅い   


コンソール時間('smi')
定数バー = { x: 1.0 };
(i = 0; i < cycleLimit; ++i とします) {
  バー.x += 1;
}
console.timeEnd('smi') // 高速

質問:

const BasicVarGen = 関数(){


    smi1 = 1 です
    .smi2 = 2 です
    this.heapNumber1 = 1.1
    this.heapNumber2 = 2.1
}
    foo = 新しい BasicVarGen() を作成します。
    bar = 新しい BasicVarGen() を作成します。
    
    デバッガ
    
    baz.obj1.heapNumber1++

数字では、単一の数字の値は変更されませんが、他の数字のアドレスが変更されます。

5. まとめ: 基本型はどこに存在するのか?

文字列:ヒープ内に存在し、スタック内の参照アドレスです。同じ文字列が存在する場合、参照アドレスは同じです。

数値:小さな整数はスタックに格納され、その他の型はヒープに格納されます。

その他のタイプ:エンジンが初期化されるときに一意のアドレスが割り当てられ、スタック内の変数には一意の参照が格納されます。

ここでは、基本的なタイプがどこに存在するかを大まかに説明するだけです。

これで、メモリの原理の詳細と、JS の変数がヒープまたはスタックに格納されるかどうかに関するこの記事は終了です。JS の変数がヒープまたはスタックに格納されるかどうかの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JS変数ストレージのディープコピーとシャローコピーの詳しい説明
  • JS 変数の昇格と関数の昇格の例の分析
  • js 変数、スコープ、メモリの詳細な説明
  • js変数とそのスコープの詳細な説明
  • JavaScript 変数の昇格についての簡単な説明
  • JavaScript 変数 Dom オブジェクトのすべてのプロパティ
  • JavaScript変数の基本的な使い方
  • JavaScript 変数宣言例の分析
  • Javascript 変数スコープとスコープチェーンの詳細な説明

<<:  Linuxコマンドをバックグラウンドで実行する方法

>>:  Mysql の読み取り/書き込み分離期限切れに対する一般的な解決策

推薦する

SQL で行の最大値または最小値を取得する方法

元データと対象データSQL文を実装する(最大) 選択 店、 月、 最大(dz,fz,sp) が最大値...

Vue ターンテーブル抽選の簡単な実装

この記事では、ホイール抽選を簡単に実装するためのVueの具体的なコードを参考までに共有します。具体的...

URLに基​​づいてリクエストを転送するnginxの実装の実践経験

序文これは fastdfs を使用してイントラネット外部に展開された分散ファイルシステムであるためで...

CSS+SVGでBステーションの課金効果を実現するサンプルコード

困難SVG グラフィックの 2 つのマスクの作成まず、コード左側のピンク色のボックスの内容ですこれに...

HTML で dl(dt,dd)、ul(li)、ol(li) を使用する方法

HTML <dl> タグ#定義と使用法<dl> タグは定義リストを定義します...

Vue はカスタム「モーダル ポップアップ ウィンドウ」コンポーネントのサンプル コードを実装します

目次序文レンダリングサンプルコード要約する序文ダイアログ ボックスは非常に一般的なコンポーネントであ...

JavaScriptはフォームデータの非同期取得を実装します

この記事では、フォームデータの非同期取得を実現するためのJavaScriptの具体的なコードを例とし...

jQueryはシンプルなコメントエリアを実装します

この記事では、参考までに、簡単なコメントエリアを実装するためのjQueryの具体的なコードを紹介しま...

Javascript のスコープとクロージャの詳細

目次1. 範囲2. スコープチェーン3. 語彙の範囲5. 閉鎖の適用6. クロージャの欠陥7. 閉会...

htm 初心者ノート(初心者は必ず読んでください)

1. HTMLとは何かHTML (ハイパーテキスト マークアップ言語): ハイパーテキスト マーク...

Docker 入門インストールチュートリアル (初心者版)

ドクター紹介: Docker はコンテナ関連の技術です。簡単に言うと、さまざまなソフトウェアを実行で...

SQL と NoSQL の違いのまとめ

主な違い: 1. タイプSQL データベースは主にリレーショナル データベース (RDBMS) とし...

aタグ内のテキストを非表示にして画像を表示するには?360モードレンダリングに対応

多くの場合、画像を表示する<a>タグのスタイルに遭遇しますが、タグ内にテキストがあり、そ...

CSS で順序付きリスト項目と順序なしリスト項目のスタイルを設定する方法

順序なしリストでは、順序なしリストのシンボルは各リストの前に表示されるドットです。順序付きリスト o...

MySQL マスタースレーブレプリケーションの読み書き分離構造の詳細な説明

MySQL マスタースレーブ設定MySQL のマスター/スレーブ レプリケーションと読み取り/書き込...