假期倒计时 - JavaScript 设计模式

119 阅读11分钟

前言

大家好!我是小瑜,你们抢到回家过年的火车票了吗? 哈哈,反正我是开售前半个小时就打开软件蹲点,虽然期间小卡了一下,当时慌的一,好在有惊无险顺利拿下一张回家的车票。返程票也即将开售,大家做好狙击准备! 今天给大家分享的是常见的几个JavaScript的设计模式,自己也是边复习边写文章,写的不好的地方请大佬指点 ~

那么话不多说,开整!

原型模式

基于构造器模式改造, 使得代码复用性增加

  • 基本demo
function Employee(name,age){
    this.name = name;
    this.age =age;
    this.say = function(){
        console.log(this.name+"-",this.age)
    }
}
new Employee("张三",100)
new Employee("lisi",18)
  • 综合案例

利用原型模式实现两个tab栏切换,要求分别是click以及mouseenter实现

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      ul {
        list-style: none;
      }

      .header {
        display: flex;
        width: 500px;
      }

      .header li {
        flex: 1;
        height: 50px;
        line-height: 50px;
        text-align: center;
        border: 1px solid black;
      }

      .box {
        position: relative;
        height: 200px;
      }

      .box li {
        position: absolute;
        left: 0;
        top: 0;
        width: 500px;
        height: 200px;
        background-color: yellow;
        display: none;
      }

      .header .active {
        background-color: red;
      }

      .box .active {
        display: block;
      }
    </style>
  </head>

  <body>
    <div class="container1">
      <h2>我是click事件</h2>
      <ul class="header">
        <li class="active">1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
      </ul>
      <ul class="box">
        <li class="active">111</li>
        <li>222</li>
        <li>333</li>
        <li>444</li>
        <li>555</li>
        <li>666</li>
      </ul>
    </div>

    <div class="container2">
      <h2>我是mouseenter事件</h2>
      <ul class="header">
        <li class="active">1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
      </ul>
      <ul class="box">
        <li class="active">111</li>
        <li>222</li>
        <li>333</li>
        <li>444</li>
        <li>555</li>
        <li>666</li>
      </ul>
    </div>

    <script>

      class MyTabs {
        container
        type

        constructor(container, type) {
          this.container = document.querySelector(container)
          this.liItems = this.container.querySelectorAll('.box li')
          this.headerItems = this.container.querySelectorAll('.header li')
          this.type = type

          // 执行
          this.handle()
        }
        handle() {
          for (let i = 0; i < this.headerItems.length; i++) {
            this.headerItems[i].addEventListener(this.type, () => {
              this.removeActive()
              this.liItems[i].classList.add('active')
              this.headerItems[i].classList.add('active')
            })
          }
        }
        removeActive() {
          for (let i = 0; i < this.liItems.length; i++) {
            this.headerItems[i].classList.remove('active')
              this.liItems[i].classList.remove('active')
                }
            }
        }
      
        const tabs1 = new MyTabs('.container1', 'click')
        const tabs2 = new MyTabs('.container2', 'mouseenter')
    </script>

</body>

</html>

工厂模式

使用工厂方法来实例化对象而不是直接调用构造函数。这样可以将对象的创建和使用分离开来,提供了更大的灵活性。 核心在于定义一个抽象的工厂方法,并且由工厂返回其他构造函数

  • 基本demo

通过输入不同的角色返回不同的权限

class User {
  role // 角色
  page // 代表不同的权限页面
  constructor(role, page) {
    this.role = role
    this.page = page
  }
  // 静态工厂 => 通过匹配角色 返回不同权限页面的实例化对象
  static permission(role) {
    switch (role) {
      case 'admin':
        return new User('admin', [
          '查看用户',
          '查看订单',
          '查看商品',
          '查看数据',
        ])
      case 'user':
        return new User('user', ['查看订单', '查看商品'])
      default:
        return new Error('角色错误')
    }
  }
}
// 调用静态
const admin = User.permission('admin')
console.log(admin)
// User { role: 'admin', page: [ '查看用户', '查看订单', '查看商品', '查看数据' ] }
  • 综合案例

super-admin 的权限页面是 ['查看用户', '查看订单', '查看商品', '查看数据'] admin 的页面权限是 ['查看用户', '查看订单'] user 的页面权限是 ['查看订单']

