资讯专栏INFORMATION COLUMN

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

AlexTuan / 2161人阅读

摘要:虽然是弱类型的语言,但是也有构造函数和实例。也就是说,我们只在第一次调用构造函数时创建新对象,之后调用返回时返回该对象即可。而我不认为这是一个单例模式的原因如下我觉得既然两次调用同一个构造函数,返回的不是同一个对象,那不就不能成为单例模式。

写在前面

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

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

如果对js的类式继承和闭包不太熟练的建议先阅读相关内容,比如我前面写过的js继承(主要看到原型链继承部分就好)和js闭包,,

知识密度较大,建议边思考,顺便跑以下相关的代码(如果碰到代码有问题的欢迎指出),中途注意休息

正文 定义

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

我觉得既然两次调用同一个构造函数,返回的不是同一个对象,那不就不能成为单例模式。当然,这一部分是我个人的看法,读者朋友还是要注意区分。

小结

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95699.html

相关文章

  • js常用设计模式实现(单例模式

    摘要:什么是设计模式设计模式是一种能够被反复使用,符合面向对象特性的代码设计经验的总结,合理的使用设计模式能够让你得代码更容易维护和可靠设计模式的类型共分为创建型模式,结构型模式,行为型模式三种创建型模式创建型模式是对一个类的实例化过程进行了抽象 什么是设计模式 设计模式是一种能够被反复使用,符合面向对象特性的代码设计经验的总结,合理的使用设计模式能够让你得代码更容易维护和可靠设计模式的类型...

    EscapedDog 评论0 收藏0
  • 从ES6重新认识JavaScript设计模式(): 单例模式

    摘要:什么是单例模式单例模式是一种十分常用但却相对而言比较简单的单例模式。对象就是单例模式的体现。总结单例模式虽然简单,但是在项目中的应用场景却是相当多的,单例模式的核心是确保只有一个实例,并提供全局访问。 1. 什么是单例模式? 单例模式是一种十分常用但却相对而言比较简单的单例模式。它是指在一个类只能有一个实例,即使多次实例化该类,也只返回第一次实例化后的实例对象。单例模式不仅能减少不必要...

    G9YH 评论0 收藏0
  • JS 单例模式

    摘要:但是如何在对构造函数使用操作符创建多个对象的时候仅获取一个单例对象呢。单例的实例引用单例构造函数单例私有属性和方法暴露出来的对象改进之前在构造函数中重写自身会丢失所有在初始定义和重定义之间添加到其中的属性。 1. 单例模式 单例模式 (Singleton) 的实现在于保证一个特定类只有一个实例,第二次使用同一个类创建新对象的时候,应该得到与第一次创建对象完全相同的对象。当创建一个新对象...

    姘存按 评论0 收藏0
  • JS设计模式-单例模式

    摘要:单例模式是一个用来划分命名空间并将一批属性和方法组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次。 单例模式是一个用来划分命名空间并将一批属性和方法组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次。 原文链接 单例模式优点 划分命名空间,减少全局变量 组织代码为一体,便于阅读维护 并非所有的对象字面量都是单例,比如模拟数据 基本结构: let Cat = {...

    sPeng 评论0 收藏0
  • JS设计模式单例模式

    摘要:以上,吐槽完毕设计模式主要分为三大类创建型模式结构型模式行为模式。单例模式所谓单例模式,是指仅实例化该类一次,该实例提供一个众所周知的全局访问点。单例模式暂时这么些,如果后续了解更多会默默补充上来,下一次会分享工厂模式。 初入js坑时,满脸懵逼的我认为设计模式就该是后端头疼的,对,头疼,盖因粗略扫描下来也就十几二十种吧,彼时颇有种隔岸观火看到你过得不好我也就安心的自得。染鹅,打脸啪啪的...

    qpal 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<