JavaScript では、シングルトン パターンは最も基本的で頻繁に使用されるデザイン パターンの 1 つであり、誤って使用してしまう可能性があります。
この記事では、最も基本的な理論から始めて、シングルトン パターンの基本概念と実装について説明し、最後に例を使用してシングルトン パターンの適用について説明します。
理論的根拠
コンセプト
シングルトン モードは、名前が示すように、インスタンスが 1 つだけ存在することを意味します。シングルトン モードでは、システム内にクラスのインスタンスが 1 つだけ存在し、外部からそのインスタンスに簡単にアクセスできるようにすることができるため、インスタンス数の制御が容易になり、システム リソースが節約されます。特定のクラスのオブジェクトを 1 つだけシステム内に存在させたい場合は、シングルトン パターンが最適なソリューションです。
基本構造
最も単純なシングルトン パターンは、関連するプロパティとメソッドをまとめたオブジェクト リテラルで始まります。
var singleton = { prop:"value", method:function(){ } }
この形式のシングルトン モードでは、すべてのメンバーがパブリックであり、シングルトン経由でアクセスできます。この欠点は、ユーザーに公開されることが想定されていない補助メソッドがシングルトンに存在し、ユーザーがこれらのメソッドを使用し、その後のメンテナンス中に一部の補助メソッドが削除されると、プログラム エラーが発生することです。
このような間違いを避けるにはどうすればよいでしょうか?
プライベートメンバーによるシングルトンパターン
クラスにプライベート メンバーを作成するにはどうすればよいですか? これは、クロージャを要求することで実現されます。クロージャの知識については、この記事では詳しく説明しませんので、ご自身で調べてください。
基本的な形式は次のとおりです:
var singleton = (function () { var privateVar = "private"; return { prop: "value", method: function () { console.log(privateVar); } } })();
1 つ目は自己実行型の匿名関数で、変数 privateVar が宣言され、オブジェクトが返されてシングルトン オブジェクトに割り当てられます。 privateVar 変数は、匿名関数の外部からはアクセスできません。このプライベート変数は、関数内または公開されたメソッドを通じてのみアクセスできます。この形式はモジュールパターンとも呼ばれます。
遅延インスタンス化
直接リテラルでもプライベート メンバー シングルトン モードでも、どちらもスクリプトのロード時に作成されるシングルトンですが、場合によってはページがこのシングルトン オブジェクトを使用しないことがあり、リソースの無駄が発生します。この状況に対処する最善の方法は遅延読み込みです。これは、必要になるまでシングルトン オブジェクトが実際にはインスタンス化されないことを意味します。
var singleton = (function () { function init() { var privateVar = "private"; return { prop: "value", method: function () { console.log(privateVar); } } } var instance = null; return { getInstance: function () { if (!instance) { instance = init(); } return instance; } } })();
まず、シングルトン オブジェクトを作成するコードを init 関数にカプセル化し、次にシングルトン オブジェクトのインスタンスを表すプライベート変数インスタンスを宣言し、シングルトン オブジェクトを取得するメソッド getInstance を公開します。
呼び出すときは、singleton.getInstance() を通じて呼び出されます。getInstance が呼び出されたときに、実際にはシングルトン オブジェクトが作成されます。
適用シーン
シングルトン パターンは、JS で最も一般的に使用されるデザイン パターンであり、モジュール性とコード構成の強化の観点から、可能な限りシングルトン パターンを使用する必要があります。大規模なプロジェクトでは、関連するコードをまとめて整理し、メンテナンスを容易にすることができます。各モジュールの遅延読み込みにより、パフォーマンスが向上し、実装の詳細が非表示になり、一般的に使用される API が公開されます。アンダースコアや jQuery などの一般的なクラス ライブラリは、シングルトン モード アプリケーションとして理解できます。
実戦との組み合わせ
前に述べたように、シングルトン パターンは最も一般的に使用されるデザイン パターンの 1 つです。例を示して説明します。
次のコードは主に、シングルトン モードで実装された単純な日付ヘルパー クラスを実装します。
基本的なシングルトン パターン構造
var dateTimeHelper = { now: function () { return new Date(); }, format: function (date) { return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(); } }; console.log(dateTimeHelper.now());<br />
遅延読み込みはシングルトン パターンを実装します
var dateTimeHelper = (function () { function init() { return { now: function () { return new Date(); }, format: function (date) { return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(); } } } var instance = null; return { getInstance: function () { if (!instance) { instance = init(); } return instance; } } })(); console.log(dateTimeHelper.getInstance().now())
さらにいくつかの例を見てみましょう:
実装 1: 最も単純なオブジェクト リテラル
var singleton = { attr : 1, method : function(){ return this.attr; } } var t1 = singleton ; var t2 = singleton ;
非常にシンプルで使いやすいのですが、欠点は、カプセル化がなく、すべての属性メソッドが公開されていることです。プライベート変数を使用する必要がある場合には、これでは不十分なようです。もちろん、これに関しては一定の欠点もあります。
実装 2: コンストラクター内部の判断
実際、クラスのインスタンスが既に存在するかどうかの判断がコンストラクター内に配置されることを除いて、これは元の JS 実装にある程度似ています。
function Construct(){ // 确保只有单例 if( Construct.unique !== undefined ){ return Construct.unique; } // 其他代码 this.name = "NYF"; this.age="24"; Construct.unique = this; } var t1 = new Construct() ; var t2 = new Construct() ;
那么也有的, t1 === t2 。
也是非常简单,无非就是提出一个属性来做判断,但是该方式也没有安全性,一旦我在外部修改了Construct的unique属性,那么单例模式也就被破坏了。
实现3 : 闭包方式
对于大着 灵活 牌子的JS来说,任何问题都能找到 n 种答案,只不过让我自己去掂量孰优孰劣而已,下面就简单的举几个使用闭包实现单例模式的方法,无非也就是将创建了的单例缓存而已。
var single = (function(){ var unique; function Construct(){ // ... 生成单例的构造函数的代码 } unique = new Constuct(); return unique; })();
只要 每次讲 var t1 = single; var t2 = single;即可。 与对象字面量方式类似。不过相对而言更安全一点,当然也不是绝对安全。
如果希望会用调用 single() 方式来使用,那么也只需要将内部的 return 改为
return function(){ return unique; }
以上方式也可以使用 new 的方式来进行(形式主义的赶脚)。当然这边只是给了闭包的一种例子而已,也可以在 Construct 中判断单例是否存在 等等。 各种方式在各个不同情况做好选着即可。
总结
单例模式的好处在于对代码的组织作用,将相关的属性和方法封装在一个不会被多次实例化的对象中,让代码的维护和调试更加轻松。隐藏了实现细节,可以防止被错误修改,还防止了全局命名空间的污染。另外可以通过惰性加载提高性能,减少不必要的内存消耗。