1. 定义工厂函数 通过传入的角色名称返回不同的类
const userFactory = (role) => {
    switch (role) {
        case 'super-admin':
            return SuperAdmin
        case 'admin':
            return Admin
        case 'user':
            return NormalUser
        default:
            throw new Error('参数错误')
    }
}

2. 分别定义 super-admin admin user 类
   因为这三个角色中需要存储的数据类型都是一样的 分别都是name, role, page
   使用原型模式做代码的封装,减少代码冗余
class User {
    constructor(name, role, pages) {
        this.name = name
        this.role = role
        this.pages = pages
    }
    welcome() {
        console.log(`欢迎您,${this.name}`)
    }
    dataShow() {
        throw new Error('抽象方法需要被实现')
    }
}

class SuperAdmin extends User {
    constructor(name) {
        super(name, 'super-admin', ['查看用户', '查看订单', '查看商品', '查看数据'])
    }
    welcome() {
        console.log(`欢迎您,${this.name}`)
    }
}

class Admin extends User {
    constructor(name) {
        super(name, 'admin', ['查看用户', '查看订单'])
    }
    welcome() {
        console.log(`欢迎您,${this.name}`)
    }

}

class NormalUser extends User {
    constructor(name) {
        super(name, 'user', ['查看订单'])
    }
    welcome() {
        console.log(`欢迎您,${this.name}`)
    }
}

3. 调用工厂函数,传入角色名称
const roleClass = userFactory('user')
const user = new UserClass('zhangsan')
user.welcome() // 欢迎您,zhangsan
console.log(user);
// user {
//     "name": "zhangsan",
//     "role": "user",
//     "pages": [
//         "查看订单"
//     ]
// }

建造者模式

建造者模式将一个复杂对象的构建层与其表示层相互分离,由单独的一个类触发在不同类中相同的方法。这么说可能有点抽象,举个例子:一个Person类 和 Student 类 都有一个方法是 eat 方法, 并且通过一个Handle类来触发这个eat方法。

  • 直接上代码
class Person {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`我是${this.name},我在吃饭`);
    }
}

class Student {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`我是${this.name},我在吃饭`);
    }
}

class Handle {
    startBuild(builder) {
        builder.eat()
    }
}

const handle = new Handle();
const person = new Person('person');
const student = new Student('student');

handle.startBuild(person); // 我是person,我在吃饭
handle.startBuild(student); // 我是student,我在吃饭
  • 执行异步方法

目前上面的代码中,只能执行同步的代码,所以需要优化,也能够执行异步

class Person {
    constructor(name) {
        this.name = name
    }
    eat() {
        return new Promise((resolve, reject) => {
            resolve(`我是${this.name},我在吃饭`)
        })
    }
}

class Student {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`我是${this.name},我在吃饭`);
    }
}

class Handle {
    async startBuild(builder) {
        return await builder.eat()
    }
}

const handle = new Handle();
const person = new Person('person');
const student = new Student('student');

handle.startBuild(person).then((res) => {
    console.log(res); // 我是person,我在吃饭
}) 
handle.startBuild(student); // 我是student,我在吃饭

单例模式

它确保一个类仅有一个实例,并提供一个全局访问点来获取这个实例。通常用于管理全局状态、执行只需要运行一次的初始化代码,或者在整个应用中提供一个共享的资源或服务。

  • 也就是说这个类只会被创建一次,只会在初始化时执行。

实现思路,利用闭包,创建一个空变量 instance,如果instance为空就将这个构造函数的this赋值给instance,否则就返回instance,由于闭包的特性,会将instance缓存,所以永远只有第一次会将instance赋值this,之后instance就永远存在。

  • es5 写法 闭包函数
// 闭包函数
const singleton = (function () {
    // 闭包变量
    let instance = null
    function User(name, age) {
        this.name = name
        this.age = age
    }
    return function (name, age) {
        // 保证只有第一次触发才会实例化
        if (!instance) {
            instance = new User(name, age)
        }
        return instance
    }
})()

console.log(singleton('张三', 20)); // { name: '张三', age: 20 }
console.log(singleton('李四', 18)); // { name: '张三', age: 20 }
console.log(singleton('王五', 16)); // { name: '张三', age: 20 }
  • es6写法 类
class SingLeton {
    constructor(name, age) {
      // 如果SingLeton对象中的instance属性为真,也就是存在方法或属性
        if (!SingLeton.instance) {
            this.name = name
            this.age = age
            // 赋值this
            SingLeton.instance = this
        }
        return SingLeton.instance
    }
}

