摘要:单例模式主要是为了解决对象的创建问题。页面中只放一个按钮登录实现得到登录框元素绑定事件关闭弹框这里做登录点击页面中的按钮每次让登录框出现即可上面的代码根据单例模式的使用构造函数来实现的。
最近打算系统的学习 Javascript 设计模式,以便自己在开发中遇到问题可以按照设计模式提供的思路进行封装,这样可以提高开发效率并且可以预先规避很多未知的问题。
先从最基本的单例模式开始
什么是单例模式
单例模式,从名字拆分来看,单指的是一个,例是实例,意思是说多次通过某个类创造出来实例始终只返回同一个实例,它限制一个类只能有一个实例。单例模式主要是为了解决对象的创建问题。单例模式的特点:
1、一个类只有一个实例
2、对外提供唯一的访问接口
在一些以类为核心的语言中,例如java,每创建一个对象就必须先定义一个类,对象是从类创建而来。js是一门无类(class-free)的语言,在js中创建对象的方法非常简单,不需要先定义类即可创建对象。
在js中,单例模式是一种常见的模式,例如浏览器中提供的window对象,处理数字的Math对象。
单例模式的实现
1
对象字面量
在js中实现单例最简单的方式是创建对象字面量,字面量对象中可以包含多个属性和方法。
var mySingleton = {
attr1:1, attr2:2, method:function (){ console.log("method"); }
}
以上创建一个对象,放在全局中,就可以在任何地方访问,要访问对象中的属性和方法,必须通过mySingleton这个对象,也就是说提供了唯一一个访问接口。
2
使用闭包私有化
扩展mySingleton对象,添加私有的属性和方法,使用闭包的形式在其内部封装变量和函数声明,只暴露公共成员和方法。
var mySingleton = (function (){
//私有变量
var privateVal = "我是私有变量";
//私有函数
function privateFunc(){
console.log("我是私有函数");
}
return { attr1:1, attr2:2, method:function (){ console.log("method"); privateFunc(); } }
})()
把privateVal和privateVal被封装在闭包产生的作用域中,外界访问不到这两个变量,这避免了对全局命名污染。
3
惰性单例
无论使用对象字面量或者闭包私有化的方式创建单例,都是在脚本一加载就被创建。有时候页面可能不会用到这个单例对象,这样就会造成资源浪费。对于这种情况,最佳处理方式是使用惰性单例,也就是在需要这个单例对象时再初始化。
var mySingleton = (function (){
function init(){ //私有变量 var privateVal = "我是私有变量"; //私有函数 function privateFunc(){ console.log("我是私有函数"); } return { attr1:1, attr2:2, method(){ console.log("method"); privateFunc(); } } } //用来保存创建的单例对象 var instance = null; return { getInstance (){ //instance没有存值,就执行函数得到对象 if(!instance){ instance = init(); } //instance存了值,就返回这个对象 return instance; } }
})();
//得到单例对象
var singletonObj1 = mySingleton.getInstance();
var singletonObj2 = mySingleton.getInstance();
console.log( singletonObj1 === singletonObj2 ); //true
程序执行后,将创建单例对象的代码封装到init函数中,只暴露了获取单例对象的函数getInstance。当有需要用到时,通过调用函数mySingleton.getInstance()得到单例对象,同时使用instance将对象缓存起来,再次调用mySingleton.getInstance()后得到的是同一个对象,这样通过一个函数不会创建多个对象,起到节省资源的目的。
4
使用构造函数
可以使用构造函数的方式,创造单例对象:
function mySingleton(){
//如果缓存了实例,则直接返回 if (mySingleton.instance) { return mySingleton.instance; } //当第一次实例化时,先缓存实例 mySingleton.instance = this;
}
mySingleton.prototype.otherFunc = function (){
console.log("原型上其他方法");
}
var p1 = new mySingleton();
var p2 = new mySingleton();
console.log( p1 === p2 ); //true
当第一次使用new调用函数创建实例时,通过函数的静态属性mySingleton.instance把实例缓存起来,在第二次用new调用函数,判断实例已经缓存过了,直接返回,那么第一次得到的实例p1和第二次得到的实例p2是同一个对象。这样符合单例模式的特点:一个类只能有一个实例。
这样做有一个问题,暴露了可以访问缓存实例的属性mySingleton.instance,这个属性的值可以被改变:
var p1 = new mySingleton();
//改变mySingleton.instance的值
//mySingleton.instance = null;
//或者
mySingleton.instance = {};
var p2 = new mySingleton();
console.log( p1 === p2 ); //false
改变了mySingleton.instance值后,再通过new调用构造函数创建实例时,又会重新创建新的对象,那么p1和p2就不是同一个对象,违反了单例模式一个类只能有一个实例。
闭包中的实例
不使用函数的静态属性缓存实例,而是重新改写构造函数:
function mySingleton(){
//缓存当前实例 var instance = this; //执行完成后改写构造函数 mySingleton = function (){ return instance; } //其他的代码 instance.userName = "abc";
}
mySingleton.prototype.otherFunc = function (){
console.log("原型上其他方法");
}
var p1 = new mySingleton();
var p2 = new mySingleton();
console.log( p1 === p2 ); //true
第一次使用new调用函数创建实例后,在函数中创建instance用来缓存实例,把mySingleton改写为另一个函数。如果再次使用new调用函数后,利用闭包的特性,返回了缓存的对象,所以p1和p2是同一个对象。
这样虽然也可以保证一个类只返回一个实例,但注意,第二次再次使用new调用的构造函数是匿名函数,因为mySingleton已经被改写:
//第二次new mySingleton()时这个匿名函数才是真正的构造函数
mySingleton = function (){
return instance;
}
再次给原mySingleton.prototype上添加是属性,实际上这是给匿名函数的原型添加了属性:
var p1 = new mySingleton();
//再次给mySingleton的原型上添加属性
mySingleton.prototype.addAttr = "我是新添加的属性";
var p2 = new mySingleton();
console.log(p2.addAttr); //undefined
对象p2访问属性addAttr并没有找到。通过一个构造函数构造出来的实例并不能访问原型上的方法或属性,这是一种错误的做法,还需要继续改进。
function mySingleton(){
var instance; //改写构造函数 mySingleton = function (){ return instance; } //把改写后构造函数的原型指向this mySingleton.prototype = this; //constructor改写为改写后的构造函数 mySingleton.prototype.constructor = mySingleton; //得到改写后构造函数创建的实例 instance = new mySingleton; //其他的代码 instance.userName = "abc"; //显示的返回改写后构造函数创建的实例 return instance;
}
mySingleton.prototype.otherFunc = function (){
console.log("原型上其他方法");
}
var p1 = new mySingleton();
//再次给mySingleton的原型上添加属性
mySingleton.prototype.addAttr = "我是新添加的属性";
var p2 = new mySingleton();
console.log(p2.addAttr); //"我是新添加的属性"
console.log( p1 === p2 ); //true
以上代码主要做了以下几件事:
数字列表改写 mySingleton 函数为匿名函数
改写 mySingleton 的原型为第一次通过 new 创建实例
因为改写了 prototype,要把 constructor 指回mySingleton
显式返回通过改写后 mySingleton 构造函数构造出的实例
无论使用多少次new调用mySingleton这个构造函数,都返回同一个对象,并且这些对象都共享同一个原型。
实践单例模式
1
使用命名空间
根据上述,在js中创建一个对象就是一个单例,把一类的方法和属性放在对象中,都通过提供的全局对象访问。
var mySingleton = {
attr1:1, attr2:2, method:function (){ console.log("method"); }
}
这样的方式耦合度极高,例如:要给这个对象添加属性:
mySingleton.width = 1000; //添加一个属性
//添加一个方法会覆盖原有的方法
mySingleton.method = function(){};
如果在多人协作中,这样添加属性的方式经常出现被覆盖的危险,可以采用命名空间的方式解决。
/A同学
mySingleton.a = {};
mySingleton.a.method = function(){}
//访问
mySingleton.a.method();
//B同学
mySingleton.b = {};
mySingleton.b.method = function(){}
//访问
mySingleton.b.method();
都在自己的命名空间中,覆盖的几率会很小。
可以封装一个动态创建命名空间的通用方法,这样在需要独立的命名空间时只需要调用函数即可。
mySingleton.namespace = function(name){
var arr = name.split("."); //存一下对象 var currentObj = mySingleton; for( var i = 0; i < arr.length; i++ ){ //如果对象中不存在,则赋值添加属性 if(!currentObj[arr[i]]){ currentObj[arr[i]] = {}; } //把变量重新赋值,便于循环继续创建命名空间 currentObj = currentObj[arr[i]] }
}
//创建命名空间
mySingleton.namespace("bom");
mySingleton.namespace("dom.style");
以上调用函数生成命名空间的方式代码等价于:
mySingleton.bom = {};
mySingleton.dom = {};
mySingleton.dom.style = {};
2
单例登录框
使用面向对象实现一个登录框,在点击登录按钮后登录框被append到页面中,点击关闭就将登录框从页面中remove掉,这样频繁的操作DOM不合理也不是必要的。
只需要在点击关闭时隐藏登录框,再次点击按钮后,只需要show出来即可。
页面中只放一个按钮:
js实现:
function Login(){
var instance; Login = function(){ return install; } Login.prototype = this; install = new Login; install.init(); return install;
}
Login.prototype.init = function(){
//得到登录框元素 this.Login = this.createHtml(); document.body.appendChild(this.Login); //绑定事件 this.addEvent();
}
Login.prototype.createHtml = function(){
var LoginDiv = document.createElement("div"); LoginDiv.className = "box"; var html = `这里做登录
` LoginDiv.innerHTML = html; return LoginDiv;
}
Login.prototype.addEvent = function(){
var close = this.Login.querySelector(".close"); var _this = this; close.addEventListener("click",function(){ _this.Login.style.display = "none"; })
}
Login.prototype.show = function(){
this.Login.style.display = "block";
}
//点击页面中的按钮
var loginBtn = document.querySelector("#loginBtn");
loginBtn.onclick = function(){
var login = new Login(); //每次让登录框出现即可 login.show();
}
上面的代码根据单例模式的使用构造函数来实现的。这样在一开始生成了一个对象,之后使用的都是同一个对象。
总结
单例模式是一种非常实用的模式,特别是懒性单例技术,在合适时候创建对象,并且只创建唯一一个,这样减少不必要的内存消耗。
正在学习设计模式,不正确的地方欢迎拍砖指正。
公众号ID:Miaovclass
关注妙味订阅号:“妙味前端”,为您带来优质前端技术干货;
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/98680.html
摘要:单例模式主要是为了解决对象的创建问题。页面中只放一个按钮登录实现得到登录框元素绑定事件关闭弹框这里做登录点击页面中的按钮每次让登录框出现即可上面的代码根据单例模式的使用构造函数来实现的。 最近打算系统的学习javascript设计模式,以便自己在开发中遇到问题可以按照设计模式提供的思路进行封装,这样可以提高开发效率并且可以预先规避很多未知的问题。 先从最基本的单例模式开始。 什么是单例...
摘要:此时我们创建的对象内保存静态变量通过取值器访问,最后将这个对象作为一个单例放在全局空间里面作为静态变量单例对象供他人使用。 单例模式 又被称为单体模式,是只允许实例化一次的对象类。有时我们也用一个对象来规划一个命名空间,井井有条的管理对象上面的属性和方法。 传统的面向对象语言中单例模式的实现,均是单例对象从类中创建而来,在以类为中心的语言中,这是很常见的做法。如果需要某个对象,就必须先...
摘要:单例模式单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。透明的单例模式利用一个形成一个闭包,在里边通过变量来记录实例,并返回构造函数。 单例模式 单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。 ———来自维基百科 一个很典型的应用是在点击登录按钮,弹出登录浮窗,不论...
摘要:在面向对象的语言中,比如,等,单例模式通常是定义类时将构造函数设为,保证对象不能在外部被出来,同时给类定义一个静态的方法,用来获取或者创建这个唯一的实例。 万事开头难,作为正经历菜鸟赛季的前端player,已经忘记第一次告诉自己要写一些东西出来是多久以的事情了。。。如果,你也和我一样,那就像我一样,从现在开始,从看到这篇文章开始,打开电脑,敲下你的第一篇文章(或者任何形式的文字)吧。 ...
阅读 2083·2023-04-25 17:23
阅读 2899·2021-11-17 09:33
阅读 2482·2021-08-21 14:09
阅读 3529·2019-08-30 15:56
阅读 2576·2019-08-30 15:54
阅读 1601·2019-08-30 15:53
阅读 2091·2019-08-29 13:53
阅读 1111·2019-08-29 12:31