定义
限制类实例化次数只能一次,一个类只有一个实例,并提供一个访问它的全局访问点。适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用。不适用动态扩展对象,或需创建多个相似对象的场景。
原理
使用一个变量存储类实例对象,初始值为 null
或者undefined
。进行类实例化时,首先判断类实例对象是否存在,存在则返回该实例,不存在则创建类实例后返回。无论调用多少次类生成实例方法,返回的都是同一个实例对象。
类图
简单单例模式
一个类可以创建多个实例,并且每个实例之间都不相等
代码语言:javascript复制class Window {
constructor() {}
}
let w1 = new Window();
let w2 = new Window();
//两个实例不相等
console.log(w1 === w2); //false
那我们想实现单例怎么办,把constructor
设成私有,这样就不能在外部访问构造函数了。在函数 SingleObject
中定义一个getInstance()
方法来管控单例,并创建返回类实例对象,而不是通过传统的 new
操作符来创建类实例对象。第一次调用Window.getInstance()
没有值,那么初始化一个值并返回。第二次有值直接返回。这样就不可以创建两个Window
的实例。
es6实现单例模式
代码语言:javascript复制/**
*使用static关键字定义静态属性 通过类访问
*TS中属性具有三种修饰符:
*public(默认值),可以在类、子类和对象中修改
*protected ,可以在类、子类中修改
*private ,可以在类中修改
*/
class Window {
private static instance: Window;
private constructor() {}
public static getInstance() {
if (!Window.instance) {
Window.instance = new Window();
}
return Window.instance;
}
}
let w1 = Window.getInstance();
let w2 = Window.getInstance();
console.log(w1 === w2); //true
es5实现的单例模式
代码语言:javascript复制interface Window {
hello: any
}
function Window() { }
Window.prototype.hello = function () {
console.log('hello');
}
Window.getInstance = (function () {
let window: Window;
return function () {
if (!window)
window = new (Window as any)();
return window;
}
})();
let window = Window.getInstance();
window.hello();
这种方式有个缺点,必须告诉使用者通过getInstance()
方法来得到单例。
透明单例模式
使用者不知道要按照单例使用,还是会使用new
来调用构造函数从而发生错误,那么为了解决这个问题,我们通过下面这种方式,还是使用new
的方式创建对象,但返回的都是同一个实例。
let Window = (function () {
let window: Window;
let Window = function (this: Window) {
if (window) {
return window;
} else {
return (window = this);
}
};
Window.prototype.hello = function () {
console.log('hello');
};
return Window;
})();
let window1 = new (Window as any)();
let window2 = new (Window as any)();
window1.hello();
console.log(window1 === window2);//true
这样使用者就不需要知道该怎么特殊使用了,正常new
就行了。
单例与构建分离
上面的例子创建单例的方法都在类的内部实现的,管理单例的操作,与对象创建的操作,功能代码耦合在一起,不符合 “单一职责原则”。所以我们要进行单例与构建的分离。
代码语言:javascript复制interface Window {
hello: any
}
function Window() {
}
Window.prototype.hello = function () {
console.log('hello');
}
let createInstance = (function () {
let instance: Window;
return function () {
if (!instance) {
instance = new (Window as any)();
}
return instance;
}
})();
let window1 = createInstance();
let window2 = createInstance();
window1.hello();
console.log(window1 === window2)
封装变化
上面的例子createInstance
只能创建Window
的实例,我希望createInstance
可以创建任何类的实例
function Window() {
}
Window.prototype.hello = function () {
console.log('hello');
}
let createInstance = function (Constructor: any) {
//创建变量 任何类型
let instance: any;
return function (this: any) {
if (!instance) {
Constructor.apply(this, arguments);
//this.__proto__ = Constructor.prototype
Object.setPrototypeOf(this, Constructor.prototype)
instance = this;
}
return instance;
}
};
let CreateWindow: any = createInstance(Window);
let window1 = new CreateWindow();
let window2 = new CreateWindow();
window1.hello();
console.log(window1 === window2)
示例
模态窗口的实现
代码语言:javascript复制<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button id="show-button">显示模态窗口</button>
<button id="hide-button">隐藏模态窗口</button>
<script>
class Login {
constructor() {
this.element = document.createElement('div');
this.element.innerHTML = (
`
用户名 <input type="text"/>
<button>登录</button>
`
);
this.element.style.cssText = 'width: 100px; height: 100px; position: absolute; left: 50%; top: 50%; display: block;';
document.body.appendChild(this.element);
}
show() {
this.element.style.display = 'block';
}
hide() {
this.element.style.display = 'none';
}
}
Login.getInstance = (function () {
let instance;
return function () {
if (!instance) {
instance = new Login();
}
return instance;
}
})();
document.getElementById('show-button').addEventListener('click', function (event) {
Login.getInstance().show();
});
document.getElementById('hide-button').addEventListener('click', function (event) {
Login.getInstance().hide();
});
</script>
</body>
</html>
缓存
访问磁盘文件是异步的,每次都调用readFile
方法会很慢,我们如果已经访问过了,就添加缓存到内存里。 访问内存比访问磁盘要快的多。
let express = require('express');
let fs = require('fs');
let app = express();
app.get('/user/:id', function (req: any, res: any) {
let id = req.params.id
fs.readFile(`./users/${id}.json`, 'utf8', function (err: any, data: any) {
let user = JSON.parse(data);
res.json(user);
});
});
app.listen(3000);
缓存一定要做成单例的,这样不管谁存进去了,别人都可以拿到
代码语言:javascript复制let express = require('express');
let fs = require('fs');
//缓存
let cache: Record<any, any> = {};
let app = express();
app.get('/user/:id', function (req: any, res: any) {
let id = req.params.id;
//如果缓存里有直接返回 否则访问磁盘
let user = cache.get(id);
if (user) {
res.json(user);
} else {
fs.readFile(`./users/${id}.json`, 'utf8', function (err: any, data: any) {
let user = JSON.parse(data);
cache.put(id, user);
res.json(user);
});
}
});
app.listen(3000);
小伙伴们觉的对你有帮助的请点赞