console.log(new SingLeton('zhangsan', 18)); // SingLeton { name: 'zhangsan', age: 18 }
console.log(new SingLeton('lisi', 20)); // SingLeton { name: 'zhangsan', age: 18 }
console.log(new SingLeton('wangwu', 22)); // SingLeton { name: 'zhangsan', age: 18 }

  • 用单例模式写一个modal弹层,要求只会创建一次弹层

思路:只会创建一次,那么就是用单例模式,不论是用类还是闭包,其中都会有一个初始变量instance,并且如果 !instance就给instance赋值,否则就返回instance。 接下来就用class来写,先写样式和一个开启按钮以及关闭按钮,并且只有初始化时后才会创建,接下来不论是点击开启还是关闭按钮,都不会销毁或者创建弹层,这里就可以用css样式的dispaly:none 、block来控制之后的显示和隐藏

<!DOCTYPE html>
<html lang='zh-CN'>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .modal {
            position: absolute;
            left: 20%;
            top: 50%;
            transform: translate(-50%, -50%);
            width: 200px;
            height: 200px;
            background-color: pink;
        }
    </style>
</head>

<body>
    <button id="show">show</button>
    <button id="hidden">hidden</button>

    <script>
        class MyModal {
            constructor() {
                if (!MyModal.instance) {
                    this.$el = document.createElement('div')
                    this.$el.className = 'modal'
                    this.$el.innerHTML = 'modal'
                    document.body.appendChild(this.$el)
                    MyModal.instance = this
                    this.$el.style.display = 'none'
                }
                return MyModal.instance
            }
            show() {
                this.$el.style.display = 'block'
            }
            hidden() {
                this.$el.style.display = 'none'
            }
        }
        const modal = new MyModal()

        show.onclick = () => {
            modal.show()
        }

        hidden.onclick = () => {
            modal.hidden()
        }


    </script>
</body>

</html>

2024-01-28-11-53-19.gif

装饰器模式

装饰器模式能够很好的对已有的功能进行拓展,并且不会更改原有的代码,这可以方便在较少的代码下,对已有功能进行拓展

  • 需求:利用装饰器模式,在一个函数执行之前以及执行之后执行逻辑。
Function.prototype.before = function (beforeFn) {
    const _this = this
    return function (arguments) {
        // 先执行前置函数
        beforeFn.apply(this, arguments)
        // 再执行原来的函数
        return _this.apply(this, arguments)
    }
}

Function.prototype.after = function (afterFn) {
    const _this = this
    return function (arguments) {
        const result = _this.apply(this, arguments)
        // 先执行原来的函数
        afterFn.apply(this, arguments)
        // 再执行后置函数
        return result
    }
}

function render() {
    console.log('render');
}

const renderBefore = render.before((value = '我是before') => {
    console.log('render之前执行', value);
}).after((value = '我是after') => {
    console.log('render之后执行', value);
})


renderBefore()
// 输出结果
// render之前执行 我是before
// render
// render之后执行 我是after
  • 数据埋点应用

在很多企业项目中,会增加数据埋点的需求,例如在按钮点击之前或者之后需要收集某些用户行为或者信息等。这里就可以使用装饰器模式,来给之前点击的逻辑 增加 after 或者 before 函数,从而达到数据埋点的功能。

const useBurialPoint = (handleFn) => {
    Function.prototype.before = function (beforeFn) {
        const _this = this
        return function (arguments) {
            // 先执行前置函数
            beforeFn.apply(this, arguments)
            // 再执行原来的函数
            return _this.apply(this, arguments)
        }
    }

    Function.prototype.after = function (afterFn) {
        const _this = this
        return function (arguments) {
            const result = _this.apply(this, arguments)
            // 先执行原来的函数
            afterFn.apply(this, arguments)
            // 再执行后置函数
            return result
        }
    }

    // 数据埋点的逻辑
    const log = () => {
        console.log('数据埋点');
    }

    handleFn = handleFn.before(log)

    return handleFn
}

// 点击事件的处理逻辑
const handleClick = () => {
    console.log('click');
}

// 调用装饰器函数
const handleBefore = useBurialPoint(handleClick)

// 绑定点击事件
btn.onclick = () => {
    handleBefore() // 输出
}

  • 实现axios请求拦截器功能 - 携带token

