JS的几种设计模式

JS的几种简单设计模式

一、单例模式

我们都知道一个构造函数可以使用 new 关键字来创造出若干的实例。并且每个实例都是不相同的。都是不同的地址,比较两个生成的实例时比较的是存储地址,因此每个实例都有不同的存储空间,两者比较值为false

  function Person(){
            this.name='小明'
        }
        var p1=new Person()
        var p2=new Person()
        console.log(p1===p2)//false
单例模式介绍

使用构造函数实例化的时候,不管实例化多少回,都实例化出同一个对象

  • 一个构造函数一生只能 new 出一个对象
  • 当我们使用构造函数,每一次 new 出来的对象 属性/功能/方法 完全一样 的时候,我们把他设计成单例模式
核心代码如下
	  	//核心代码
	  	
	  	//一个构造函数
	   function Person() {
            this.name = 'Jack'
        }
		//全局变量  instance
        var instance = null

        function singleton() {
            if (instance === null) {
                instance = new Person()
            }
            return instance
        }
		
		//这是singleton函数第一次调用,这时全局变量instance=null
		//因此,instance=new Person()
		//全局变量instance的值是Person构造函数的一个实例(将其命名为 实例 001)
		//p1 为 实例001
        var p1=new singleton()
		
		//这是singleton函数第二次调用,这时全局变量instance 为 实例001
		//instance的值不为空 ,因此直接返回 全局变量instance原来的值(实例001)
		//p2 为 实例001
        var p2=new singleton()
		
		//p1 p2   的值都为实例001 得到的都是同一个对象 因此值为true 
        console.log(p1===p2)//true
		
		//之后不管再实例化多少回,过程都与第二次调用singleton一致
注意
  • var p1 = new singleton()这里的new关键字并不是实例一个对象,因为p1就是singleton函数的返回值instance构造函数如果返回一个复杂数据类型,那么最后得到的值就是那个复杂数据。这里的实例是构造函数Person的实例。用new关键字只是表示,我们每一次都实例了一个一模一样的对象。
将代码进行优化
  • 这一步我们需要了解JS的闭包以及对构造函数有较为深刻的理解。
    • 闭包的条件
      • 有一个 A 函数,再 A 函数内部返回一个 B 函数
      • 再 A 函数外部有变量引用这个 B 函数
      • B 函数内部访问着 A 函数内部的私有变量
      • 以上三个条件缺一不可
    • 形成闭包后,函数的执行空间不会被销毁,会一直存在。里面的变量也就不会被销毁。
    • 构造函数
      • 一个构造函数可以使用 new 关键字来创造出若干的实例。
      • 每一个实例都可以使用这个构造函数的属性和方法。

想了解JS的闭包,请点击这里
想对构造函数有更多的理解,请点击这里

//外部的Person并不是生成实例的构造函数  new Person  与  Person()得到的值都一样
//最后的结果写成 p = new Person() 是因为p 确实是构造函数的实例,
//不过这个构造函数是内部的Person 
//也是因为这样写代码更美观,更直观的看出是用一个构造函数创造相同的实例。

//外部的Person 是一个自执行函数   (function () {})()
// 执行后立马的到返回值 => 实例对象
 	var Person = (function () {
 	//这一部分就是我们的核心代码
      function Person() {}
      var instance = null
      //函数返回值  返回一个函数   函数的值是一个实例  
      return function () {
      //利用  三木运算符 简化代码
        return !instance ? instance = new Person() : instance
      }
    })()
	//自执行函数	的执行空间不会销毁,形成了一个闭包空间
	//执行空间不被销毁   那么instance  这个变量也就不会销毁
	//第一次执行得到的是一个Person的实例
	//之后的每一次执行,得到的都是第一次的那个实例

    p1=new Person()
    p2=new Person()

    console.log(p1==p2)//true
