JavaScriptでWeakMapを使用してプライベートフィールドを実装する

背景



JavaScriptの最大の欠落の1つは、ユーザータイプにプライベートフィールドを作成できないことです。 コンストラクター内にプライベート変数を作成し、それらにアクセスできる特権メソッドを作成する方法は1つしかありません。



function Person(name) { this.getName = function() { return name; }; }
      
      





この例では、 getName()メソッドはname引数(実際にはローカル変数)を使用して、オブジェクトのプロパティとしてnameを展開することなく、人の名前の値を返します。 このアプローチはそれ自体に非常に適していますが、パフォーマンスの点では非常に非効率的です。 ご存知のように、 JavaScriptの関数はオブジェクトであり、Personオブジェクトのインスタンスをさらに使用すると、プロトタイプの1つだけを使用する代わりに、それぞれがgetName()メソッドのコピーを保存します。



別の解決策として、同意によってフィールドをプライベートに作成する方法を選択し、その名前にプレフィックスを付けます。通常は下線の形式です。 下線は魔法ではなく、フィールドを使用から保護するものではありません。ほとんどの場合、触れてはならないことを思い出させるだけです。 例:



 function Person(name) { this._name = name; } Person.prototype.getName = function() { return this._name; };
      
      





各インスタンスはプロトタイプの同じメソッドを使用するため、このパターンはより効果的です。 メソッドとフィールドには外部からアクセスできます。._nameにタッチできないことに同意するだけです 。 このソリューションは理想とはほど遠いですが、かなりの数のプログラマーが使用しています。 かつて、それはPythonからもたらされました。



すべてのインスタンスに共通フィールドを使用する場合、コンストラクターを含むIIFE関数を使用して簡単に作成できる別のオプションがあります。 例:



 var Person = (function() { var sharedName; function Person(name) { sharedName = name; } Person.prototype.getName = function() { return sharedName; }; return Person; }());
      
      





ここで、 sharedNamePersonのすべてのインスタンスに共通であり、それぞれの新しいインスタンスはname引数で値を上書きします。 これは明らかに無意味なソリューションですが、真にプライベートなフィールドを実装する方法を理解する上で非常に重要です。



プライベートフィールドへの道



共有プライベートフィールドのパターンは、潜在的な解決策を示しています。プライベートデータがインスタンスに保存されていないが、アクセスできる場合はどうでしょうか。 隠しフィールドを保存して、実装に関するすべての個人情報を隠すことができるオブジェクトがある場合はどうなりますか? ECMAScript 6より前はおそらく次のように実装していました。



 var Person = (function() { var privateData = {}, privateId = 0; function Person(name) { Object.defineProperty(this, "_id", { value: privateId++ }); privateData[this._id] = { name: name }; } Person.prototype.getName = function() { return privateData[this._id].name; }; return Person; }());
      
      





このようにして、私たちはすべて何かになりました:) privateDataオブジェクト外部IIFEからアクセスできず、内部に保存されているすべての情報を完全に隠します。 privateId変数は、インスタンスが使用する次に使用可能なIDを保持します。 残念ながら、IDはインスタンスに保存する必要があり、使用中も使用できないようにする必要があります。 したがって、 Object.defineProperty()を使用して値を設定し、変数が読み取り専用であることを確認します。 次に、 getName()内で、メソッドは読み取りと書き込みのために_idを使用してプライベート変数にアクセスします。



このアプローチは優れており、プライベート変数の保存に非常に適しています。 ただし、 _idの過剰使用は問題の一部にすぎません。 このアプローチも問題を引き起こします-インスタンスがガベージコレクターによって削除されても、 privateDataに書き込んだデータはメモリに残ります。 なるほど、これはECMAScript 5で実装できる最高のものです。



WeakMap出力



画像

WeakMapは、前の例の残りの問題を解決します。 まず、インスタンス自体を一意のIDにできるため、一意のIDを保存する必要がなくなりました。 次に、WeakMapは弱いリンクを持つコレクションであるため、ガベージコレクターは不要になったレコードを削除できます。 インスタンスが削除されると、レコードにはハードリンクがなくなります。 この場合のガベージコレクターは、このインスタンスに関連付けられたすべてのレコードを削除します。 前の例とまったく同じ基本パターンですが、よりコーシャ:



 var Person = (function() { var privateData = new WeakMap(); function Person(name) { privateData.set(this, { name: name }); } Person.prototype.getName = function() { return privateData.get(this).name; }; return Person; }());
      
      





この例では、 privateDataはWeakMapのインスタンスです。 新しいPersonが作成されると、WeakMapに新しいレコードが追加され、プライベート変数が保存されます。 WeakMapのキーはthisです。開発Personオブジェクトのインスタンスにアクセスできますが、このインスタンスの外部からprivateDataにアクセスすることはできません。 このデータにアクセスしようとするメソッドは、そのインスタンス(メソッドが配置されている内部)を渡すだけです。 この例では、 getName()はレコードを取得し、 nameプロパティを返します。



おわりに



これは、 ECMAScript 6で新しいWeakMapオブジェクトの使用を見つけていない人にとっては素晴らしい例です。 JavaScriptコードの記述方法に多くの予言的な将来の変化があります。 個人的には、これはOOP JavaScriptの力のターニングポイントのようなものです。



All Articles