利用axios的请求拦截器,可以在每次请求前,给请求头添加token,这里可以利用装饰器模式来实现这个功能

Function.prototype.before = function (beforeFn) {
    let _this = this
    return function () {
        // 先进行前置函数调用
        beforeFn.apply(this, arguments)
        // 执行原来的函数
        return _this.apply(this, arguments)
    }
}

Function.prototype.after = function (afterFn) {
    const _this = this
    return function () {
        const result = _this.apply(this, arguments)
        afterFn.apply(this, arguments)
        return result
    }
}


function ajax(url, method, params) {
    console.log(url, method, params) 
}

const ajax1 = ajax.before((url, method, params) => {
    params.token = "aaaaaaaaaaaaaaaaa"
})

ajax1("/api", "post", {
    name: "zhangsan"
})

// 输出结果 /api post {name:"zhangsan",token:"aaaaaaaaaaaaaaaaa"}

适配器模式

两个类的方法调用不一样,如果要同时调用,需要分别实例化两次并调用,如果是多个就需要多次重复。通过使用适配器模式就可以简化这种重复的操作。

/**
 * TXMap以及BDMap渲染的方法不一样, 但是需要统一调用display方法就可以显示渲染
 * 这时候就需要适配器模式
*/
class TXMap {
    show() {
        console.log('腾讯地图');
    }
}

class BDMap {
    display() {
        console.log('百度地图');
    }
}

// 创建一个类, 继承TXMap, 重写display方法, 在display方法中调用show方法
class TencentAdapter extends TXMap {
    constructor() {
        super()
    }
    display() {
        this.show()
    }
}

// 适配器模式
const renderMap = (map) => {
    map.display()
}

renderMap(new TencentAdapter()) // 腾讯地图
renderMap(new BDMap()) // 百度地图

策略模式

策略模式就是优化代码的方式,例如if if-else这些判断语句,就可以进行优化,例如在使用框架写带页面应用路由,也是使用策略模式。

  • 需求: 如果 leave为S 年终奖✖️4 如果leave为A 年终奖✖️3 如果leave为B 年终奖 ✖️ 2
// 通过对象的方式,匹配key处理对应的相乘函数
const tacticsObj = {
    'S': (salary) => {
        return salary * 4
    },
    'A': (salary) => {
        return salary * 3
    },
    'B': (salary) => {
        return salary * 2
    }
}

const handle = (leave, salary) => {
    console.log(tacticsObj[leave](salary));
    return tacticsObj[leave](salary)
}
handle('S', 2000) // 8000
handle('A', 1000) // 3000
  • 再例如后端会传数字给前端,用来判断某些type,如果直接写if (type === 1) 这样很不利于代码的可读性和维护,除非使用的是TS的枚举。

需求:后端返回一段数据,要求展示按需渲染 [{ title: '张三', type: 1 },....] 如果type是1 文字是审核中,要求按钮的背景是红色 如果type是2 文字是审核通过,要求按钮背景是绿色 如果type是3 文字是审核不通过,要求按钮背景是黄色

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="box">
        <input type="text" id="ipt">
        <span id="span"></span>
    </div>

    <script>
        let vueobj = {
        }

        let proxy = new Proxy(vueobj, {
            get(target, key) {
                return target[key]
            },
            set(target, key, value) {
                if (key === "data") {
                    //操作dom
                    span.innerHTML = value
                }

                target[key] = value
            }
        })
        ipt.oninput = function () {
            proxy.data = this.value
        }
    </script>
</body>

</html>

代理模式

利用ES6 Proxy对数据进行代理,并且设置条件,如果条件满足则处理某一段逻辑

  • 需求: 判断价格是否超过10000 如果超过就可以合作,如果没超过就是报错提示价格不合适
const star = {
  name: "张三",
  workPrice: 100000
}

let proxy = new Proxy(star, {
  get(target, key) {
    if (key === "workPrice") {
      console.log("访问了")
    }
    return target[key]
  },
  set(target, key, value) {
    if (key === "workPrice") {
      if (value > 10000) {
        console.log("可以合作")
      } else {
        throw new Error("价钱不合适")
      }
    }
  }
})
proxy.workPrice = 10000000 // 价格合适
proxy.workPrice = 1 // error 价格不合适 

  • 输入input内容,同步显示在页面中,类似vue的v-model 响应式绑定

2024-01-28-14-11-07.gif

