单例模式的定义
单例模式:一个类只能有一个实例,即使多次实例化该类,也只返回第一次实例化后的实例对象。
核心要点:确保只有一个实例, 并提供全局访问。
推荐使用场景:用单例模式进行命名空间,管理模块
优点:
1. 减少不必要的内存开销
2. 减少全局的函数和变量冲突
演示范例——通过对象字面量创建对象的方式实现单例模式
得益于JavaScript创建对象的方式十分灵活, 可以直接通过对象字面量的方式实例化一个对象, 而其他面向对象的语言必须使用类进行实例化,所以下方代码中定义的对象timeTool
就已经是一个单例模式创建对象的实例(let
和const
不允许重复声明的特性,确保了timeTool
不能被重新覆盖)
let timeTool = { name: '处理时间工具库', getISODate: function() {}, getUTCDate: function() {} }
演示范例——通过定义类实现单例模式
class SingletonApple { constructor(name, creator, products) { this.name = name; this.creator = creator; this.products = products; } //静态方法 static getInstance(name, creator, products) { if(!this.instance) { this.instance = new SingletonApple(name, creator, products); } return this.instance; } } let appleCompany = SingletonApple.getInstance('苹果公司', '乔布斯', ['iPhone', 'iMac', 'iPad', 'iPod']); let copyApple = SingletonApple.getInstance('苹果公司', '阿辉', ['iPhone', 'iMac', 'iPad', 'iPod']) console.log(appleCompany === copyApple); //true
通过类的静态方法getInstance创建
实例,若是第一次创建对象实例则new一个对象实例(触发构造函数constructor),若检测到已进行过实例化,则返回已创建过的对象实例,不再重新创建实例。
应用场景——命名空间
命名空间:全局只暴露一个对象名,将变量作为该对象的属性,将方法作为该对象的方法,这样就能大大减少全局变量的个数,用来解决全局变量冲突的问题
//开发者A写了一大段js代码 let devA = { addNumber() { } } //开发者B开始写js代码 let devB = { add: '' } //A重新维护该js代码 devA.addNumber();
devA
和devB
就是两个命名空间,采用命名空间可以有效减少全局变量的数量,以此解决变量冲突的发生。
应用场景——管理模块
var devA = (function(){ //ajax模块 var ajax = { get: function(api, obj) {console.log('ajax get调用')}, post: function(api, obj) {} } //dom模块 var dom = { get: function() {}, create: function() {} } //event模块 var event = { add: function() {}, remove: function() {} } return { ajax: ajax, dom: dom, event: event } })()
上面的代码库中有ajax
,dom
和event
三个模块,用同一个命名空间devA
来管理。在进行相应操作的时候,只需要devA.ajax.get()
进行调用即可,这样可以让库的功能更加清晰。
项目实战范例——单例模式
范例功能:点击登录按钮后永远只返回一个登录框的实例。
实现思路:
- 给顶部导航模块的登录按钮注册点击事件
- 登录按钮点击后JS动态创建遮罩层和登陆弹框
- 遮罩层和登陆弹框插入到页面中
- 给登陆框中的关闭按钮注册事件, 用于关闭遮罩层和弹框
……
代码解析:
第一次点击登录按钮时,调用Login.getInstance()
实例化了一个登录框,在之后的点击中,不再重新创建新的登录框,只是移除掉"display: none"
这个样式来显示登录框,节省了内存开销。
1. 给页面添加顶部导航栏的HTML代码
<nav class="top-bar"> <div class="top-bar_left"> LTH BLOG </div> <div class="top-bar_right"> <div class="login-btn">登录</div> <div class="signin-btn">注册</div> </div> </nav>
2. 使用ES6的语法创建Login类
class Login { //构造器 constructor() { this.init(); } //初始化方法 init() { //新建div let mask = document.createElement('div'); //添加样式 mask.classList.add('mask-layer'); //添加模板字符串 mask.innerHTML = ` <div class="login-wrapper"> <div class="login-title"> <div class="title-text">登录框</div> <div class="close-btn">×</div> </div> <div class="username-input user-input"> <span class="login-text">用户名:</span> <input type="text"> </div> <div class="pwd-input user-input"> <span class="login-text">密码:</span> <input type="password"> </div> <div class="btn-wrapper"> <button class="confrim-btn">确定</button> <button class="clear-btn">清空</button> </div> </div> `; //插入元素 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; } }
3. 给登录按钮添加注册点击事件
//注册点击事件 Login.getLoginDom('.login-btn').addEventListener('click', () => { Login.getInstance(); })
更多设计模式详见——js设计模式【详解】总目录
https://blog.csdn.net/weixin_41192489/article/details/116154815