js设计模式

正文

1. 单例模式(SingleTon)

定义

也叫单例模式,核心思想是确保一个类只对应一个实例
虽然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

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

2. 借助闭包

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

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 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值