<div id="box">
  <input type="text" id="ipt">
  <span id="span"></span>
</div>

![2024-01-28-14-11-07.gif](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fbac1334cb5d431493795ba9a5e5f5f9~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=660&h=746&s=284890&e=gif&f=215&b=fcfbfe)
<script>
  let vueobj = {
  }

  let proxy = new Proxy(vueobj, {
    get(target, key) {
      return target[key]
    },
    set(target, key, value) {
      if (key === "data") {
        //操作dom
        span.innerHTML = value
      }

      target[key] = value
    }
  })
  
  ipt.oninput = function () {
    proxy.data = this.value
  }
</script>

观察者模式

观察者模式分为被观察者(Subject)以及观察者(Observer), 在被观察者的状态发生变化后及时通知一些列依赖于他的对象也就是观察者进行某些逻辑操作。

  • 被观察者(Subject)

这里可以理解为状态是一个数组,里面包含了被观察者所需要查看的一个个数据,方法=>有添加数组 ,删除数组,以及执行数组中所有逻辑的函数

  • 观察者(Observer)

在Subject执行执行数组中所有逻辑的函数中的具体更新方法 更具以上的概念就可以写出一个基本的方法类

// 被观察者
class Subject {
  constructor() {
    // 需要被观察的列表
    this.observers = [] 
  }
  // 添加观察者
  add(observe) {
    this.observers.push(observe)
    console.log(this.observers,'我需要观察这些');
  }
  // 删除观察者
  remove(observe) {
    this.observers = this.observers.filter(item => item !== observe)
  }
  // 通知执行观察者的update方法
  notify() {
    this.observers.forEach(observe => {
      observe.update()
    })
  }
}
// 观察者
class Observer {
 constructor(name) {
        this.name = name
    }
    // 更新方法
    update() {
        console.log('更新了' + this.name);
    }
}

实例化执行函数

// 1、实例化被观察者
const sub = new Subject() 

// 2、实例化观察者 并添加需要被观察的数据或方法
const obs1 = new Observer('张三需要被观察')
const obs2 = new Obserber('李四需要被观察')

// 3、将obs1和obs2添加到sub的数组中
sub.add(obs1)
sub.add(obs2)

// 4、当observers中数据变化后,通知执行观察者的update方法
sub.notify()

输出结果

  • 接下来通过观察者模式实现一个小demo,通过点击每一个li,来实现头部以及右侧的显示,先看一下效果

思路: 1、要给每个li注册点击事件,获取到点击的内容, 并且通知更新

for (let i = 0; i < lis.length; i++) {
    lis[i].addEventListener('click', (e) => {
        const data = e.target.innerText
        subject.notify(data) 
    })
}

2、需要将点击后的内容给头部以及右侧内容进行更新

class Observer {
    constructor(name) {
        this.$el = document.querySelector(name)
    }
    update(data) {
        console.log(this.$el, data);
        this.$el.innerHTML = data
    }
}

3、被观察者就需要,存储头部以及右侧的dom元素这个dom数组,并且将需要执行这个数组中的更新方法

class Subject {
    constructor() {
        this.observers = []
    }
    add(observe) {
        this.observers.push(observe)
        console.log(observe);
    }
    remove(observe) {
        this.observers = this.observers.filter(item => item !== observe)
    }
    notify(data) {
        this.observers.forEach(observe => {
            observe.update(data)
        })
    }
}

4、实例化观察者模式

// 创建被观察者
const subject = new Subject()

// 创建观察者
const headerObj = new Observer('.header')
const containerObj = new Observer('.container')

// 添加观察者
subject.add(headerObj)
subject.add(containerObj)

5、完整代码

