1. 什么是单例模式?
单例模式是一种十分常用但却相对而言比较简单的单例模式。它是指在一个类只能有一个实例,即使多次实例化该类,也只返回第一次实例化后的实例对象。单例模式不仅能减少不必要的内存开销, 并且在减少全局的函数和变量冲突也具有重要的意义。
1.1 最简单的单例模式
就算你对于单例模式的概念还比较模糊,但是我相信你肯定已经使用过单例模式了。我们来看一下下面的一段代码:
1.2 惰性单例
采用对象字面量创建单例只能适用于简单的应用场景,一旦该对象十分复杂,那么创建对象本身就需要一定的耗时,且该对象可能需要有一些私有变量和私有方法。此时使用对象字面创建单例就不再行得通了,我们还是需要采用构造函数的方式实例化对象。下面就是使用立即执行函数和构造函数的方式改造上面的timeTool工具库。
上面的timeTool实际上是一个函数,_instance作为实例对象最开始赋值为null,init函数是其构造函数,用于实例化对象,立即执行函数返回的是匿名函数用于判断实例是否创建,只有当调用timeTool()时进行实例的实例化,这就是惰性单例的应用,不在js加载时就进行实例化创建, 而是在需要的时候再进行单例的创建。 如果再次调用, 那么返回的永远是第一次实例化后的实例对象。
2. 单例模式的应用场景
2.1 命名空间
一个项目常常不只一个程序员进行开发和维护, 然后一个程序员很难去弄清楚另一个程序员暴露在的项目中的全局变量和方法。如果将变量和方法都暴露在全局中, 变量冲突是在所难免的。就想下面的故事一样:
2.2 管理模块
上面说到的timeTool对象是一个只用来处理时间的工具库,但是实际开发过程中的库可能会有多种多样的功能,例如处理ajax请求,操作dom或者处理事件。这个时候单例模式还可以用来管理代码库中的各个模块,例如下面的代码所示。
上面的代码库中有ajax,dom和event三个模块,用同一个命名空间devA来管理。在进行相应操作的时候,只需要devA.ajax.get()进行调用即可。这样可以让库的功能更加清晰。
3. ES6中的单例模式
3.1 ES6创建对象
ES6中创建对象时引入了class和constructor用来创建对象。下面我们来使用ES6的语法实例化苹果公司
点击我的头像,关注我,私信回复:【学习资料】就可以领取小编给大家分享的学习
3.2 ES6中创建单例模式
苹果这么伟大的公司明显有且只有一个, 就是乔爷爷创建的那个, 哪能容别人进行复制?所以appleCompany应该是一个单例, 现在我们使用ES6的语法将constructor改写为单例模式的构造器。
3.3 ES6的静态方法优化代码
ES6中提供了为class提供了static关键字定义静态方法, 我们可以将constructor中判断是否实例化的逻辑放入一个静态方法getInstance中,调用该静态方法获取实例, constructor中只包需含实例化所需的代码,这样能增强代码的可读性、结构更加优化。
4. 单例模式的项目实战应用
4.1 实现登陆弹框
登陆弹框在项目中是一个比较经典的单例模式,因为对于大部分网站不需要用户必须登陆才能浏览,所以登陆操作的弹框可以在用户点击登陆按钮后再进行创建。而且登陆框永远只有一个,不会出现多个登陆弹框的情况,也就意味着再次点击登陆按钮后返回的永远是一个登录框的实例。
现在来梳理一下我登陆弹框的流程,在来进行代码的实现:
给顶部导航模块的登陆按钮注册点击事件
登陆按钮点击后JS动态创建遮罩层和登陆弹框
遮罩层和登陆弹框插入到页面中
给登陆框中的关闭按钮注册事件, 用于关闭遮罩层和弹框
给登陆框中的输入框添加校验(此步骤略)
4.1.1 给页面添加顶部导航栏的HTML代码
4.1.2 使用ES6的语法创建Login类
lass Login {
//构造器
constructor() {
this.init();
}
//初始化方法
init() {
//新建div
let mask = document.createElement('div');
//添加样式
mask.classList.add('mask-layer');
//添加模板字符串
mask.innerHTML =
`
登录框
×
用户名:
密码:
`;
//插入元素
document.body.insertBefore(mask, document.body.childNodes[0]);
//注册关闭登录框事件
Login.addCloseLoginEvent();
}
//静态方法: 获取元素
static getLoginDom(cls) {
return document.querySelector(cls);
}
//静态方法: 注册关闭登录框事件
static addCloseLoginEvent() {
this.getLoginDom('.close-btn').addEventListener('click', () => {
//给遮罩层添加style, 用于隐藏遮罩层
this.getLoginDom('.mask-layer').style = "display: none";
})
}
//静态方法: 获取实例(单例)
static getInstance() {
if(!this.instance) {
this.instance = new Login();
} else {
//移除遮罩层style, 用于显示遮罩层
this.getLoginDom('.mask-layer').removeAttribute('style');
}
return this.instance;
}
}
单例模式虽然简单,但是在项目中的应用场景却是相当多的,单例模式的核心是确保只有一个实例, 并提供全局访问。就像我们只需要一个浏览器的window对象, jQuery的$对象而不再需要第二个。 由于JavaScript代码书写方式十分灵活, 这也导致了如果没有严格的规范的情况下,大型的项目中JavaScript不利于多人协同开发, 使用单例模式进行命名空间,管理模块是一个很好的开发习惯,能够有效的解决协同开发变量冲突的问题。灵活使用单例模式,也能够减少不必要的内存开销,提高用于体验。
閱讀更多 易學前端筆記 的文章
關鍵字: 编程语言 JavaScript 单例