单例模式的简单应用场景
  • 网站的弹出层alert()或者右下角的广告页面
  • 一个网站不可能只弹出一次这个界面,不能每次弹出就创建一个
  • 每次弹出的都是之前创造好的,只是内容发生变化

二、组合模式

组合模式介绍
  • 举一个简单的例子,就像家里每个电器都有单独的开关,而组合模式就是设置一个总开关,这个开关可以控制家中所有电器的开关,这就是组合模式
实现思想
  • 先定义控制不同电器的开关,也就是一个个构造函数,每一个构造函数都有一个启动方法
    • 每一个构造函数的实例就是一个个电器开关
  • 再定义一个总开关一个构造函数,有一个方法可以启动所有构造函数的,
    • 这时,需要一个承载所有构造函数实例的数组
  • 总开关构造函数需要一个方法, 向数组里面添加内容
  • 总开关构造函数需要一个方法, 能把数组里面的所有内容启动了
组合模式代码
 // 一个构造函数的启动方式  控制玩的开关
    class Play {
      constructor () {}

      // 这个构造函数的启动方法
      init () {
        console.log('开始玩游戏')
        this.a()
        this.b()
        this.c()
      }

      a () {}

      b () {}

      c () {}
    }

    // 第二个构造函数  控制吃的开关
    class Eat {
      constructor () {}

      init () {
        console.log('去吃饭')
      }
    }
	//第三个构造函数  控制睡觉的开关
    class Sleep {
      constructor () {}

      init () {
        console.log('去睡觉')
      }
    }


    // 组合模式的代码   总开关
    class Compose {
      constructor () {
        // 用来承载每一个实例的数组
        this.composeArr = []
      }

      // 向数组里面添加内容
      add (instance) {
        this.composeArr.push(instance)
      }

      // 把数组里面的每一个内容调用了
      init () {
        console.log('总开关启动了')
        this.composeArr.forEach(item => item.init())
      }
    }

    // c 就是一个总开关
    var c = new Compose()
    // 每一次执行 add 方法就是向总开关上添加内容   使得总开关 可以控制 其他开关
    c.add(new Play())
    c.add(new Eat())
    c.add(new Sleep())

    // 只要我的总开关一启动
    // 里面的每一个构造函数就同时都启动了
    c.init()
    console.log(c)

在这里插入图片描述

组合模式的简单应用场景
  • 一个页面上有许多轮播图,当我切换其他标签页面时,这个页面上的轮播图还在运行,定时器没有停止
  • 设置离开这个页面时,定时器停止,轮播图停止运动
  • 这时就可以利用组合模式,设置一个总开关,控制所有轮播图的停止与运动。

三、观察者模式

观察者模式介绍
  • 观察者观察着被观察者只要被观察者数据发生变化,观察者就要做一些事情
  • 举个生动的例子,学生就是被观察者,老师就是观察者,只要学生上课状态不好,老师就会请家长。
观察者模式介绍实现思想
  • 学生刚开始需要一个认真学习的状态
  • 当学生认真学习的状态改变,在上课吃辣条时
  • 老师观察到这个变化,就会请家长
    • 学生需要一个状态改变的方法,一个添加观察者老师的方法,还要有一个通知观察者自己状态改变的方法,并且再状态改变后立即让观察者老师 叫家长。