<!DOCTYPE html>
<html lang='zh-CN'>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
      .box {
        display: flex;
        width: 100%;
        height: 100%;
      }

      .left {
        width: 200px;
        background-color: skyblue;
      }

      .right {
        flex: 1;
        background-color: pink;
      }

      li {
        cursor: pointer;
      }
    </style>
  </head>

  <body>
    <header class="header">header</header>
    <div class="box">
      <div class="left">
        <ul>
          <li>首页</li>
          <li>部门中心</li>
          <li>财务中心</li>
          <li>人事中心</li>
          <li>信息中心</li>
        </ul>
      </div>
      <div class="right container">main</div>
    </div>

    <script>

      class Subject {
        constructor() {
          this.observers = []
        }
        add(observe) {
          this.observers.push(observe)
          console.log(observe);
        }
        remove(observe) {
          this.observers = this.observers.filter(item => item !== observe)
        }
        notify(data) {
          this.observers.forEach(observe => {
            observe.update(data)
          })
        }
      }

      class Observer {
        constructor(name) {
          this.$el = document.querySelector(name)
        }
        update(data) {
          console.log(this.$el, data);
          this.$el.innerHTML = data
        }
      }

      // 创建被观察者
      const subject = new Subject()

      // 创建观察者
      const headerObj = new Observer('.header')
      const containerObj = new Observer('.container')

      // 添加观察者
      subject.add(headerObj)
      subject.add(containerObj)

      const lis = document.querySelectorAll('li')

      for (let i = 0; i < lis.length; i++) {
        lis[i].addEventListener('click', (e) => {
          console.log(e.target.innerText);
          const data = e.target.innerText
          subject.notify(data)
        })
      }

    </script>
  </body>

</html>

发布订阅模式

相比于观察者的存储单一数组的方式,发布订阅订阅则更加灵活,他需要“观察”的数据是长类似这个样子,通过第三方实现调度,属于经过解耦合的观察者模式。

{
  "type1": ['fn1', 'fn2'],
  "type2": [ 'fn3', 'fn4']
}

这种数据结构就更加灵活,可以给任意一个key添加方法,也可以添加任意一个key。同样也是可以执行任意一个key中的方法。 1、先不着急写核心代码,首先订阅要执行的函数

// 定义要执行的函数
const fn1 = (data) => {
    console.log('fn1', data);
}
const fn2 = (data) => {
    console.log('fn2', data);
}
const fn3 = (data) => {
    console.log('fn3', data);
}

// 订阅要执行的函数 PubSub对象中有一个subscribe方法可以添加方法或者称为订阅
PubSub.subscribe('我是type1分组', fn1)
PubSub.subscribe('我是type1分组', fn2)
PubSub.subscribe('我是type2分组', fn3)

// 此时期待的订阅的数据内容应该是这样
{
    "我是type1分组": [
        fn1,
        fn2
    ],
    "我是type2分组": [
        fn3
    ]
}

2、写发布订阅模式中的订阅模式,也就是添加数据

const PubSub = {
  // 最终订阅的数据都会存储在这里
   message: {},
  // 订阅,也就是将方法放入到订阅的消息列表中
   subscribe(type, cb) {
      // 如果没有这一项,则需要创建一个对象,将方法添加到数组中
      if (!this.message[type]) {
          this.message[type] = [cb]
      } else {
        // 如果有这项就push添加方法
          this.message[type].push(cb)
      }
  },
}

3、现在有数据了就需要执行,也就是发布的过程,但是我添加了type1分组和type2分组,我只要执行type1分组的逻辑就可以了,所以接下来先写发布执行的代码

PubSub.publish('我是type1分组', 'hello')
PubSub.publish('我是type2分组', 'world')

4、这里传入了一个type分组还有其他的参数,还有hello和world, 其中二个参数就是传递给要执行的函数的参数,那么开始写这部分的发布逻辑

const PubSub = {
  ... 订阅代码此处省略
  message: {},
  publish(type, data) {
      // 如果在订阅的消息列表中没有这一项,则不需要执行
      if (!this.message[type]) return
      this.message[type].forEach(item => item(data))
  }
}

5、只有添加订阅没有删除订阅?

const PubSub = {
  ...
  message:{},
  // 删除订阅
  unSubscribe(type, cb) {
    // 如果没有这一项,则不需要删除
    if (!this.message[type]) return
    // 如果没有传递事件则需要将这个type数组全部清空
    if (!cb) {
        this.message[type] = []
    } else {
        // 删除单个
        this.message[type] = this.message[type].filter(item => item !== cb)
    }
 }
}
  • 完整代码
