JavaScript-设计模式-单例模式

文章目录

        • 单例模式
          • 简介
          • 分类
            • 闭包(熟悉闭包的可以跳)
            • 懒汉式单例模式
            • 饿汉式单例模式
            • 登记式单例模式
            • 懒汉式单例的多线程问题

单例模式

简介
  1. 单例模式就是某一个类型在同一时间只能存在一个实例。
    例如,一个页面登录的弹窗就只能存在一个,存在多个…em不可想象。
    再例如,电脑上打开的任务管理器,一次只能打开一个。如果一次打开两个,两个显示CPU的占用还不一样,那就乱套了。
    所以针对某些只能或者只需要存在一个的实例,我们就可以使用单例模式。
分类
  1. 单例模式分为两种,懒汉式和饿汉式(之前还搜到有登记式,这个稍后再说)。
  2. 懒汉式单例:就是在定义实例的时候,不给他分配内存空间。(字面理解,他很懒,所以开始的时候什么也不干,只有在需要的时候才给他分配内存空间)
  3. 饿汉式单例:就是在定义实例的同时,给他分配内存空间。(字面理解,他很饿,很想要,所以一开始就把内存分好了)
  4. 两者的区别:
    • 内存占用方面:懒汉式单例由于在使用的时候才分配内存,所以在未使用之前占用的内存比饿汉式单例少。
    • 使用速度方面:懒汉式单例由于一开始是undefined,什么也没有,所以第一次使用的时候才开始创建实例,所以比饿汉式单例来得慢。
    • 可以想象一下,
      1. 如果你需要创建一个很复杂的实例(可能不会占用大量内存,但是需要复杂计算,或许还需要服务端的支持),那么肯定是刚开始就准备比较好,也就是饿汉式比较占优。如果到使用的时候再准备,那么显然会有一个比较长的等待期。
      2. 如果你的实例占用的内存非常大,你可能只是在整个网页结束的时候才使用一次,那么你肯定没必要一直让该实例保存在内存中,只需要在最后需要的时候再创建执行就OK。那么这种情况,就是懒汉式比较占优。(至于为什么实例会被一致保存在内存中,是因为单例的实现是基于闭包的,实例作为变量一直常驻内存不会被回收,具体可以看下面单例模式的实现)
      3. 所以实际使用的时候,需要考虑该实例的具体使用情况。
闭包(熟悉闭包的可以跳)
  1. 网上找了找:闭包是能够访问别的函数内部变量的函数。(就是在函数的外部访问该函数内部定义的变量)
    (感觉还是《JavaScript权威指南》里面关于闭包的定义比较准确)
  2. 简单理解:
  • 一般情况,一个函数执行完成,其内部的所有变量都会被销毁,也就是说其内部的变量占用的内存都会被回收。那么当然,我们也就不能在外部再去访问这个函数里面的变量了。

    	function test() {
    	  var a = 1
    	}
    	var b = a	// 肯定会报错,ReferenceError: a is not defined,因为此时a已经不存在了
    

    同时,我们还可以发现一个很有趣的问题,该错误是发生在编译阶段,还是发生在执行阶段呢?
    不妨加一行输出看一下。如果this is hello没有输出,说明编译阶段就没有通过,如果this is hello输出,可以说明边编译边执行。

    	function test() {
    	  var a = 1
    	}
    	console.log('this is hello')
    	var b = a
    

    去某乎找了找,发现

    先编译后执行叫 AOT(Ahead of time),边编译边执行叫JIT(Just in time),JavaScript v8引擎明确地说过是JIT。

    0.0
    我的运行环境是nodejs,其JavaScript引擎就是v8。

    言归正传,那么如何访问到a呢?只需要一直保持对a的引用,那么a就不会被销毁。(感觉可以参考一下js的垃圾回收的引用计数法,只要有变量在使用该值,那么该值就不会被垃圾回收机制回收)

    	function test() {
    	  var a = {
    	    name: '张三'
    	  }
    	  return function() {
    	    return a;
    	  }
    	}
    	var b = test()()	//	在test()执行结束之后,a仍然保存在内存中,可以通过返回的匿名函数去访问它
    	console.log(b)
    
