JavaScript 如何实现单例
平时我们用网页的时候,总会遇到一些“只需要有一份就行”的功能——比如网站的全局配置工具、弹窗管理器,或者聊天软件的状态管理器。如果不小心多创建了几份,要么会浪费内存,要么会出现“两份配置打架”的问题,这时候“单例模式”就派上用场了。它简单说就是:不管怎么调用,只生成一个实例,用完就共享这一份。
先搞懂最基础的单例写法
单例的核心是“记录有没有创建过实例”,用一个变量存实例就行。举个生活化的例子:小区门口的快递驿站,不管几个人去寄快递,驿站本身只有一个,不会因为有人来就多开一个。对应的JS代码可以这样写:

`javascript
// 用闭包存实例,避免全局变量污染
const CourierStation = (function() {
let instance; // 专门存唯一的驿站实例
return function() {
// 如果已经有实例,就直接返回
if (instance) return instance;
// 没有的话就创建新实例,存起来
instance = { name: "小区快递驿站", address: "1号楼底商" };
return instance;
};
})();
// 测试:不管new多少次,都是同一个实例
const station1 = new CourierStation();
const station2 = new CourierStation();
console.log(station1 === station2); // 输出true,说明是同一个
`
这个写法的关键是用闭包把instance藏起来,不会被外部随便修改,保证了唯一性。如果不用闭包,把instance放全局,很容易被误改成别的东西,就失去单例的意义了。
适合对象的单例:用类来实现
如果你的单例要带方法,比如驿站有“存快递”“取快递”的功能,用ES6类来写会更清晰。就像小区驿站不仅有地址,还能提供服务,对应的单例类可以这样写:
`javascript
class ParkStation {
constructor() {
// 把构造函数改成只允许创建一次实例
if (ParkStation.instance) {
return ParkStation.instance;
}
ParkStation.instance = this;
this.name = "小区停车场";
this.cars = [];
}
// 存车方法
parkCar(car) {
this.cars.push(car);
console.log(${car}已停放,当前共${this.cars.length}辆车);
}
}
// 测试:创建两次,还是同一个实例
const park1 = new ParkStation();
const park2 = new ParkStation();
park1.parkCar("特斯拉"); // 输出:特斯拉已停放,当前共1辆车
park2.parkCar("比亚迪"); // 输出:比亚迪已停放,当前共2辆车
console.log(park1 === park2); // 输出true
`
这里的ParkStation.instance就是用来“记账”的,第一次创建实例后,下次再new就直接返回已存的实例,不会重复生成。这个写法比闭包更直观,适合需要定义多个属性和方法的单例。
灵活的单例:通用的“创建工具”
有时候你需要不同的单例——比如有的是驿站,有的是停车场,这时候可以写一个通用的单例生成函数,就像一个“单例工厂”,不管你要创建什么单例,都能用它来做。举个例子,要做两个不同的单例:小区的超市和楼下的咖啡店:
`javascript
// 通用单例生成器,接收一个对象作为实例模板
function createSingleton(template) {
let instance;
return function() {
if (instance) return instance;
instance = Object.create(template);
return instance;
};
}
// 生成超市单例
const Supermarket = createSingleton({ type: "超市", goods: ["蔬菜", "零食"] });
// 生成咖啡店单例
const CoffeeShop = createSingleton({ type: "咖啡店", drinks: ["拿铁", "美式"] });
// 测试:两个单例完全独立
const market1 = new Supermarket();
const market2 = new Supermarket();
const coffee1 = new CoffeeShop();
console.log(market1 === market2); // true
console.log(market1 === coffee1); // false
`
这个工厂函数把“创建唯一实例”的逻辑抽出来,不管要做什么单例,只要传入对应的模板就行,不用每次都重新写判断逻辑,适合项目里要用到多个单例的场景。
单例的小注意事项
单例虽然好用,但不能滥用。比如把每个工具都做成单例,会让代码灵活性变差——比如你想同时用两个不一样的弹窗,就会做不到。就像你不能把小区所有的店都只开一家,不然想买不同的东西都不方便。所以单例只适合“全局唯一”的场景,比如全局的配置、状态管理器,用的时候要想好是不是真的需要“只有一个”。