const PubSub = {
    message: {},
    // 发布 => 也就是执行订阅的方法
    publish(type, data) {
        // 如果在订阅的消息列表中没有这一项,则不需要执行
        if (!this.message[type]) return
        this.message[type].forEach(item => item(data))
    },
    // 订阅,也就是将方法放入到订阅的消息列表中
    subscribe(type, cb) {
        // 如果没有这一项,则需要创建一个对象,将方法添加到数组中
        if (!this.message[type]) {
            this.message[type] = [cb]
        } else {
            this.message[type].push(cb)
        }
        console.log(this.message, 'message');
    },
    unSubscribe(type, cb) {
        // 如果没有这一项,则不需要删除
        if (!this.message[type]) return
        if (!cb) {
            this.message[type] = []
        } else {
            this.message[type] = this.message[type].filter(item => item !== cb)
        }
    }
}
// 定义要执行的函数
const fn1 = (data) => {
    console.log('fn1', data);
}
const fn2 = (data) => {
    console.log('fn2', data);
}
const fn3 = (data) => {
    console.log('fn3', data);
}

// 订阅要执行的函数 PubSub对象中有一个subscribe方法可以添加方法或者称为订阅
// 这里给type1添加了两个方法,分别是fn1 fn2 给type2添加了一个方法为 fn3
PubSub.subscribe('我是type1分组', fn1)
PubSub.subscribe('我是type1分组', fn2)
PubSub.subscribe('我是type2分组', fn3)

// 这里我又想删除type1中的fn1方法
PubSub.unSubscribe('我是type1分组', fn1)

// 此时message中的数据为
PubSub.message:{
    "我是type1分组": [ fn2 ],
    "我是type2分组": [ fn1 ]
}

// 发布 => 我想要执行type1分组以及type2分组的方法
PubSub.publish('我是type1分组', 'hello') 
PubSub.publish('我是type2分组', 'world')

// 输出结果
fn2 hello
fn3 world

迭代器模式

迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供了一种顺序访问集合对象的元素,而不需要暴露该对象的内部表示的方法。

  • 需求:我需要一个方法接受两个参数,参数1需要迭代的数组,参数2是执行的回调函数,返回key和value
const myIterator = function (arr, cb) {
    for (let i = 0; i < arr.length; i++) {
        cb(i, arr[i])
    }
}

// 执行函数
myIterator([11, 22, 33, 44], (key, value) => {
    console.log(key, value); 
})
// 执行结果
0 11
1 22
2 33
3 44
  • 迭代器如何实现呢?我可以自己手动迭代么?

Array map set String NodeList arguments 这些内容都内置Symbol.iterator函数 调用Symbol.iterator中的next方法就可以进行迭代 1、定义一个数组,通过打印查看

const arr = [2, 5, 6, 7]
console.log(arr);
const it = arr[Symbol.iterator]()
console.log(it);

2、调用next方法手动迭代,直到done为true则停止迭代

const arr = [2, 5, 6, 7]
console.log(arr);
const it = arr[Symbol.iterator]()
console.log(it);
console.log(it.next()); // {value: 2, done: false}
console.log(it.next()); // {value: 5, done: false}
console.log(it.next()); // {value: 6, done: false}
console.log(it.next()); // {value: 7, done: false}
console.log(it.next()); // {value: undefined, done: true}
  • 如何让对象实现迭代,也就是使用for of方法
// 让具有下标的对象进行迭代
// 必须要加上长度以及Symbol.iterator函数
const objKey = {
    0: '张三',
    1: '李四',
    length: 2,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for (const item of objKey) {
    // console.log(item);
}
  • 给对象中某个属性进行手动迭代

如果要实现迭代,必须有一个Symbol.iterator函数 并且Symbol.iterator函数里面有next方法,根据这个思路就好些代码了

// 我需要将arr进行手动迭代
const obj = {
    name: 'zs',
    age: 20,
    arr: [1, 2, 3, 5],
    [Symbol.iterator]: function () {
        let index = 0
        return {
            next: () => {
                if (this.arr.length > index) {
                    return {
                        value: this.arr[index++],
                        done: false
                    }
                } else {
                    return {
                        value: undefined,
                        done: true
                    }
                }
            }
        }
    }
}

const res1 = obj[Symbol.iterator]()
console.log(res1.next()); // {value: 1, done: false}
console.log(res1.next()); // {value: 2, done: false}
console.log(res1.next()); // {value: 3, done: false}
console.log(res1.next()); // {value: 5, done: false}
console.log(res1.next()); // {value: undefined, done: true}

for (const item of obj) {
    console.log(item); // 1 2 3 5
}


完结 ~ 如果能帮助到你,或者觉得写的还马虎,可以给我免费的赞么~ 谢谢各位大佬~ 手动撒花 ✿✿ヽ(°▽°)ノ✿

2024.1.28 浦东图书馆角落里