懒汉式单例模式

只有在第一次使用的时候,给person分配了内存,并将其指定到了{name: name}对象。
最后一步,可以验证personA、personB、personC三者所指向的内存是一样的。

	// 懒汉式
	function lazySingleton() {
	  var person
	  return function(name) {
	    if (!person) {
	      person = {
	        name: name
	      }
	    }
	    return person
	  }
	}
	
	var personCreator = lazySingleton();
	var personA = personCreator('a'); console.log('personA:', personA) // personA: { name: 'a' }
	var personB = personCreator('c'); console.log('personB:', personB) // personB: { name: 'a' }
	var personC = personCreator('b'); console.log('personC:', personC) // personC: { name: 'a' }
	console.log(personA === personB, personB === personC) // true true
饿汉式单例模式

在定义的时候就直接给实例分配内存空间。
===判断出三个变量指向的内存是一样的。
之后修改name属性,发现三者name都修改了,也可以说明这三个变量指向的都是同一块内存空间。

	// 饿汉式
	var hungrySingleton = (function() {
	  var person = {
	    name: '张三', 
	    age: 20
	  }
	  return function() {
	    return {
	      getInfo: function() {
	        return person;
	      }
	    }
	  }
	})();
	
	var personA = hungrySingleton(); 
	var personB = hungrySingleton(); 
	var personC = hungrySingleton(); 
	var infoA = personA.getInfo();
	var infoB = personB.getInfo();
	var infoC = personC.getInfo();
	
	console.log(infoA); // { name: '张三', age: 20 }
	console.log(infoB); // { name: '张三', age: 20 }
	console.log(infoC); // { name: '张三', age: 20 }
	console.log(infoA === infoB, infoB === infoC); // true true
	
	infoA.name = '李四';
	
	console.log(infoA); // { name: '李四', age: 20 }
	console.log(infoB); // { name: '李四', age: 20 }
	console.log(infoC); // { name: '李四', age: 20 }
	console.log(infoA === infoB, infoB === infoC); // true true
登记式单例模式

登记式单例主要是为了解决懒汉式单例和饿汉式单例均不可继承的问题。

懒汉式单例的多线程问题

其实,如果细心的话,我们会发现懒汉式单例在多线程模式下存在问题。

  • 如果实例不存在(!person判定为真),A线程要去给person变量分配内存。
  • 恰好此时,B线程也运行到了if判断处,A线程还未给person分配完成(可以假设A运行到if结束,刚刚判定应该往赋值走,还没开始赋值),那么此时person仍然不存在,!person判定结果还是真,B线程也要去给person变量赋值。
  • 那么此时问题就出现了,A、B线程同时给变量person分配内存空间,最终结果就出现问题了。

其实JavaScript中这种情况比较少,JavaScript是单线程的,这种情况Java里面会有。
这是在参考的博文里的一段代码:
单例模式的懒汉式在多线程的问题

	public static SingleDemo getInstance(){
		synchronized (SingleDemo.class) {
		    if(singleDemo==null){
		        singleDemo = new SingleDemo();
		    }
		}
		return singleDemo;
    }

对于多线程的这种资源的访问问题,一般可以加锁。
这里使用了synchronized关键字,就是一次只能有一个线程去访问同步代码块中的代码。
就第一个线程来的时候,去判断一下singleDemo是否分配了内存,如果没有的话,就分配。在判断-分配流程执行完成之前,所有运行到此处的其他线程都在等待,所以就不会存在两个线程同事去给变量赋值的情况。

但是呢,这样又出现了一个问题,就是效率太低。每次运行到这里都只有一个线程能执行,别的线程都在等待。
但是其实没必要,只有第一次给该变量分配内存的时候才需要加锁,后面来的线程只需要知道该变量已经分配过内存了,就无需再等待。所以在外层再加一层空判断即可。

public static SingleDemo getInstance(){
        //二次判断提高效率
        if(singleDemo==null){
            synchronized (SingleDemo.class) {
                if(singleDemo==null){
                    singleDemo = new SingleDemo();
                }
            }
        }
        return singleDemo;
    }
    

你可能感兴趣的:(学习,JavaScript-设计模式,javascript)