JavaScript 中的單例模式確保一個類只有一個實例,並提供全局訪問點。以下是幾種常見的實現方式:
1.對象字面量(最簡單的方式)
const Singleton = {
property: 'value',
method() {
// 使用 this(在方法被解構調用時會丟失上下文)
// console.log(this.property);
// 使用 Singleton(更安全)
console.log(Singleton.property);
}
};
// 使用
Singleton.method();
JavaScript 可以使用對象字面量快速創建一個對象,創建的對象本身就是單例。這種方式最為簡單,但是需要注意 this 的引用可能出問題。
為了解決 this 的問題,可以直接引用單例對象本身。
2.閉包實現
const Singleton = (function() {
let instance;
function createInstance() {
const object = new Object('I am the instance');
return object;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// 使用
const instance1 = Singleton.getInstance();
這裏利用了閉包的特性實現了模塊的封裝和單例對象的引用,返回一個 getInstance 方法用於獲取實例對象。
3.ES6 Class 實現
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = 'Singleton Data';
Singleton.instance = this;
}
getData() {
return this.data;
}
setData(data) {
this.data = data;
}
}
// 使用
const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2); // true
s1.setData('New Data');
console.log(s2.getData()); // 'New Data'
這裏的 instance 是一個靜態變量,這種方式在 constructor 的最後將 this 賦值給 instance。
4.改進的 class 實現(typescript 版本)
class Singleton {
private static instance?: Singleton;
private constructor() {}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 使用
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true
typescript 版本抽取 getInstance 方法,讓代碼更可讀。使用 private 關鍵字實現私有屬性和方法,保證代碼不會被隨意篡改。
5.ES6 模塊模式的單例
// 在模塊文件中
let instance = null;
class Database {
constructor(config) {
if (instance) {
return instance;
}
this.connection = this.connect(config);
instance = this;
}
connect(config) {
return { connected: true, config };
}
}
// 導出一個獲取實例的函數
export const getInstance= (() => {
let instance = null;
return (config) => {
if (!instance) {
instance = new Database(config);
}
return instance;
};
})();
這種方式利用 ES6 模塊變量的引用共享特性,保證 instance 唯一。導出一個 getInstance 方法。
6.ES6 模塊本身就是單例
class Database {
constructor(config) {
this.connection = this.connect(config);
}
connect(config) {
return { connected: true, config };
}
}
// 直接導出一個實例
export default new Database({ host: 'localhost' });
實測證明,導出的實例在多個模塊間是共享的。
總結
JavaScript 實現單例模式的方式很多,這裏介紹常用的6種,主要分4大類:對象字面量、閉包實現、 ES6 class 實現、ES6 模塊模式實現。
選擇哪種實現方式取決於具體需求,簡單場景可以使用對象字面量,複雜場景建議使用 ES6 Class 或 ES6 模塊模式實現。