最近打算系统的学习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();
}
}
})()

privateValprivateVal被封装在闭包产生的作用域中,外界访问不到这两个变量,这避免了对全局命名污染。

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调用构造函数创建实例时,又会重新创建新的对象,那么p1p2就不是同一个对象,违反了单例模式一个类只能有一个实例。

闭包中的实例

不使用函数的静态属性缓存实例,而是重新改写构造函数:

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调用函数后,利用闭包的特性,返回了缓存的对象,所以p1p2是同一个对象。

这样虽然也可以保证一个类只返回一个实例,但注意,第二次再次使用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

以上代码主要做了以下几件事:

  1. 改写mySingleton函数为匿名函数
  2. 改写mySingleton的原型为第一次通过new创建的实例
  3. 因为改写了prototype,要把constructor指回mySingleton
  4. 显式返回通过改写后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出来即可。

页面中只放一个按钮:

<input type="button" value="登录" id="loginBtn" />

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 = `<input type="button" value="关闭弹框" class="close" /><p>这里做登录</p>` 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();
}

上面的代码根据单例模式的使用构造函数来实现的。这样在一开始生成了一个对象,之后使用的都是同一个对象。

总结

单例模式是一种非常实用的模式,特别是懒性单例技术,在合适时候创建对象,并且只创建唯一一个,这样减少不必要的内存消耗。


正在学习设计模式,不正确的地方欢迎拍砖指正。

探索Javascript设计模式---单例模式的更多相关文章

  1. JavaScript设计模式-单例模式、模块模式(转载 学习中。。。。)

    (转载地址:http://technicolor.iteye.com/blog/1409656) 之前在<JavaScript小特性-面向对象>里面介绍过JavaScript面向对象的特性 ...

  2. JavaScript设计模式 - 单例模式

    单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 一.实现一个标准的单例模式,用一个变量来标志当前是否已经为某个类创建过对象, 如果是,则在下一次获取该对象实例时,直接返回之前创建的对 ...

  3. javascript设计模式——单例模式

    前面的话 单例模式是指保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式是一种常用的模式,有一些对象往往只需要一个,比如线程池.全局缓存.浏览器中的window对象等.在javaScri ...

  4. [读书笔记] JavaScript设计模式: 单例模式

    单例模式:保证一个类只有一个实例,并提供一个可以访问它的全局访问点. 一种简单.方便的写法就是用一个变量来标识当前类是否已经创建过对象,如果有,则返回已经创建好的对象,否则创建一个新对象,并将其返回. ...

  5. javascript 设计模式-----单例模式

    单例模式的意思是只需要实例化某个类一次,它的方法也比较简单,通过判断某个类是否已经被实例化了,再返回该值.可以通过各种方法来实现单例模式,下面我们采取以下这种实现方式: var single = (f ...

  6. javascript设计模式--单例模式(Singleton)

    <!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8& ...

  7. JavaScript设计模式—单例模式

    单例模式介绍 系统中被唯一使用的,一个类只有一个实例 单例模式的思路是: 一个类能返回一个对象的引用(并且永远是同一个)和一个获得该实例的方法(静态方法,通常使用 getInstance 名称). 那 ...

  8. 单例模式 | 程序员都想要探索的 Javascript 设计模式

    最近打算系统的学习 Javascript 设计模式,以便自己在开发中遇到问题可以按照设计模式提供的思路进行封装,这样可以提高开发效率并且可以预先规避很多未知的问题. 先从最基本的单例模式开始 什么是单 ...

  9. JavaScript设计模式与开发实践 - 单例模式

    引言 本文摘自<JavaScript设计模式与开发实践> 在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返 ...

随机推荐

  1. libusb 开发者指南-牛胜超(转)

    源:libusb 开发者指南 libusb Developers Guidelibusb 开发者指南 原作者:Johannes Erdfelt翻译者:牛胜超 Table of Contents目录 P ...

  2. iOS开发笔记 - 界面调试神器Reveal

    http://blog.csdn.net/jackfrued/article/details/50934092     Reveal是iOS开发工具中的神器之一,它能够在应用程序运行过程中调试应用程序 ...

  3. leetcode day7

    这道题弄的心好累..  [Reverse Linked List]206 描述: Reverse a singly linked list. click to show more hints. Hin ...

  4. python3中bytes与string的互相转换

    首先来设置一个原始的字符串, Python 3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)] on win32 Ty ...

  5. ExcelUtil

    package cn.com.jansh.core.util; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi ...

  6. YII 1.0 隐藏单入口index.php 设置路由与伪静态

    隐藏 index.php 保证apache配置文件httpd.conf里的LoadModulerewrite_module modules/mod_rewrite.so开启(去掉#)将相对应目录的Al ...

  7. iOS 项目上线流程

    一.证书的导出      1.1 前期工作        首先你需要有一个苹果的开发者帐号,一个Mac系统.        如果没有帐号可以在打开http://developer.apple.com/ ...

  8. 把记事本文件固定在Win8的开始屏幕

    1.创建该文件的桌面快捷方式: 2.将快捷方式拷贝至开始菜单目录,在开始屏幕的查看全部中可以看见该文件快捷: 3.在查看全部中右键点击该快捷,选择固定在开始屏幕:

  9. node源码详解(四) —— js代码如何调用C++的函数

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载保留声明头部与原文链接https://luzeshu.com/blog/nodesource4 本博客同步在https://cnodejs.o ...

  10. 【Scala】Scala之Control Structures

    一.前言 前面学习了Scala的Numbers,接着学习Scala的Control Structures(控制结构). 二.Control Structures Scala中的控制结构与Java中的颇 ...