观察者模式代码
 // 观察者  模式
        // => 让 观察者 看着 被观察者, 只要数据改变了
        // => 就让 观察者 做一些事情
        // observers  观察者



        // 被观察者
        class student {
            constructor(name) {
                this.name = name
                //最开始的状态
                this.state='学习'

                // 数组 中放入观察者  一旦被观察者状态发生改变  就让 观察者 做一些事情
                this.observers=[]
            }

            // 改变状态
            setState(state){
                this.state=state
                //只要状态发生变化 通知观察者  数据改变了
                this.noticy()
             
            }

            // 获取状态
            getState(){
                return this.state
            }

            // 添加  观察者
            attach(observer){
                this.observers.push(observer)
            } 

            // 通知  观察者 => 被观察者  状态改变了
            noticy(){
                this.observers.forEach(item=>item.jiaojiazhang(this.name,this.state))
            }

        }


        // 观察者
        class teacher{
            constructor(name){
                this.name=name
            }

            jiaojiazhang(name,state){
                console.log(`我是  ${this.name}  因为  ${name}${state},我要叫家长 `)
            }
        }



        // 被观察者
        let s=new student('小明')
        console.log(s)//student {name: "小明", state: "学习", observers: Array(0)}


        // 添加  观察者
        let ob1=new teacher('班主任')
        let ob2=new teacher('教导主任')

        console.log(ob1)
        console.log(ob2)

        s.attach(ob1)
        s.attach(ob2)


        //先获取最开始的状态
        console.log(s.getState())
		// 状态改变    改变后观察者 直接执行方法
        s.setState('上课吃辣条')
        //状态改变后的状态
        console.log(s.getState())

在这里插入图片描述
在这里插入图片描述

四、发布/订阅模式

发布/订阅模式介绍及实现思想
  • 分成三个状态
  1. 订阅
  2. 取消订阅
  3. 发布
  • 要实现订阅/取消订阅功能需要一个消息盒子{ }
    • 订阅就是往消息盒子里添加内容
    • 取消订阅就是删除消息盒子里的内容
    • 发布就是执行消息盒子里的内容
发布/订阅模式代码
//   准备几个 事件处理函数  要往消息盒子里添加的内容
        function handlerA(e) { console.log('我是事件处理函数 handlerA', e) }
        function handlerB(e) { console.log('我是事件处理函数 handlerB', e) }
        function handlerC(e) { console.log('我是事件处理函数 handlerC', e) }
        function handlerD(e) { console.log('我是事件处理函数 handlerD', e) }
        function handlerE(e) { console.log('我是事件处理函数 handlerE', e) }


        class Observer {
            constructor() {
                //  准备好的消息盒子
                this.message = {}
            }

            // 订阅的方法
            on(type, fn) {
                // type  要订阅 事件类型   fn  事件处理函数
                // 如果  message 盒子中  没有  type  就添加
                // 如果  message 盒子中  有  type  就添加 事件函数
                if (!this.message[type]) {
                    this.message[type] = []
                }
                this.message[type].push(fn)
            }

            // 取消订阅的方法
            off(type, fn) {
                // 删除消息盒子里面的某一个成员
                // type  要取消的 事件类型   fn  事件处理函数
                // 如果  message 盒子中  没有  type 不操作
                // 如果  message 盒子中  有  type  删除
                if (!this.message[type]) return
                this.message[type] = this.message[type].filter(item => item != fn)

            }

            // 发布的方法
            emit(type, ...arg) {
                // 没有 订阅过 type  不操作
                //      订阅过 type  执行

                if (!type) return
                // 自己组装一个事件对象
                let event = {
                    type: type,
                    data: arg
                }

                this.message[type].forEach(element => element(event))

            }

        }


        let ob = new Observer()

        console.log('订阅   发布')
        ob.on('click', handlerA)
        ob.on('click', handlerB)
        ob.on('click', handlerC)
        ob.on('abc', handlerD)
        ob.on('abc', handlerE)

        ob.emit('click', 100, true, { name: '小明' }, [1, 2, 3, 4])
        ob.emit('abc', 200, true, { name: '小红' }, [1, 2, 3, 4])

        console.log('')

        console.log('取消订阅  handlerA handlerD 然后  发布')
        ob.off('click', handlerA)
        ob.off('abc', handlerD)

        ob.emit('click', false,{name:'小明'})
        ob.emit('abc',false,{name:'小红'})

在这里插入图片描述

发布了9 篇原创文章 · 获赞 9 · 访问量 456

猜你喜欢

转载自blog.csdn.net/k464746/article/details/104477111