js设计模式(一)-单例模式

写在前面

(度过一阵的繁忙期,又能够愉快的开始学习新知识了,一年来技术栈切来切去,却总以为js都还没学完-_-)segmentfault

本文主要围绕js的设计模式进行展开,对每一个设计模式从特征,原理和实现方式几个方面进行说明。因为内容较长,因此拆分红多篇文章。若是有不对的地方欢迎指出,阅读前请注意几点:设计模式

  1. 若是对js的类式继承和闭包不太熟练的建议先阅读相关内容,好比我前面写过的js继承(主要看到原型链继承部分就好)js闭包,,
  2. 知识密度较大,建议边思考,顺便跑如下相关的代码(若是碰到代码有问题的欢迎指出),中途注意休息

正文

定义

也叫单体模式,核心思想是确保一个类只对应一个实例
虽然js是弱类型的语言,可是js也有构造函数和实例。因此这里能够理解为确保屡次构造函数时,都返回同一个实例缓存

实现

根据定义,咱们须要实现一个构造函数,而且知足如下条件:闭包

function A(){
    //须要实现的函数内容
}
var a1 = new A() 
var b1 = new A()
a1 ==== b1 //true

在前面咱们说到了构造函数和实例,而且也知道了引用类型的值赋值的时候存放的实际是变量的地址指针,因此要实现这个构造函数的核心思路是:每次调用构造函数时,返回指向同一个对象的指针。 也就是说,咱们只在第一次调用构造函数时建立新对象,以后调用返回时返回该对象便可。因此重点变成了--如何缓存初次建立的变量对象。 函数

首先先排除全局变量,由于通常状况下须要保证全局环境的纯净,其次全局变量容易被改写,出现意外状况。因此采用如下2种方案来实现缓存。学习

1. 使用构造函数的静态属性

由于构造函数自己也是对象,能够拥有静态属性。因此能够这样实现:测试

function A(name){
    // 若是已存在对应的实例
   if(typeof A.instance === 'object'){
       return A.instance
   }
   //不然正常建立实例
   this.name = name
   
   // 缓存
   A.instance =this
   return this
}
var a1 = new A() 
var a2= new A()
console.log(a1 === a2)//true

这种方法的缺点在于静态属性是可以被人为重写的,不过不会像全局变量那样被无心修改。this

2. 借助闭包

经过闭包的方式来实现的核心思路是,当对象第一次被建立之后,重写构造函数,在重写后的构造函数里面访问私有变量。prototype

function A(name){
  var instance = this
  this.name = name
  //重写构造函数
  A = function (){
      return instance
  }
}
var a1 = new A() 
var a2= new A()
console.log(a1 === a2)//true

到这里咱们其实已经实现了最核心的步骤,可是这样的实现存在问题,若是看过原型链继承的小伙伴会注意到,若是咱们在第一次调用构造函数以后,因为构造函数被重写,那么在以后添加属性和方法到A的原型上,就会丢失。好比:设计

function A(name){
  var instance = this
  this.name = name
  //重写构造函数
  A = function (){
      return instance
  }
}
A.prototype.pro1 = "from protptype1"

var a1 = new A() 
A.prototype.pro2 = "from protptype2"
var a2= new A()

console.log(a1.pro1)//from protptype1
console.log(a1.pro2)//underfined
console.log(a2.pro1)//from protptype1
console.log(a2.pro2)//underfined

重写构造函数以后,,实际上原先的A指针对应的函数实际上还在内存中(由于instance变量还在被引用着,这里的内容若是忘记了请看闭包),可是此时A指针已经指向了一个新的函数了,能够简单测试下:

console.log(a1.constructor ==== A)//false

因此接下来咱们应该解决这个问题,根据上文可知,咱们的重点是,调整原型实例之间的关系,因此应该这样实现(这一块忘记的仍是建议回头看看js继承里面的那张函数、原型、实例之间的关系图点击直达):

function A(name){
  var instance = this
  this.name = name
 
  //重写构造函数
  A = function (){
      return instance
  }
  
  // 第一种写法,这里实际上实现了一次原型链继承,若是不想这样实现,也能够直接指向原来的原型
  A.prototype = this
  // 第二种写法,直接指向旧的原型
  A.prototype = this.constructor.prototype
  
  instance = new A()
  
  // 调整构造函数指针,这里实际上实现了一次原型链继承,若是不想这样实现,也能够直接指向原来的原型
  instance.constructor = A
  
  return instance
}
A.prototype.pro1 = "from protptype1"

var a1 = new A() 
A.prototype.pro2 = "from protptype2"
var a2= new A()

console.log(a1.pro1)//from protptype1
console.log(a1.pro2)//from protptype2
console.log(a2.pro1)//from protptype1
console.log(a2.pro2)//from protptype2

如今一切就正常了。还有一种方式,是利用当即执行函数来保持私有变量,(当即执行函数的内容请看《详解js中的函数部分》)原理也是闭包:

var A;
(function(name){
    var instance;
    A = function(name){
        if(instance){
            return instance
        }
        
        //赋值给私有变量
        instance = this
        
        //自身属性
        this.name = name
    }
}());
A.prototype.pro1 = "from protptype1"

var a1 = new A('a1') 
A.prototype.pro2 = "from protptype2"
var a2 = new A('a2')

console.log(a1.name)
console.log(a1.pro1)//from protptype1
console.log(a1.pro2)//from protptype2
console.log(a2.pro1)//from protptype1
console.log(a2.pro2)//from protptype2

简单说明一下上面的内容,首先利用在当即执行函数中保存一个私有变量instance,初次执行以后,第一次调用new A()以后,生成一个对象并让instance指向该对象,从第二次开始,调用new A(),都只返回这个对象,

*特殊状况

不少地方会提到,使用字面量直接建立一个对象也是一个单例模式的实例。这个说法我我的以为并不够严格,和同事探讨以后以为多是这样(若是有有其余看法的小伙伴欢迎指出):使用字面量写法的时候,实际上至关于使用原生的Object函数new了一个对象,而后存储到内存里,以后咱们每次使用对应的指针去读取时,读到的都是这个对象。而我不认为这是一个单例模式的缘由以下:

var obj1 = new Object({
  name:111
})

var obj2 = new Object({
  name:111
})

console.log(obj1===obj2)//false

我以为既然两次调用同一个构造函数,返回的不是同一个对象,那不就不能成为单例模式。固然,这一部分是我我的的见解,读者朋友仍是要注意区分。

小结

单例模式先说到这里,后面会陆续补充其余的设计模式。
感谢以前的热心读者,尤为是为我指出错误的小伙伴。而后依然是每次都同样的结尾,若是内容有错误的地方欢迎指出;若是对你有帮助,欢迎点赞和收藏,转载请征得赞成后著明出处,若是有问题也欢迎私信交流,主页添加了邮箱地址~溜了

相关文章
相关标签/搜索