JS常见设计模式

本文介绍了JavaScript中的设计模式,包括代理模式、单例模式、工厂模式、观察者模式和发布订阅模式。代理模式用于控制对象访问,例如在JavaScript中实现事件代理以优化性能。单例模式确保一个类只有一个实例,如登录框的实现。工厂模式则用于对象的封装和创建,如在vue-router中的应用。观察者模式和发布订阅模式在事件处理中发挥关键作用,其中发布订阅模式通过事件中心解耦订阅者和发布者。
摘要由CSDN通过智能技术生成

什么是设计模式?

       所谓设计模式,是前辈们总结下来的,在软件设计、开发过程中,针对特定场景、特定问题的较优解决方案。

为什么需要设计模式?

       实际上,不使用设计模式,照样可以进行需求开发。但是这造成的后果是:因设计缺陷、代码实现缺陷,给后期维护、开发、迭代带来了麻烦。

一、代理模式

       代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。引入代理模式,其实是为了实现单一职责的面向对象设计原则。单一职责其实就是指在一个类中(js中通常指对象和函数等),应仅有一个引起它变化的原因。这样会帮助程序设计具有良好的健壮和高内聚特性,从而当变化发生时,程序设计会尽量少的受到意外破坏。

  • 用户无权访问目标对象
  • 中间加上代理对象,通过代理进行授权和控制。

在这里插入图片描述

如何实现代理模式

       代理对象内部含有对本体对象的引用,因而可以与调用本体的相关方法;同时,代理对象提供与本体对象相同的接口,方便在任何时刻代理本体对象。

例一
class Girl{
    sayBrokeup(b,a){
        console.log(b+','+a+'想要和你分手!');
    }
}

class Sisters{
    constructor(){
        this.someone=new Girl();
    }
    sayBrokeup(b,a){
        this.someone.sayBrokeup(b,a);
    }
}
let julia=new Sisters();
julia.sayBrokeup('jack','rose');

运行结果:
在这里插入图片描述

由上述代码可以看出,女生确实有说分手的能力,但她不会自己亲口说出分手,而是让自己的闺蜜向男生转述分手!

例二

      加入页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发。

    <li>1</li>
    <li>2</li>
    <li>3</li>
    </ul>
     <script>
    var lis = document.getElementsByTagName('li');
     for (let i = 0; i < lis.length; i++) {
        lis[i].onclick = function () {
             alert(i+1);
  }
    }
    </script>

      相信部分小伙伴的写法可能和我上面的写法一样,循环,给每个li添加点击事件,我们看看有多少次的dom操作,我们来看看,首先要找到ul,然后遍历li,然后点击li的时候,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li,那么问题来了,如果有很多li怎么办?

      Event对象给我们提供了一个属性叫target,它可以返回事件的目标节点,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom, 我们还要用nodeName来获取具体是的标签名,这个返回的是一个大写的标签名,我们还需要转成小写。

<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
</ul>

<script>
    var ul = document.getElementById("ul");
    ul.onclick = function (e) {
        var e = e || window.event;
        var target = e.target;
        if (target.nodeName.toLowerCase() == 'li') {
            alert(target.innerHTML);
        }
    }
</script>

      这样就达到了事件代理的效果,将所有li的点击事件,全部代理给ul,而且只有点击li才会触发事件了,而且每次只需要执行一次dom操作,如果li数量很多 ,就可以大大的减少dom操作,大大提高了性能优化。

二、单例模式

      单例就是保证一个类只有一个实例,实现方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。

class SingleObject {
    login() {
        console.log('login...')
    }
}
SingleObject.getInstance = (function () {
    let instance
    return function () {
        if (!instance) {//判断实例存在与否
            instance = new SingleObject();
        }
        return instance
    }
})()

// 测试
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log(obj1 === obj2)//true

       任意一个网站,点击登录按钮,只会弹出有且仅有一个登录框,即使后面再点击登录按钮,也不会再弹出多一个弹框。这就是单例模式的应用场景

三、工厂模式

       将new操作单独封装,遇到new时,就要考虑是否该使用工厂模式。

class Animal{
    static getKind(name){
        switch(name){
            case 'dog':
                return new Dog();
            case 'cat':
                return new Cat();
            default:
                throw new Error('没有这个动物');
        }
    }
};
class Cat{
    constructor(){
        this.name='mimi'
    }
    say(){
        console.log('小猫'+this.name)
    }
}
class Dog{
    constructor(){
        this.name='wangwang'
    }
    say(){
        console.log('小狗'+this.name)
    }
}
const a=Animal.getKind('dog')
a.say();

工厂模式的应用

       工厂模式在源码中应用频繁,以 vue-router 中的源码为例:

export default class VueRouter {
    constructor(options) {
        // 路由模式
        this.mode = mode	
        // 简单工厂
        switch (mode) {   
            // history 方式        
            case 'history':       
                this.history = new HTML5History(this, options.base)
                break
            // hash 方式
            case 'hash':          
                this.history = new HashHistory(this, options.base, this.fallback)
                break
            // abstract 方式
            case 'abstract':      
                this.history = new AbstractHistory(this, options.base)
                break
            // 初始化失败报错
            default:   
        }
    }
}

工厂模式的优缺点

工厂模式将 对象的创建和实现分离,这带来了优点:

  1. 良好的封装,代码结构清晰,访问者无需知道对象的创建流程,特别是创建比较复杂的情况下;

  2. 扩展性优良,通过工厂方法隔离了用户和创建流程隔离,符合开放封闭原则;

  3. 解耦了高层逻辑和底层产品类,符合最少知识原则,不需要的就不要去交流;

工厂模式的缺点:带来了额外的系统复杂度,增加了抽象性

四、观察者模式

在这里插入图片描述
       观察者模式,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。

// 定义主题,即观察者观察的对象(目标)
class Subject {
    constructor (name) {
      this.name = name
      this.observers = [] // 用来存储观察者
      this.state = '雨天'
    }
    // 添加观察者
    attach (observer) {
      this.observers.push(observer)
    }
  
    // 一旦被观察的信息发生变化,则通知观察者
    setState (newState) {
      this.state = newState
      this.observers.forEach(observer => {
        observer.update(newState)
      })
    }
  }
  // 定义观察者
  class Observer {
    constructor (name) {
      this.name = name
    }
    // 定义观察者针对数据发生变化做出的响应
    update (newState) {
      console.log(this.name + '知道了' + newState)
    }
  }
  const subject = new Subject('我是天气')
  const observerOne = new Observer('我是播报员1')
  const observerTwo = new Observer('我是播报员2')
  subject.attach(observerOne)
  subject.attach(observerTwo)
  subject.setState('晴天')

五、发布订阅模式

在这里插入图片描述

       发布/订阅模式,订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

观察者模式和发布订阅模式的区别

  • 角色角度来看,发布订阅模式需要三种角色,发布者、事件中心、订阅者。而观察者模式需要两种角色,目标和观察者,无事件中心负责通信。
  • 从耦合度上来看,发布订阅模式是一个中心调度模式,订阅者和发布者是没有直接关联的,通过事件中心尽心关联,两者是解耦的。而观察者模式中的目标和观察者是直接关联的,耦合在一起(有些观念说观察者是解耦,解耦的是业务代码,不是目标和观察者本身)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值