|
| 1 | +### 一、单体模式(Singleton) |
| 2 | +#### 1.概念介绍 |
| 3 | +**单体模式**的思想在于**保证一个特定类仅有一个实例**,即不管使用这个类创建多少个新对象,都会得到**与第一次创建的对象完全相同**。 |
| 4 | + |
| 5 | +它让我们能将代码组织成一个逻辑单元,并可以通过单一变量进行访问。 |
| 6 | + |
| 7 | +单体模式有以下优点: |
| 8 | +* 用来划分命名空间,减少全局变量数量。 |
| 9 | +* 使代码组织的更一致,提高代码阅读性和维护性。 |
| 10 | +* 只能被实例化一次。 |
| 11 | + |
| 12 | +但在JavaScript中没有类,只有对象。当我们创建一个新对象,它都是个新的单体,因为JavaScript中永远不会有完全相等的对象,除非它们是同一个对象。 |
| 13 | +因此,**我们每次使用对象字面量创建对象的时候,实际上就是在创建一个单例**。 |
| 14 | +```js |
| 15 | +let a1 = { name : 'leo' }; |
| 16 | +let a2 = { name : 'leo' }; |
| 17 | +a1 === a2; // false |
| 18 | +a1 == a2; // false |
| 19 | +``` |
| 20 | + |
| 21 | +这里需要注意,单体模式有个条件,是该对象能被实例化,比如下面这样就不是单体模式,因为它不能被实例化: |
| 22 | +```js |
| 23 | +let a1 = { |
| 24 | + b1: 1, b2: 2, |
| 25 | + m1: function(){ |
| 26 | + return this.b1; |
| 27 | + }, |
| 28 | + m2: function(){ |
| 29 | + return this.b2; |
| 30 | + } |
| 31 | +} |
| 32 | +new a1(); // Uncaught TypeError: a1 is not a constructor |
| 33 | +``` |
| 34 | +下面展示一个单体模式的基本结构: |
| 35 | +```js |
| 36 | +let Singleton = function (name){ |
| 37 | + this.name = name; |
| 38 | + this.obj = null; |
| 39 | +} |
| 40 | +Singleton.prototype.getName = function(){ |
| 41 | + return this.name; |
| 42 | +} |
| 43 | +function getObj(name){ |
| 44 | + return this.obj || (this.obj = new Singleton(name)); |
| 45 | +} |
| 46 | +let g1 = getObj('leo'); |
| 47 | +let g2 = getObj('pingan'); |
| 48 | +g1 === g2; // true |
| 49 | +g1 == g2; // true |
| 50 | +g1.getName(); // 'leo' |
| 51 | +g2.getName(); // 'leo' |
| 52 | +``` |
| 53 | +从这里可以看出,单体模式只能实例化一次,后面再调用的话,都是使用第一次实例化的结果。 |
| 54 | + |
| 55 | +#### 2.应用场景 |
| 56 | +单例模式只允许实例化一次,能提高对象访问速度并且节约内存,通常被用于下面场景: |
| 57 | +* 需要频繁创建再销毁的对象,或频繁使用的对象:如:弹窗,文件; |
| 58 | +* 常用的工具类对象; |
| 59 | +* 常用的资源消耗大的对象; |
| 60 | + |
| 61 | +#### 3.实现弹框案例 |
| 62 | +这里我们要用单体模式,创建一个弹框,大概需要实现:元素值创建一次,使用的时候直接调用。 |
| 63 | +因此我们这么做: |
| 64 | +```js |
| 65 | +let create = (() => { |
| 66 | + let div; |
| 67 | + return () => { |
| 68 | + if(!div){ |
| 69 | + div = document.createElement('div'); |
| 70 | + div.innderHTML = '我是leo创建的弹框'; |
| 71 | + div.style.display = 'none'; |
| 72 | + div.setAttribute("id", "leo"); |
| 73 | + document.body.appendChild(div); |
| 74 | + } |
| 75 | + return div; |
| 76 | + } |
| 77 | +})(); |
| 78 | +// 触发事件 |
| 79 | +document.getElementById('otherBtn').onclick = () => { |
| 80 | + let first = create(); |
| 81 | + first.style.display = 'block'; |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +#### 4.使用new操作符 |
| 86 | +由于JavaScript中没有类,但JavaScript有`new`语法来用构造函数创建对象,并可以使用这种方法实现单体模式。 |
| 87 | +当使用同一个构造函数以`new`操作符创建多个对象,获得的是指向完全相同的对象的新指针。 |
| 88 | + |
| 89 | +通常我们使用`new`操作符创建单体模式的三种选择,让构造函数总返回最初的对象: |
| 90 | +* 使用全局对象来存储该实例(不推荐,容易全局污染)。 |
| 91 | +* 使用静态属性存储该实例,无法保证该静态属性的私有性。 |
| 92 | +```js |
| 93 | +function Leo(name){ |
| 94 | + if(typeof Leo.obj === 'object'){ |
| 95 | + return Leo.obj; |
| 96 | + } |
| 97 | + this.name = name; |
| 98 | + Leo.obj = this; |
| 99 | + return this; |
| 100 | +} |
| 101 | +let a1 = new Leo('leo'); |
| 102 | +let a2 = new Leo('pingan'); |
| 103 | +a1 === a2 ; // true |
| 104 | +a1 == a2 ; // true |
| 105 | +``` |
| 106 | +唯一的缺点就是`obj`属性是公开的,容易被修改。 |
| 107 | + |
| 108 | +* 使用闭包将该实例包裹,保证实例是私有性并不会被外界修改。 |
| 109 | + |
| 110 | +我们这通过重写上面的方法,加入闭包: |
| 111 | +```js |
| 112 | +function Leo(name){ |
| 113 | + let obj; |
| 114 | + this.name = name; |
| 115 | + obj = this; // 1.存储第一次创建的对象 |
| 116 | + Leo = function(){ // 2.修改原来的构造函数 |
| 117 | + return obj; |
| 118 | + } |
| 119 | +} |
| 120 | +let a1 = new Leo('leo'); |
| 121 | +let a2 = new Leo('pingan'); |
| 122 | +a1 === a2 ; // true |
| 123 | +a1 == a2 ; // true |
| 124 | +``` |
| 125 | +当我们第一次调用构造函数,像往常一样返回this,而后面再调用的话,都将重写构造函数,并访问私有变量`obj`并返回。 |
| 126 | + |
| 127 | + |
| 128 | +### 参考资料 |
| 129 | +1. 《JavaScript Patterns》 |
0 commit comments