データ構造の設定とマップ
セット
基本的な使い方
ES6 は新しいデータ構造 Set を提供します。配列に似ていますが、メンバーの値は一意であり、重複する値はありません。
Set
自体は、Set データ構造を生成するために使用されるコンストラクターです。
const s = 新しい Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
コンソール.ログ(i);
}
// 2 3 5 4
上記のコードは、add()
メソッドを通じて Set 構造体にメンバーを追加します。結果は、Set 構造体が重複する値を追加しないことを示しています。
Set()
関数は、初期化のパラメータとして配列 (または反復可能なインターフェイスを備えた他のデータ構造) を受け入れることができます。
//例1
const セット = 新しい Set([1, 2, 3, 4, 4]);
[...セット]
// [1、2、3、4]
//例2
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
//例 3
const set = new Set(document.querySelectorAll('div'));
set.size // 56
// に似ています
const セット = 新しい Set();
書類
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 56
上記のコードでは、例 1 と 2 は両方とも配列をパラメータとして受け入れる Set
関数であり、例 3 は配列のようなオブジェクトをパラメータとして受け入れます。
上記のコードは、配列から重複するメンバーを削除する方法も示しています。
//配列から重複したメンバーを削除します
[...新しいセット(配列)]
上記の方法は、文字列内の繰り返し文字を削除するためにも使用できます。
[...new Set('ababbc')].join('')
// "ABC"
Set に値を追加する場合、型変換は行われないため、5
と "5"
は 2 つの異なる値になります。 Set は 2 つの値が異なるかどうかを内部的に決定します。使用されるアルゴリズムは「同じ値のゼロの等価性」と呼ばれるもので、主な違いは NaN
であることです。 Set に値を追加するときに NaN
はそれ自体と等しいとみなされますが、完全等価演算子は NaN
をそれ自体と等しくないとみなします。
let set = new Set();
a = NaN とします。
b = NaN とします。
set.add(a);
set.add(b);
set // {NaN}を設定します
上記のコードは Set インスタンスに 2 つの NaN
を追加しますが、追加されるのは 1 つだけです。これは、内部的には 2 つの NaN が等しいことを示しています。
さらに、2 つのオブジェクトは常に等しくありません。
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
上記のコードは、2 つの空のオブジェクトは等しくないため、2 つの値として扱われることを示しています。
Array.from()
メソッドは Set 構造体を配列に変換できます。
const items = new Set([1, 2, 3, 4, 5]);
const 配列 = Array.from(項目);
これは、配列から重複メンバーを削除する別の方法を提供します。
関数重複排除(配列) {
return Array.from(new Set(array));
}
重複排除([1, 1, 2, 3]) // [1, 2, 3]
インスタンスのプロパティとメソッドを設定します。
Set 構造のインスタンスには次のプロパティがあります。
Set.prototype.constructor
: コンストラクター関数。デフォルトはSet
関数です。Set.prototype.size
:Set
インスタンスのメンバーの総数を返します。
Set インスタンスのメソッドは、操作メソッド (データの操作に使用) とトラバーサル メソッド (メンバーのトラバースに使用) の 2 つのカテゴリに分類されます。まずは以下の4つの操作方法を紹介します。
Set.prototype.add(value)
: 値を追加し、Set 構造体自体を返します。Set.prototype.delete(value)
: 特定の値を削除し、削除が成功したかどうかを示すブール値を返します。Set.prototype.has(value)
: 値がSet
のメンバーであるかどうかを示すブール値を返します。Set.prototype.clear()
: すべてのメンバーをクリアします。戻り値はありません。
上記のプロパティとメソッドの例は次のとおりです。
s.add(1).add(2).add(2);
// 2 が 2 回追加されることに注意してください
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2) // true
s.has(2) // false
以下に、キーの有無と、Object構造体とSet構造体の書き方の違いを比較します。
// オブジェクトの書き方
const プロパティ = {
「幅」: 1、
「高さ」: 1
};
if (プロパティ[someName]) {
// 何かをする
}
// Setの書き方
const プロパティ = 新しい Set();
プロパティ.add('幅');
プロパティ.add('高さ');
if (properties.has(someName)) {
// 何かをする
}
トラバーサル操作
Set 構造のインスタンスには、メンバーを反復処理するために使用できる 4 つの走査メソッドがあります。
Set.prototype.keys()
: キー名のトラバーサーを返します。Set.prototype.values()
: キー値のトラバーサーを返します。Set.prototype.entries()
: キーと値のペアのトラバーサーを返します。Set.prototype.forEach()
: コールバック関数を使用して各メンバーを走査します
特に「Set」の走査順序が挿入順序であることに注意してください。この機能は、Set を使用してコールバック関数のリストを保存し、追加された順序で呼び出すことができるなど、非常に役立つ場合があります。
(1)keys()
、values()
、entries()
keys
メソッド、values
メソッド、および entries
メソッドはすべてイテレータ オブジェクトを返します (詳細については、「イテレータ オブジェクト」の章を参照してください)。 Set 構造体にはキー名がなく、キー値のみ (またはキー名とキー値が同じ値) があるため、keys
メソッドは values
メソッドとまったく同じように動作します。
let set = new Set(['red', 'green', 'blue']);
for (set.keys() の項目を許可) {
コンソール.ログ(アイテム);
}
// 赤
// 緑
// 青
for (set.values() の項目を許可) {
コンソール.ログ(項目);
}
// 赤
// 緑
// 青
for (set.entries() の let 項目) {
コンソール.ログ(項目);
}
// ["赤", "赤"]
// ["緑", "緑"]
// ["青", "青"]
上記のコードでは、「entries」メソッドによって返されるトラバーサーにはキー名とキー値の両方が含まれるため、配列が出力されるたびに、その 2 つのメンバーは完全に等しくなります。
Set 構造体のインスタンスはデフォルトでトラバース可能であり、そのデフォルトのトラバーサ生成関数はその values
メソッドです。
Set.prototype[Symbol.iterator] === Set.prototype.values
// 真実
これは、values
メソッドを省略し、for...of
を直接使用して Set をループできることを意味します。
let set = new Set(['red', 'green', 'blue']);
for (セットの x を考えます) {
コンソール.ログ(x);
}
// 赤
// 緑
// 青
(2)forEach()
Set 構造体のインスタンスには、配列と同様に、値を返さずに各メンバーに対して何らかの操作を実行するために使用される forEach
メソッドもあります。
let set = new Set([1, 4, 9]);
set.forEach((値, キー) => console.log(キー + ' : ' + 値))
// 1:1
// 4 : 4
// 9 : 9
上記のコードは、「forEach」メソッドのパラメータが処理関数であることを示しています。この関数のパラメータは、キー値、キー名、コレクション自体である配列の forEach
と一致しています (このパラメータは上の例では省略されています)。ここで注意すべき点は、Set構造体のキー名がキー値(両方とも同じ値)となるため、第1パラメータと第2パラメータの値は常に同じになります。
さらに、forEach
メソッドには、バインディング ハンドラー関数内の this
オブジェクトを表す 2 番目のパラメーターを持つこともできます。
(3) トラバーサルの適用
スプレッド演算子 (...
) は内部で for...of
ループを使用するため、Set 構造体でも使用できます。
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['赤', '緑', '青']
スプレッド演算子を Set 構造体と組み合わせると、配列の重複するメンバーを削除できます。
arr = [3, 5, 2, 2, 5, 5] とします。
let unique = [...new Set(arr)];
// [3、5、2]
さらに、配列の map
および filter
メソッドも Set で間接的に使用できます。
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
//セット構造体を返す: {2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// セットの構造体を返す: {2, 4}
したがって、Union、Intersect、Difference は Set を使用して簡単に実装できます。
let a = new Set([1, 2, 3]);
let b = 新しい Set([4, 3, 2]);
// ユニオン
let Union = new Set([...a, ...b]);
// {1, 2, 3, 4} を設定します
// 交差点
let intersect = new Set([...a].filter(x => b.has(x)));
// {2, 3} を設定します
// (b に対する a) の差集合
letDifference = new Set([...a].filter(x => !b.has(x)));
// {1} を設定します
トラバーサル操作中に元の Set 構造を同期的に変更する場合、現時点では直接的な方法はありませんが、2 つの回避策があります。 1 つは、元の Set 構造を使用して新しい構造をマップし、それを元の Set 構造に割り当てる方法です。もう 1 つは、Array.from
メソッドを使用することです。
//方法1
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
//セットの値は2、4、6です
//方法2
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
//セットの値は2、4、6です
上記のコードは、トラバーサル操作中に元の Set 構造を直接変更する 2 つのメソッドを提供します。
集合演算
ES2025 以下のセット操作メソッドを Set 構造体に追加しました。
- Set.prototype.intersection(other):交差点
- Set.prototype.union(other): ユニオン
- Set.prototype.difference(other): 差分セット
- Set.prototype.metricDifference(other): 対称差分セット
- Set.prototype.isSubsetOf(other): サブセットかどうかを判断します。
- Set.prototype.isSupersetOf(other): スーパーセットかどうかを判断します。
- Set.prototype.isDisjointFrom(other): 素かどうかを判断します。
上記のメソッドのパラメータは、Set 構造体、または Set に類似した構造体 (size
属性、keys()
および has()
メソッドを含む) でなければなりません。
.union()
は、両方のセットに存在するすべてのメンバーを含むセットを返す結合演算です。
constfrontEnd = new Set(["JavaScript", "HTML", "CSS"]);
const backEnd = new Set(["Python", "Java", "JavaScript"]);
const all = フロントエンド.ユニオン(バックエンド);
// {"JavaScript", "HTML", "CSS", "Python", "Java"} を設定します
.intersection()
は交差演算であり、両方のセットに含まれるメンバーのセットを返します。
constfrontEnd = new Set(["JavaScript", "HTML", "CSS"]);
const backEnd = new Set(["Python", "Java", "JavaScript"]);
constfrontAndBackEnd =frontEnd.intersection(backEnd);
// {"JavaScript"} を設定します
.difference()
は、最初のセットには存在するが 2 番目のセットには存在しないすべてのメンバーのセットを返す差分演算です。
constfrontEnd = new Set(["JavaScript", "HTML", "CSS"]);
const backEnd = new Set(["Python", "Java", "JavaScript"]);
constonlyFrontEnd =frontEnd.difference(backEnd);
// {"HTML", "CSS"} を設定します
constonlyBackEnd = backEnd.difference(frontEnd);
// {"Python", "Java"} を設定します
.symmetryDifference()
は対称差分セットで、2 つのセットの一意のメンバーすべてのセットを返します。つまり、重複したメンバーは削除されます。
constfrontEnd = new Set(["JavaScript", "HTML", "CSS"]);
const backEnd = new Set(["Python", "Java", "JavaScript"]);
constonlyFrontEnd =frontEnd.metricDifference(backEnd);
// {"HTML"、"CSS"、"Python"、"Java"} を設定します
constonlyBackEnd = backEnd.metricDifference(frontEnd);
// {"Python", "Java", "HTML", "CSS"} を設定します
返される結果内のメンバーの順序は、コレクションに追加された順序によって決定されることに注意してください。
.isSubsetOf()
は、最初のセットが 2 番目のセットのサブセットであるかどうか、つまり、最初のセットのすべてのメンバーが 2 番目のセットのメンバーであるかどうかを判断するブール値を返します。
constfrontEnd = new Set(["JavaScript", "HTML", "CSS"]);
const declarative = new Set(["HTML", "CSS"]);
declarative.isSubsetOf(フロントエンド);
// 真実
frontEndLanguages.isSubsetOf(declarativeLanguages);
// 間違い
任意のセットはそれ自体のサブセットです。
フロントエンド.isSubsetOf(フロントエンド);
// 真実
isSupersetOf()
は、最初のセットが 2 番目のセットのスーパーセットであるかどうか、つまり 2 番目のセットのすべてのメンバーが最初のセットのメンバーであるかどうかを示すブール値を返します。
constfrontEnd = new Set(["JavaScript", "HTML", "CSS"]);
const declarative = new Set(["HTML", "CSS"]);
declarative.isSupersetOf(フロントエンド);
// 間違い
フロントエンド.isSupersetOf(宣言的);
// 真実
任意のセットはそれ自体のスーパーセットです。
フロントエンド.isSupersetOf(フロントエンド);
// 真実
.isDisjointFrom()
は、2 つのセットが素であるかどうか、つまり共通のメンバーがないかどうかを判断します。
constfrontEnd = new Set(["JavaScript", "HTML", "CSS"]);
const解釈済み = new Set(["JavaScript", "Ruby", "Python"]);
constコンパイル = new Set(["Java", "C++", "TypeScript"]);
解釈された.isDisjointFrom(コンパイル済み);
// 真実
フロントエンド.isDisjointFrom(解釈された);
// 間違い
ウィークセット
意味
WeakSet 構造は Set に似ており、一意の値のコレクションでもあります。ただし、Set とは 2 つの点で異なります。
まず第一に、WeakSet のメンバーにはオブジェクトとシンボル値のみを使用でき、他のタイプの値は使用できません。
const ws = new WeakSet();
ws.add(1) // エラーを報告する
ws.add(Symbol()) // エラーは報告されませんでした
上記のコードは数値と「シンボル」値を WeakSet に追加しようとしますが、WeakSet はオブジェクトとシンボル値しか配置できないため、前者はエラーを報告します。
第 2 に、WeakSet 内のオブジェクトはすべて弱参照です。つまり、ガベージ コレクション メカニズムは WeakSet のオブジェクトへの参照を考慮しません。つまり、他のオブジェクトがそのオブジェクトを参照しなくなった場合、ガベージ コレクション メカニズムは自動的に再利用します。オブジェクトが WeakSet にまだ存在するかどうかに関係なく、オブジェクトのメモリが占有しているスペース。
これは、ガベージ コレクション メカニズムがオブジェクトの到達可能性に基づいてリサイクルを決定するため、オブジェクトにまだアクセスできる場合、ガベージ コレクション メカニズムはこのメモリを解放しません。値の使用が終了した後、その値を逆参照するのを忘れてメモリが解放されず、メモリ リークが発生することがあります。 WeakSet 内の参照はガベージ コレクション メカニズムではカウントされないため、この問題は存在しません。したがって、WeakSet は、オブジェクトのグループを一時的に保存したり、オブジェクトにバインドされた情報を保存したりするのに適しています。これらのオブジェクトが外部で消える限り、WeakSet 内のそれらの参照は自動的に消えます。
上記の特性により、WeakSet のメンバはいつ消滅してしまうため、参照には適していません。また、WeakSet 内のメンバー数はガベージ コレクション機構が動作しているかどうかに依存するため、ガベージ コレクション機構が動作する前後でメンバー数が異なる可能性があり、ガベージ コレクション機構がいつ動作するかは予測できないため、ES6 では WeakSet を規定しています。横断することはできません。
これらの特性は、この章で後ほど紹介する WeakMap 構造にも当てはまります。
文法
WeakSet はコンストラクターであり、new
コマンドを使用して WeakSet データ構造を作成できます。
const ws = new WeakSet();
WeakSet はコンストラクターとして、配列または配列のようなオブジェクトをパラメーターとして受け入れることができます。 (実際には、Iterable インターフェイスを持つ任意のオブジェクトを WeakSet のパラメーターとして使用できます。) 配列のすべてのメンバーは自動的に WeakSet インスタンス オブジェクトのメンバーになります。
const a = [[1, 2], [3, 4]];
const ws = 新しい WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
上記のコードでは、「a」は配列であり、2 つのメンバーがあり、どちらも配列です。 WeakSet コンストラクターのパラメーターとして「a」を使用すると、「a」のメンバーが自動的に WeakSet のメンバーになります。
WeakSet のメンバーになるのは、「a」配列自体ではなく、「a」配列のメンバーであることに注意してください。これは、配列のメンバーはオブジェクトのみであることができることを意味します。
const b = [3, 4];
const ws = 新しい WeakSet(b);
// Uncaught TypeError: 弱いセットで使用された無効な値(…)
上記のコードでは、配列 b
のメンバーはオブジェクトではないため、WeakSet を追加するとエラーが報告されます。
WeakSet構造には以下の3つのメソッドがあります。
- WeakSet.prototype.add(value): 新しいメンバーを WeakSet インスタンスに追加し、WeakSet 構造自体を返します。
- WeakSet.prototype.delete(value): WeakSet インスタンスの指定されたメンバーをクリアします。クリアが成功した場合、メンバーが WeakSet 内に見つからない場合、またはメンバーが 'true' を返します。オブジェクトの場合、「false」を返します。
- WeakSet.prototype.has(value): 値が WeakSet インスタンス内にあるかどうかを示すブール値を返します。
以下に例を示します。
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(ウィンドウ);
ws.add(obj);
ws.has(ウィンドウ); // true
ws.has(foo); // false
ws.delete(ウィンドウ); // true
ws.has(ウィンドウ); // false
WeakSet には「size」プロパティがなく、そのメンバーを反復処理する方法がありません。
ws.size // 未定義
ws.forEach // 未定義
ws.forEach(function(item){ console.log('WeakSet には ' + item)})
// TypeError: 未定義は関数ではありません
上記のコードは、size
プロパティと forEach
プロパティを取得しようとしますが、失敗します。
WeakSet は、メンバーが弱参照であり、いつでも消滅する可能性があるため、走査できません。走査メカニズムでは、走査の完了直後にメンバーが使用できない可能性が非常に高くなります。 WeakSet の用途の 1 つは、DOM ノードがドキュメントから削除されたときにメモリ リークを心配せずにそれらのノードを保存することです。
WeakSet の別の例を次に示します。
const foos = 新しい WeakSet()
クラス Foo {
コンストラクター() {
foos.add(this)
}
方法 () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method は Foo のインスタンスでのみ呼び出すことができます!');
}
}
}
上記のコードは、Foo
のインスタンス メソッドが Foo
のインスタンス上でのみ呼び出せることを保証します。ここで WeakSet を使用する利点は、インスタンスへの foos
の参照がメモリ リサイクル メカニズムでカウントされないため、インスタンスを削除するときに foos
を考慮する必要がなく、メモリ リークが発生しないことです。 。
地図
意味と基本的な使い方
JavaScript オブジェクト (オブジェクト) は本質的にキーと値のペア (ハッシュ構造) のコレクションですが、従来は文字列のみがキーとして使用できました。これは、その使用に大きな制限をもたらします。
定数データ = {};
const 要素 = document.getElementById('myDiv');
データ[要素] = 'メタデータ';
data['[object HTMLDivElement]'] // "メタデータ"
上記のコードの本来の目的は、DOM ノードをオブジェクト data
のキーとして使用することですが、オブジェクトはキー名として文字列のみを受け入れるため、element
は文字列 [object HTMLDivElement]
に自動的に変換されます。
この問題を解決するために、ES6 は Map データ構造を提供します。オブジェクトと似ており、キーと値のペアのコレクションでもありますが、「キー」の範囲は文字列に限定されず、さまざまな種類の値(オブジェクトを含む)をキーとして使用できます。言い換えれば、Object 構造は「文字列と値」の対応を提供し、Map 構造は「値と値」の対応を提供します。これは、ハッシュ構造のより完全な実装です。 「キーと値」のデータ構造が必要な場合は、オブジェクトよりもマップの方が適しています。
const m = 新しい Map();
const o = {p: 'Hello World'};
m.set(o, 'コンテンツ')
m.get(o) // "コンテンツ"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
上記のコードは、Map 構造体の set
メソッドを使用してオブジェクト o
を m
のキーとして扱い、次に get
メソッドを使用してキーを読み取り、次に delete
メソッドを使用して削除します。鍵。
上の例は、マップにメンバーを追加する方法を示しています。 Map はコンストラクターとして配列をパラメーターとして受け入れることもできます。この配列のメンバーは、キーと値のペアを表す配列です。
const マップ = 新しいマップ([
['名前', '張三'],
['タイトル', '著者']
]);
マップ.サイズ // 2
map.has('name') // true
map.get('name') // "張三"
map.has('title') // true
map.get('title') // "著者"
上記のコードは、新しい Map インスタンスを作成するときに 2 つのキー name
と title
を指定します。
Map
コンストラクターは配列をパラメーターとして受け取り、実際に次のアルゴリズムを実行します。
定数項目 = [
['名前', '張三'],
['タイトル', '著者']
];
const マップ = 新しい Map();
items.forEach(
([キー, 値]) => map.set(キー, 値)
);
実際、配列だけでなく、Iterator インターフェイスを備え、各メンバーが 2 つの要素の配列である任意のデータ構造 (詳細については「Iterator」の章を参照) を Map
コンストラクターのパラメーターとして使用できます。これは、「Set」と「Map」の両方を使用して新しいマップを生成できることを意味します。
const セット = 新しい Set([
['foo', 1],
['バー', 2]
]);
const m1 = 新しいマップ(セット);
m1.get('foo') // 1
const m2 = 新しい Map([['baz', 3]]);
const m3 = 新しいマップ(m2);
m3.get('baz') // 3
上記のコードでは、Set オブジェクトと Map オブジェクトをそれぞれ「Map」コンストラクターのパラメーターとして使用し、その結果、新しい Map オブジェクトが生成されます。
同じキーに複数の割り当てが行われた場合、後続の値が前の値を上書きします。
const マップ = 新しい Map();
地図
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"
上記のコードは、2 つの連続する値をキー「1」に割り当て、後の値で前の値を上書きします。
不明なキーを読み取る場合は、「unknown」を返します。
new Map().get('asfddfsasadf')
// 未定義
Map 構造では、同じオブジェクトへの参照が同じキーとして扱われることに注意してください。これには十分注意してください。
const マップ = 新しい Map();
マップ.set(['a'], 555);
map.get(['a']) // 未定義
上記のコードの set
メソッドと get
メソッドは同じキーを対象としているように見えますが、実際には異なるメモリ アドレスを持つ 2 つの異なる配列インスタンスであるため、get
メソッドはキーを読み取ることができず、unknown
を返します。 `。
同様に、同じ値の 2 つのインスタンスは、Map 構造内の 2 つのキーとみなされます。
const マップ = 新しい Map();
const k1 = ['a'];
const k2 = ['a'];
地図
.set(k1, 111)
.set(k2, 222);
マップ.get(k1) // 111
map.get(k2) // 222
上記のコードでは、変数k1
とk2
の値は同じですが、Map構造体では2つのキーとして扱われます。
上記から、Map のキーは実際にはメモリ アドレスにバインドされていることがわかります。メモリ アドレスが異なる限り、それらは 2 つのキーとみなされます。これにより、他の人のライブラリを拡張するときに、オブジェクトをキー名として使用する場合、自分のプロパティが元の作成者のプロパティと同じ名前を持つことを心配する必要がなくなります。
マップのキーが単純なタイプの値 (数値、文字列、ブール値) である場合、2 つの値が厳密に等しい限り、マップはそれをキーとして扱います。たとえば、「0」と「0」です。 -0
はキー、ブール値です。値 true
と文字列 true
は 2 つの異なるキーです。さらに、「unknown」と「null」も 2 つの異なるキーです。 NaN
はそれ自体と厳密には等しくありませんが、Map はそれらを同じキーとして扱います。
let Map = new Map();
マップ.セット(-0, 123);
マップ.get(+0) // 123
マップ.set(true, 1);
マップ.set('true', 2);
map.get(true) // 1
マップ.set(未定義, 3);
マップ.set(null, 4);
map.get(unknown) // 3
マップ.set(NaN, 123);
マップ.get(NaN) // 123
インスタンスの属性と操作方法
Map 構造体のインスタンスには以下のプロパティと操作メソッドがあります。
(1) サイズ属性
size
属性は、Map 構造体のメンバーの総数を返します。
const マップ = 新しい Map();
map.set('foo', true);
map.set('バー', false);
マップ.サイズ // 2
(2)Map.prototype.set(キー, 値)
set
メソッドは、キー名 key
に対応するキー値を value
に設定し、Map 構造全体を返します。 「key」にすでに値がある場合はキーの値が更新され、そうでない場合はキーが新たに生成されます。
const m = 新しい Map();
m.set('edition', 6) // キーは文字列です
m.set(262, 'standard') // キーは数値です
m.set(unknown, 'nah') // キーは未定義です
set
メソッドは現在の Map
オブジェクトを返すため、チェーン書き込みを使用できます。
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
(3)Map.prototype.get(key)
get
メソッドは、key
に対応するキー値を読み取ります。 key
が見つからない場合は、unknown
が返されます。
const m = 新しい Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // キーは関数です
m.get(hello) // こんにちは ES6!
(4)Map.prototype.has(key)
「has」メソッドは、キーが現在の Map オブジェクト内にあるかどうかを示すブール値を返します。
const m = 新しい Map();
m.set('エディション', 6);
m.set(262, '標準');
m.set(未定義, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(未定義) // true
(5)Map.prototype.delete(キー)
delete()
メソッドはキーを削除し、true
を返します。削除に失敗した場合は「false」を返します。
const m = 新しい Map();
m.set(未定義, 'nah');
m.has(未定義) // true
m.delete(未定義)
m.has(未定義) // false
(6)Map.prototype.clear()
clear()
メソッドはすべてのメンバーをクリアし、戻り値はありません。
let Map = new Map();
map.set('foo', true);
map.set('バー', false);
マップ.サイズ // 2
マップ.クリア()
マップ.サイズ // 0
トラバーサルメソッド
Map 構造は、3 つのトラバーサー生成関数と 1 つのトラバーサル メソッドをネイティブに提供します。
Map.prototype.keys()
: キー名のトラバーサーを返します。Map.prototype.values()
: キー値のトラバーサーを返します。Map.prototype.entries()
: すべてのメンバーのトラバーサーを返します。Map.prototype.forEach()
: Map のすべてのメンバーを走査します。
Map の走査順序が挿入順序であることに注意することが重要です。
const マップ = 新しいマップ([
['F'、'いいえ']、
['T'、'はい']、
]);
for (map.keys() のキーを許可) {
コンソール.ログ(キー);
}
//「ふ」
//「た」
for (map.values() の値を許可) {
console.log(値);
}
// "いいえ"
// "はい"
for (map.entries() の let 項目) {
console.log(アイテム[0], アイテム[1]);
}
// 「ふ」「いいえ」
// 「た」「はい」
// または
for (map.entries() の [キー, 値]) {
console.log(キー, 値);
}
// 「ふ」「いいえ」
// 「た」「はい」
// map.entries() を使用するのと同等
for (マップの [キー, 値] にします) {
console.log(キー, 値);
}
// 「ふ」「いいえ」
// 「た」「はい」
上記のコードの最後の例は、Map 構造のデフォルトの反復子インターフェイス (Symbol.iterator
プロパティ)、つまり entries
メソッドを表しています。
マップ[シンボル.イテレータ] === マップ.エントリ
// 真実
Map 構造を配列構造に変換するより迅速な方法は、スプレッド演算子 (...
) を使用することです。
const マップ = 新しいマップ([
[1、「1」]、
[2、「2」]、
[3、「3」]、
]);
[...map.keys()]
// [1、2、3]
[...map.values()]
// ['1 2 3']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...地図]
// [[1,'one'], [2, 'two'], [3, 'three']]
配列のmapメソッドやfilterメソッドと組み合わせることで、Mapのトラバーサルやフィルタリングを実現できます(Map自体にはmapメソッドやfilterメソッドはありません)。
const map0 = 新しい Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = 新しいマップ(
[...map0].filter(([k, v]) => k < 3)
);
// マップ構造を生成 {1 => 'a', 2 => 'b'}
const map2 = 新しいマップ(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// マップ構造を生成 {2 => '_a', 4 => '_b', 6 => '_c'}
さらに、Map には forEach
メソッドもあります。これは配列の forEach
メソッドに似ており、トラバースすることもできます。
map.forEach(関数(値, キー, マップ) {
console.log("キー: %s、値: %s", キー, 値);
});
forEach
メソッドは、this
をバインドするために使用される 2 番目のパラメーターも受け入れることができます。
const レポーター = {
レポート: 関数(キー, 値) {
console.log("キー: %s、値: %s", キー, 値);
}
};
map.forEach(関数(値, キー, マップ) {
this.report(キー, 値);
}、記者);
上記のコードでは、「forEach」メソッドのコールバック関数の「this」が「reporter」を指しています。
他のデータ構造との間の変換
(1) マップを配列に変換
前に述べたように、Map を配列に変換する最も便利な方法は、スプレッド演算子 (...
) を使用することです。
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...マイマップ]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
(2) 配列からマップへ
配列を Map コンストラクターに渡して Map に変換します。
新しい地図([
[本当、7]、
[{foo: 3}, ['abc']]
])
// マップ {
// true => 7,
// オブジェクト {foo: 3} => ['abc']
// }
(3) マップをオブジェクトに変換
すべての Map キーが文字列の場合、ロスレスでオブジェクトに変換できます。
関数 strMapToObj(strMap) {
obj = Object.create(null); とします。
for (let [k,v] of strMap) {
obj[k] = v;
}
オブジェクトを返します。
}
const myMap = new Map()
.set('はい', true)
.set('いいえ'、false);
strMapToObj(myMap)
// { はい: true、いいえ: false }
文字列以外のキー名がある場合、キー名は文字列に変換され、オブジェクトのキー名として使用されます。
(4) オブジェクトをマップに変換
オブジェクトは「Object.entries()」を通じてマップに変換できます。
obj = {"a":1, "b":2};
let map = new Map(Object.entries(obj));
さらに、変換関数を自分で実装することもできます。
関数 objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
strMap を返します。
}
objToStrMap({はい: true、いいえ: false})
// マップ {"はい" => true、"いいえ" => false}
(5) JSON へのマッピング
Map を JSON に変換する場合は、2 つの状況を区別する必要があります。 1 つの状況として、マップのキー名がすべて文字列である場合があります。この場合、それらをオブジェクト JSON に変換することを選択できます。
関数 strMapToJson(strMap) {
JSON.stringify(strMapToObj(strMap)) を返します。
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"はい":true,"いいえ":false}'
もう 1 つの状況は、マップのキー名に文字列以外の名前が含まれている場合です。この場合、それらを配列 JSON に変換することを選択できます。
関数mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
(6) JSON をマップに変換
JSON を Map に変換します。 通常の状況では、すべてのキー名は文字列です。
関数 jsonToStrMap(jsonStr) {
objToStrMap(JSON.parse(jsonStr)) を返します。
}
jsonToStrMap('{"はい": true, "いいえ": false}')
// マップ {'はい' => true、'いいえ' => false}
ただし、JSON 全体が配列であり、各配列メンバー自体が 2 つのメンバーを持つ配列であるという特殊なケースがあります。このとき、1対1対応でMapに変換することができます。これは多くの場合、Map を配列 JSON に変換する逆の操作です。
関数 jsonToMap(jsonStr) {
新しい Map(JSON.parse(jsonStr)) を返します。
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// マップ {true => 7, オブジェクト {foo: 3} => ['abc']}
ウィークマップ
意味
「WeakMap」構造は「Map」構造に似ており、キーと値のペアのコレクションを生成するためにも使用されます。
//WeakMap は set メソッドを使用してメンバーを追加できます
const wm1 = 新しい WeakMap();
const キー = {foo: 1};
wm1.set(key, 2);
wm1.get(key) // 2
// WeakMap は配列も受け入れることができます。
//コンストラクタのパラメータとして
const k1 = [1, 2, 3];
const k2 = [4, 5, 6];
const wm2 = new WeakMap([[k1, 'foo'], [k2, 'bar']]);
wm2.get(k2) // "バー"
「WeakMap」と「Map」には 2 つの違いがあります。
まず第一に、WeakMap
はオブジェクト (null
を除く) と シンボル値 のみをキー名として受け入れます。他のタイプの値をキー名として使用します。
const マップ = 新しい WeakMap();
map.set(1, 2) // エラーを報告する
map.set(null, 2) // エラーを報告する
map.set(Symbol(), 2) // エラーは報告されませんでした
上記のコードでは、WeakMap のキー名として 1
と null
の値が使用されている場合はエラーが報告されますが、Symbol 値がキー名として使用されている場合はエラーは報告されません。
第二に、キー名 WeakMap
が指すオブジェクトはガベージ コレクション メカニズムに含まれていません。
「WeakMap」の設計目的は、オブジェクトにデータを保存したい場合があるが、これによりオブジェクトへの参照が形成されることです。以下の例を参照してください。
const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1, 'foo 要素'],
[e2, 'バー要素'],
];
上記のコードでは、e1
と e2
は 2 つのオブジェクトであり、arr
配列を通じてこれら 2 つのオブジェクトにテキストの説明を追加します。これにより、「arr」から「e1」および「e2」への参照が形成されます。
これら 2 つのオブジェクトが不要になったら、この参照を手動で削除する必要があります。そうしないと、ガベージ コレクション メカニズムが e1
と e2
によって占有されていたメモリを解放できません。
// e1とe2が不要な場合
// 参照は手動で削除する必要があります
arr[0] = null;
arr[1] = null;
上記の書き方は明らかに非常に不便です。書き忘れるとメモリリークが発生します。
WeakMap は、この問題を解決するために生まれました。そのキー名によって参照されるオブジェクトは弱参照です。つまり、ガベージ コレクション メカニズムは参照を考慮しません。したがって、参照されたオブジェクトへの他の参照がクリアされている限り、ガベージ コレクション メカニズムはオブジェクトによって占有されていたメモリを解放します。つまり、WeakMap 内のキー オブジェクトと対応するキーと値のペアは、不要になると、手動で参照を削除しなくても自動的に消えます。
基本的に、ガベージ コレクション メカニズムを妨げずにオブジェクトにデータを追加したい場合は、WeakMap を使用できます。典型的なアプリケーション シナリオは、Web ページの DOM 要素にデータを追加することであり、WeakMap
構造を使用できます。 DOM 要素がクリアされると、対応する WeakMap
レコードが自動的に削除されます。
const wm = 新しい WeakMap();
const 要素 = document.getElementById('example');
wm.set(要素, '何らかの情報');
wm.get(element) // 「何らかの情報」
上記のコードでは、まず新しい WeakMap インスタンスを作成します。次に、DOM ノードがキー名としてインスタンスに保存され、追加情報がキー値として WeakMap に保存されます。現時点では、WeakMap の element
への参照は弱参照であり、ガベージ コレクション メカニズムには含まれません。
つまり、上記の DOM ノード オブジェクト内の WeakMap の弱い参照に加え、他の場所にあるオブジェクトへの参照が削除されると、オブジェクトによって占有されていたメモリはガベージ コレクション メカニズムによって解放されます。 WeakMap によって保存されたキーと値のペアも自動的に消えます。
つまり、「WeakMap」の特殊なケースは、そのキーに対応するオブジェクトが将来消滅する可能性があるということです。 「WeakMap」構造はメモリ リークの防止に役立ちます。
WeakMap はキー値ではなくキー名のみを弱く参照することに注意してください。キー値は通常の参照のままです。
const wm = 新しい WeakMap();
キー = {} にします。
obj = {foo: 1} とします。
wm.set(key, obj);
obj = null;
wm.get(キー)
// オブジェクト {foo:1}
上記のコードでは、キー値 obj
は通常の参照です。したがって、WeakMap の外側で「obj」への参照が削除されても、WeakMap の内部の参照は依然として存在します。
WeakMap の構文
WeakMap と Map には主に 2 つの API の違いがあります。まず、トラバーサル操作がありません (つまり、keys()
、values()
、entries()
メソッドがありません)、および がありません。 size
属性。すべてのキー名をリストする方法はないため、特定のキー名が存在するかどうかはまったく予測できず、ガベージ コレクション メカニズムが実行されているかどうかによって決まります。この時点ではキー名は取得できますが、次の瞬間に突然ガベージコレクション機構が動作してしまい、キー名がなくなってしまうという不確実性を避けるため、キー名は取得できないと一律に規定されています。 2 番目に、クリアすることはできません。つまり、「clear」メソッドはサポートされていません。したがって、WeakMap
で利用できるメソッドは get()
、set()
、has()
、delete()
の 4 つだけです。
const wm = 新しい WeakMap();
// size、forEach、clear メソッドはいずれも存在しません
wm.size // 未定義
wm.forEach // 未定義
wm.clear // 未定義
WeakMap の例
WeakMap の例は、その中の参照が自動的に消えることを観察する方法がないため、実証するのが困難です。現時点では他のリファレンスも公開されており、WeakMapを指すキー名への言及はなく、キー名が存在するかどうかを確認することはできません。
He Shijun 先生 ヒント、参照が指す値が特に大量のメモリを占有する場合は、Node の プロセスを使用できます。 .memoryUsage
メソッドを参照してください。この考えに基づいて、netizen vtxf は次の例を追加しました。
まず、ノードのコマンドラインを開きます。
$ ノード --expose-gc
上記のコードでは、「--expose-gc」パラメータは、ガベージ コレクション メカニズムの手動実行が許可されていることを示しています。
次に、以下のコードを実行します。
// ガベージ コレクションを手動で実行して、取得したメモリ使用状況が正確であることを確認します
> グローバル.gc();
未定義
// メモリ使用量の初期状態を確認します。ヒープ使用量は約 4M です。
> process.memoryUsage();
{ RSS: 21106688、
ヒープ合計: 7376896、
使用ヒープ: 4153936、
外部: 9059 }
> wm = new WeakMap();
未定義
// 5*1024*1024 の配列を指す新しい変数キーを作成します
> let key = 新しい配列(5 * 1024 * 1024);
未定義
//WeakMap インスタンスのキー名を設定し、キー配列もポイントします
// このとき、実際にはキー配列が2回参照されていますが、
// 変数 key が 1 回参照され、WeakMap のキー名が 2 回目に参照されます。
// ただし、WeakMap はエンジンにとっては弱い参照であり、参照カウントは 1 のままです。
> wm.set(key, 1);
ウィークマップ {}
> グローバル.gc();
未定義
// この時点で、メモリ使用量 heapused は 45M に増加しました
> process.memoryUsage();
{ RSS: 67538944、
ヒープ合計: 7376896、
使用ヒープ: 45782816、
外部: 8945 }
// 変数キーの配列への参照をクリアします。
// ただし、WeakMap インスタンスのキー名による配列への参照は手動ではクリアされません
> キー = null;
ヌル
// ガベージコレクションを再度実行します
> グローバル.gc();
未定義
// メモリ使用量 heapused は約 4M に戻ります。
// WeakMap のキー名参照によって gc によるメモリのリサイクルが妨げられないことがわかります。
> process.memoryUsage();
{ RSS: 20639744、
ヒープ合計: 8425472、
使用ヒープ: 3979792、
外部: 8956 }
上記のコードでは、外部参照が消える限り、WeakMapの内部参照はガベージコレクションによって自動的にクリアされます。 WeakMap を使用すると、メモリ リークの解決がはるかに簡単になることがわかります。
Chrome ブラウザの開発ツールのメモリ パネルには、ガベージ コレクション (ガベージ コレクト) を強制できるゴミ箱ボタンがあります。このボタンは、WeakMap 内の参照が消えるかどうかを観察するために使用することもできます。
WeakMap の目的
前述したように、WeakMap の典型的なアプリケーション シナリオは、キー名としての DOM ノードです。以下に例を示します。
let myWeakmap = new WeakMap();
myWeakmap.set(
document.getElementById('ロゴ'),
{クリック回数: 0})
;
document.getElementById('ロゴ').addEventListener('クリック', function() {
let logoData = myWeakmap.get(document.getElementById('logo'));
logoData.timesClicked++;
}、 間違い);
上記のコードでは、document.getElementById('logo')
が DOM ノードであり、click
イベントが発生するたびにステータスが更新されます。この状態を WeakMap のキー値として置き、対応するキー名がこのノード オブジェクトです。この DOM ノードが削除されると、この状態は自動的に消え、メモリ リークの危険はありません。
WeakMap のもう 1 つの用途は、プライベート プロパティをデプロイすることです。
const _counter = 新しい WeakMap();
const _action = 新しい WeakMap();
クラス カウントダウン {
コンストラクター(カウンター、アクション) {
_counter.set(これ, カウンタ);
_action.set(これ, アクション);
}
dec() {
let counter = _counter.get(this);
if (カウンター < 1) を返します。
カウンタ - ;
_counter.set(これ, カウンタ);
if (カウンター === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
//終わり
上記のコードでは、Countdown
クラスの 2 つの内部属性 _counter
と _action
はインスタンスへの弱参照であるため、インスタンスが削除されるとそれらは消え、メモリ リークは発生しません。
弱い参照
WeakSet と WeakMap は、弱参照に基づいたデータ構造です。ES2021 はさらに一歩進んで、オブジェクトへの弱参照を直接作成するための WeakRef オブジェクトを提供します。
ターゲット = {} にします。
let wr = 新しい WeakRef(ターゲット);
上記の例では、target
が元のオブジェクトであり、コンストラクター WeakRef()
は、target
に基づいて新しいオブジェクト wr
を作成します。ここで、wr
は WeakRef のインスタンスであり、target
への弱参照です。ガベージ コレクション メカニズムはこの参照をカウントしません。つまり、wr
の参照は元のオブジェクト target
を妨げません。ガベージコレクションから削除されます。
WeakRef インスタンス オブジェクトには deref()
メソッドがあり、元のオブジェクトが存在する場合はそれを返します。元のオブジェクトがガベージ コレクション メカニズムによってクリアされている場合、このメソッドは unknown
を返します。
ターゲット = {} にします。
let wr = 新しい WeakRef(ターゲット);
obj = wr.deref(); とします。
if (obj) { // ターゲットはガベージ コレクション メカニズムによってクリアされていません
// ...
}
上の例では、deref()
メソッドは元のオブジェクトがクリアされたかどうかを判断できます。
弱参照オブジェクトの用途の 1 つは、キャッシュとして使用することです。キャッシュがクリアされていない場合は、キャッシュから値を取得できます。キャッシュはクリアされると自動的に無効になります。
関数 makeWeakCached(f) {
const キャッシュ = 新しい Map();
リターンキー => {
const ref = キャッシュ.get(キー);
if (参照) {
const キャッシュ = ref.deref();
if (キャッシュ済み !== 未定義) キャッシュ済みを返します。
}
const fresh = f(キー);
キャッシュ.set(キー、新しいWeakRef(新鮮));
新鮮なものを返します。
};
}
const getImageCached = makeWeakCached(getImage);
上の例では、makeWeakCached()
を使用して、元のファイルへの弱参照を保存するキャッシュを作成します。
標準では、WeakRef()
を使用して元のオブジェクトへの弱参照が作成されると、元のオブジェクトはこのイベント ループ (イベント ループ) では絶対にクリアされず、後続のイベント ループでのみクリアされると規定されていることに注意してください。 。
ファイナライズレジストリ
ES2021 では、よりクリーンなレジストリ関数 FinalizationRegistry が導入されています。これは、ターゲット オブジェクトがガベージ コレクション メカニズムによってクリアされた後に実行されるコールバック関数を指定するために使用されます。
まず、新しいレジストリ インスタンスを作成します。
const registry = new FinalizationRegistry(heldValue => {
// ....
});
上記のコードでは、FinalizationRegistry()
はシステムによって提供されるコンストラクターであり、実行されるコールバック関数を登録する、よりクリーンなレジストリ インスタンスを返します。コールバック関数は、FinalizationRegistry()
のパラメータとして渡され、それ自体にはパラメータ heldValue
があります。
次に、レジストリインスタンスの「register()」メソッドを利用して、監視対象のオブジェクトを登録します。
registry.register(theObject, "何らかの値");
上の例では、theObject
が監視対象のオブジェクトです。オブジェクトがガベージ コレクション メカニズムによってクリアされると、レジストリは以前に登録されたコールバック関数を呼び出し、パラメータ (以前は) heldValue
として some value
を渡します。 `) はコールバック関数に渡されます。
レジストリはターゲット オブジェクト「theObject」への強参照ではなく、弱参照であることに注意してください。強参照があると元のオブジェクトがガベージコレクション機構によってクリアされなくなり、レジストリを使用する意味がなくなってしまうためです。
コールバック関数のパラメータ heldValue
は、任意のタイプの値、文字列、数値、ブール値、オブジェクト、さらには unknown
にすることができます。
最後に、登録したコールバック関数を将来キャンセルしたい場合は、3 番目のパラメータをマーク値として register()
に渡す必要があります。このタグ値はオブジェクトである必要があり、通常はプリミティブ オブジェクトが使用されます。次に、レジストリ インスタンス オブジェクトの unregister()
メソッドを使用して登録を解除します。
registry.register(theObject, "何らかの値", theObject);
// ...その他の操作...
registry.unregister(theObject);
上記のコードでは、register()
メソッドの 3 番目のパラメータはマーク値 theObject
です。コールバック関数をキャンセルする場合は、unregister()
メソッドを使用し、タグの値をパラメータとしてメソッドに渡します。ここでの「register()」メソッドの 3 番目のパラメータへの参照も弱参照です。このパラメータがないと、コールバック関数をキャンセルできません。
コールバック関数は呼び出された後はレジストリに存在しないため、コールバック関数を呼び出す前に unregister()
を実行する必要があります。
次に、「FinalizationRegistry」を使って前項のキャッシュ機能を強化します。
関数 makeWeakCached(f) {
const キャッシュ = 新しい Map();
const cleanup = new FinalizationRegistry(key => {
const ref = キャッシュ.get(キー);
if (ref && !ref.deref()) キャッシュ.削除(キー);
});
リターンキー => {
const ref = キャッシュ.get(キー);
if (参照) {
const キャッシュ = ref.deref();
if (キャッシュ済み !== 未定義) キャッシュ済みを返します。
}
const fresh = f(キー);
キャッシュ.set(キー、新しいWeakRef(新鮮));
cleanup.register(新鮮、キー);
新鮮なものを返します。
};
}
const getImageCached = makeWeakCached(getImage);
前のセクションの例と比較すると、上の例では、キャッシュされた元のオブジェクトがガベージ コレクション メカニズムによってクリアされると、コールバック関数が自動的に実行されます。このコールバック関数は、キャッシュ内の期限切れのキーをクリアします。
別の例を示します。
クラス Thing {
#ファイル;
#cleanup = ファイル => {
コンソール.エラー(
`ファイル "${file.name}" の \`Thingy\` に対して \`release\` メソッドが呼び出されませんでした`
);
};
#registry = 新しい FinalizationRegistry(this.#cleanup);
コンストラクター(ファイル名) {
this.#file = File.open(ファイル名);
this.#registry.register(this, this.#file, this.#file);
}
リリース() {
if (this.#file) {
this.#registry.unregister(this.#file);
File.close(this.#file);
this.#file = null;
}
}
}
上記の例では、何らかの理由で Thingy
クラスのインスタンス オブジェクトが release()
メソッドを呼び出さずにガベージ コレクション メカニズムによってクリアされた場合、クリーナーはコールバック関数 #cleanup()
を呼び出します。そしてエラーメッセージを出力します。
クリーナーがいつ実行されるかを知る方法がないため、使用しないことが最善です。さらに、ブラウザ ウィンドウが閉じられている場合、またはプロセスが予期せず終了した場合、クリーナーは実行されません。
参考リンク
作者: wangdoc
アドレス: https://wangdoc.com/
ライセンス: クリエイティブ・コモンズ 3.0