美文网首页
[深入23] JS设计模式 - 代理,策略,单例

[深入23] JS设计模式 - 代理,策略,单例

作者: woow_wu7 | 来源:发表于2021-10-02 11:54 被阅读0次

导航

[react] Hooks

[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[数据结构和算法01] 二分查找和排序

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例

(一) 前置知识

  • 设计模式一直都没敢写,主要是个人觉得理解不难,主要胆怯在运用于生产项目

(1) 一些单词

pattern: 模式 // design pattern: 设计模式
strategy: 策略,战略

performance: 性能,绩效
salary: 工资,薪水
bonus: 奖金

singleton: 单身,单例模式

(2) Storage

  • 概念
    • 两个对象部署了 ( Storage接口 ),分别是 ( window.localStorage ) 和 ( window.sesstionStorage )
  • 方法
    • Storage.setItem(key, value)
      • key 必须是符串,非字符串会被转成字符串
      • value 也必须是字符串,非字符串会被转成字符串
      • 如果key已经存在,则新的value覆盖旧的value
      • 如果存储空间已满,该方法会抛错
      • Storage.setItem('name', 'woow_wu7') 等价于 Storage.name = 'woow_wu7'
    • Storage.getItem(key)
      • key不存在,则返回 null
    • Storage.removeItem(key)
      • 清除key对应的value
    • Storage.clear()
      • 清除Storage中的所有数据
      • 返回值是undefined
    • Storage.key(number)
      • 接受一个整数作为参数,返回该位置对应的键值
  • 事件
    • window.addEventListener('storage', onStorageChange)
    • 该监听函数的event对象
      • StorageEvent.key: 发生变动的键名
      • StorageEvent.newValue: 新的键值,字符串
      • Storage.oleValue: 旧的键值,字符串
      • Storage.storageArea: 返回整个对象
      • Storage.url: 表示原始触发 storage 事件的 ( 网页地址 )
  • 特别注意
    • 该事件不在导致数据变化的当前页面触发,而是在 ( 同一个域名 ) 的其他窗口触发
    • 所以:如果浏览器只有一个窗口,可能看不到该事件触发
    • 所以:可以实现多窗口通信
      image

(3) 对象的命令空间

var namespace = {
  a: () => { console.log('a') },
  b: () => { console.log('b') },
}

(二) JS设计模式

(1) 代理模式

(1) 代理模式的定义

  • 代理模式: 指给某一个对象提供一个 ( 代理对象 ),并由 ( 代理对象 ) 来控制 ( 原对象的引用 ),代理模式类似 ( 中介 )

(2) 为什么要使用代理模式?

  • 中介隔离
    • ( 客户对象 ) === ( 代理对象 ) === ( 委托对象 )
    • 一些情况下,客户对象不想或者不能直接引用一个委托对象,则代理对象可以充当中介作用
  • 开闭原则,增加功能
    • ( 代理类 ) 除了是 ( 客户类 ) 和 ( 委托类 ) 的中介之外,还可以给代理类增加额外的功能来扩展委托类的功能,这样我们就可以直接修改代理类而不是直接修改委托类,符合代码设计的开闭原则
    • ( 代理类 ) 主要负责委托类的 ( 预处理消息, 过滤消息, 转发消息给委托类,对返回结果的处理 )
    • ( 代理类 ) 本身不提供服务,而是通过调用委托类相关的方法,来提供特定的服务,真正的业务功能还是由委托类来实现

(3) 实战

(3-1) 代理ajax请求 - 增加缓存功能

  • 作用
    • 通过 代理函数proxyRequest() 调用 cache() 使得 request() 方法做了一层缓存
    • 缓存每次请求的参数,如果参数一样,就直接返回之前参数对应的 Map 中的缓存的 value
  • 原理
    • 利用 ( 代理模式 ) 给原由的函数添加新的逻辑但又不影响原函数,相当于用 ( 函数组合 ) 来实现 ( 复用逻辑和添加逻辑 )
<!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>
  <script>
    // Map
    // 1.实例属性 mapInstance.size
    // 2.原型方法 mapInstance.get() set() has() delete() clear()
    const mapInstance = new Map()

    // request 发送请求,返回一个promise实例
    const request = (params) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          return resolve(`data: ${params}`)
        }, 2000)
      })
    }

    // cache 缓存 ( 请求参数 )和 ( 请求结果 promise )
    const cache = (params) => {
      if (mapInstance.has(params)) { // Map.prototype.has
        return mapInstance.get(params) // Map.prototype.get
      }
      mapInstance.set(params, request(params))
      return request(params)
    }

    // proxyRequest 代理请求
    const proxyRequest = (params) => {
      return cache(params)
    }
    
    // 当参数一样时,是从Map中返回的缓存值
    proxyRequest('11').then(data1 => {
      proxyRequest('11').then(data2 => console.log('data1 === data2 ? 这里返回true ', data1 === data2))
    })
  </script>
</body>
</html>

(3-2) 缓存代理 - 处理缓存过期时间 - class版本

<!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>
  <script>
    class Storage {
      constructor({ type, expires }) {
        this.type = type // 类型
        this.expires = expires // 过期时间
        this.storageType = {
          local: 'localStorage',
          session: 'sessionStorage'
        }
      }
      // 取
      // 代理原生 getItem() 方法,根据缓存过期时间,判断数据是否过期
      getItem(key) {
        const now = +new Date()
        const { value, setTimes } = JSON.parse(window[this.storageType[this.type]].getItem(key))
        if (now > setTimes + this.expires) {
          window[this.storageType[this.type]].removeItem(key) // 如果过期,清除该 Storage
          return null
        }
        return value
      }
      // 存
      // 代理原生的 setItem() 方法,添加缓存时间
      setItem(key, value) {
        window[this.storageType[this.type]].setItem(key, JSON.stringify({
          value,
          setTimes: +new Date()
        }))
      }
    }
    const localStorage = new Storage({type: 'local', expires: 1000})

    localStorage.setItem('name', 'woow_wu7')
    console.log('未过期', localStorage.getItem('name'))

    setTimeout(() => {
    console.log('过期', localStorage.getItem('name'))
    }, 2000)
    
  </script>
</body>
</html>
image

2021/05/03 优化

(3-2) 优化缓存代理 - 处理缓存过期时间 - class版

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      class Storage {
        constructor(type, expires) {
          this.storage = window[type];
          this.expires = expires;
        }
        // 存
        // 代理原生的 setItem() 方法,添加缓存过期时间
        setItem = (key, value) => {
          this.storage.setItem(
            key,
            JSON.stringify({
              value,
              setTime: +new Date(), // --------------------- ( 存 ) 的时间戳
            })
          );
        };
        // 取
        // 代理原生的 getItem() 方法,根据传入的过期时间,来判断数据是否过期
        getItem = (key) => {
          const now = +new Date(); // --------------------- ( 取 ) 的时间戳
          const { value, setTime } = JSON.parse(this.storage.getItem(key));
          if (now - setTime > this.expires) {
            this.storage.removeItem(key); // -------------- 过期删除
          }
          return this.storage.getItem(key);
        };
      }
      const localStorageInstance = new Storage("localStorage", 5000);
      localStorageInstance.setItem("name", "woow_wu7");
      console.log("未过期", localStorageInstance.getItem("name"));
      setTimeout(() => {
        console.log("过期", localStorageInstance.getItem("name"));
      }, 6000);
    </script>
  </body>
</html>

(3-3) 缓存代理 - 处理缓存过期时间 - function版本

<!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>
  <script>
    function Storage(type, expires) {
      this.type = type
      this.expires = expires
      this.storageType = {
        local: 'localStorage',
        sesstion: 'sesstionStorage'
      }       
    }
    Storage.prototype.getItem = function(key) {
      const now = +new Date()
      const { value, setTimes } = JSON.parse(window[this.storageType[this.type]].getItem(key))
      if (now - setTimes > this.expires) {
        window[this.storageType[this.type]].removeItem(key)
        return null
      }
      return value
    }
    Storage.prototype.setItem = function(key, value) {
      window[this.storageType[this.type]].setItem(key, JSON.stringify({
        value,
        setTimes: +new Date()
      }))
    }
    const storage = new Storage('local', 1000)
    storage.setItem('name', 'woow_wu7')
    console.log('未过期', storage.getItem('name'))

    setTimeout(() => {
      console.log('过期', storage.getItem('name'))
    }, 2000)
  </script>
</body>
</html>
image

(2) 策略模式

(1) 概念

  • 定义一系列算法,把它们一个个封装起来,并使它们可以 ( 相互替换 )
  • 将 ( 不变的部分 ) 和 ( 变化的部分 ) 隔开
  • ( 策略模式 ) 的主要目的就是将 ( 算法的使用 ) 和 ( 算法的实现 ) 分离开来

(2) 组成 - 策略模式组要包含 ( 策略类 ) 和 ( 环境类 )

  • 策略类
    • 封装了具体的算法,并负责具体的计算过程
  • 环境类 context
    • 环境类context接受客户的请求,随后把请求 ( 委托 ) 给某一个策略类
    • 环境类context中要维持对某个策略类的引用

(3) 特点

  • 避免多重选择语句出现:策略模式利用 ( 组合,委托,多态 ) 等技术和思想,可以有效的避免 ( 多重条件选择语句if...else ) 的情况
  • 符合开放封闭原则:将算法封装在独立strategy策略中,使得它们容易切换,理解,扩展
  • 算法可复用:可以复用在系统其他地方,从而避免冗余重复的工作

(4) 缺点

  • 必须了解所有策略:必须了解各个策略的不同点,才能实现一个特定的策略

(5) 实战

(5-1) 计算奖金

  • S绩效 - 4倍工资
  • A绩效 - 3倍工资
  • B绩效 - 2倍工资
  • bonus: 奖金
  • salary: 工资
  • performance: 绩效,性能
  • strategy: 策略
  • strategy pattern 策略模式
(1) 未经过任何优化的写法
- 缺点
  - 具有很多if...else语句
  - 缺乏扩展性:如要添加C绩效,则需要修改内部的函数实现,违反开放封闭原则
  - 复用性差
function getBonus(performance, salary) { // bonus奖金 performance绩效 salary奖金
  if ( performance === 'S') return 4 * salary;
  if ( performance === 'A') return 3 * salary;
  if ( performance === 'B') return 2 * salary;
}
getBonus('S', 1000) // 输出:4000



---------------
(2) 使用 - 函数组合 - 重构代码
function getS (salary) { return 4 * salary }
function getA (salary) { return 3 * salary }
function getB (salary) { return 2 * salary }
function getBonus(performance, salary) {
  if ( performance === 'S') return getS(salary)
  if ( performance === 'A') return getA(salary)
  if ( performance === 'B') return getB(salary)
}
getBonus('S', 1000) // 输出:4000



---------------
(3) 使用 - 策略模式strategyPattern - 重构代码
- 优点
  - 消除了大量if...else语句
  - 所有计算奖金bonus的逻辑都不在getBonus函数即环境类context中,而是分布在各个策略对象strategy中
  - context环境类不负责计算,只是负责将请求委托给strategy策略类
const strategy = {
  S: (salary) => 4 * salary,
  A: (salary) => 3 * salary,
  B: (salary) => 2 * salary,
}
const getBonus = (performance, salary) => strategy[performance](salary)
getBonus('S', 1000) // 输出:4000

(5-2) 表单验证

  • 未优化的代码如下
(1) 未优化的代码如下

<!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>
  <form action="javascript:void(0)" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <button>表单提交</button>
  </form>
  <script>
    const form = document.querySelector('form')
    form.onsubmit = () => {
      // form.username可以访问到 name="username" 的 input元素节点
      if (form.username.value === '') {
        console.log('用户名不能为空')
        return false
      }
      if (form.password.value.length < 6) {
        console.log('密码长度不能少于6位')
        return false
      }
    }
  </script>
</body>
</html>
  • 使用策略模式 - 优化表达验证
    • 第一步:将验证逻辑封装到 ( 策略对象strategy中 )
    • 第二步:将用户的请求通过 ( 环境对象context ) 委托给策略对象strategy来处理
(2) 使用策略模式 - 优化表达验证

<!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>
  <form action="javascript:void(0)" method="post">
    <input type="text" name="username">
    <input type="password" name="password">
    <button>表单提交</button>
  </form>
  <script>
    // 策略对象 strategy
    // 注意:
    // 1. 这里每个方法可以能有 ( 两个 ) 或者 ( 三个参数 )
    // 2. 很巧妙的利用了 
    //    - "isEmpty".split(';') => ["isEmpth"].shift() => [].unshift(value) => [value].push(err) => [value, err]
    //    - "minLength:6".split(";") => ["minLength", "6"].shift() => ['6'].unshift(value) => [value, '6'].push(err) => [value, '6', err]
    //    - 最后就是两种情况: [value, err] 或 [value, minLength, err]
    // ------------------------------------ strategys
    const strategys = {
      notEmpty: (value, err) => { if (value === '') return err },
      minLength: (value, minLength, err) => { if (value.length < minLength) return err },
    }

    // 环境类 context
    // ------------------------------------ Validator
    class Validator {
      constructor() {
        this.rules = [] // 规则数组,用于存放所有的规则函数
      }
      add = (dom, rule, err) => {
        const ruleToArr = rule.split(':') // ['isEmpty'] 或 ['minLength', 6]
        this.rules.push(() => { // 每次没push一个匿名函数到this.rules中
          const strategy = ruleToArr.shift() // 'isEmpty' 或 'minLength'
          ruleToArr.unshift(dom.value) // 头部添加
          ruleToArr.push(err) // 尾部添加
          return strategys[strategy].apply(null, ruleToArr) // ruleToArr = [value, err] 或 [value, minLength, err]
        })
      }
      start = () => {
        for(let i = 0; i < this.rules.length; i++) { // 遍历执行每一个函数
          let err = this.rules[i]();
          if (err) { return err }
        }
      }
    }
    
    // ------------------------------------ form元素节点
    const form = document.querySelector('form')
    form.onsubmit = (e) => {
      e.preventDefault()
      const validator = new Validator()
      validator.add(form.username, 'notEmpty', '用户名不能为空') // add
      validator.add(form.password, 'minLength:6', '密码不能少于6位') // add
      const err = validator.start() // start
      if (err) {
        console.log('err', err)
        return false
      }
    }
  </script>
</body>
</html>
image

(3) 单例模式 singleton

(1) 定义

  • 保证一个类只有一个实例,并提供一个访问它的全局访问点
  • 优点
    • ( 创建对象 ) 和 ( 管理单例 ) 被分布在不同的方法中

(2) 应用场景

  • 全局变量
  • 连接数据库,防止多次连接或断开
  • 全局状态管理redux vuex
  • 登陆框,form表单,loading层等

(3) 实现

(3-1) 闭包实现一个单例

  • 缺点:需要调用 getInstance 函数创建对象
function Singleton(name) { // 构造函数, singleton: 单身, 注意:箭头函数不能作为构造函数,arguments,yeild命令
  this.name = name
}
Singleton.getInstance = (() => {
  let instance = null // 闭包变量
  return (name) => {
    if (!instance) {
      instance = new Singleton(name)
    }
    return instance
  }
})(); // IIFE立即调用的函数表达式,注意小括号和中括号开头的前一条语句需要加分号,或者小括号中括号前加分号

const a = Singleton.getInstance('A')
const b = Singleton.getInstance('B') // 这一次传参没用了
console.log('a === b', a === b) // true

(3-2) 函数实现一个单例

  • 缺点:需要调用 getInstance 函数创建对象
function Singleton(name) {
  this.name = name
}
Singleton.getInstance = function(name) {
  // 注意:
  //  1. 这里的 this 指向的是 Singleton ,而不是 Singleton生成的实例
  if (!this.instance) {
    this.instance = new Singleton(name)
  }
  return this.instance
}
const a = Singleton.getInstance('A')
const b = Singleton.getInstance('B')
console.log('a === b', a === b)
console.log('a', a)
console.log('b', b)
console.log('Singleton.instance', Singleton.instance) // instance直接是Singleton的属性

(3-3) 透明的单例模式

  • 优点:解决12中的需要调用 createInstance 函数来生成对象的缺点,这里直接通过new调用
  • 缺点:不符合单一职责原则,这个对象其实负责了两个功能:单例和创建对象
var Singleton = (function() {
  var instance = null
  return function(name) {
    if (instance) return instance
    this.name = name
    return instance = this // 返回实例对象,instance仅仅是为了做判断
  }
})(); // IIFE返回一个构造函数
const a = new Singleton('A')
const b = new Singleton('B')
console.log('a === b', a === b)
console.log('a', a)

(3-4) es6实现单例

class Book {
  constructor(name) {
    if (!Book.intance) {
      this.name = name
      Book.intance = this
    }
    return Book.intance
  }
}
const english = new Book('english')
const chinese = new Book('chinese')
console.log('english === english', english === english)
console.log('english', english)
console.log('chinese', chinese)

(4) 实战 - 登陆框

<!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>
  <button id="login">登陆</button>
  <script>
    function init() {
      const button = document.getElementById('login')
      button.addEventListener('click', showModal, false)
    }
    init() // 绑定监听
    
    var singleton = (() => {
      var instance = null
      return function(fn) {
        if (!instance) {
          instance = fn.call(null)
        }
        return instance
      }
    })() // IIFE单例子模式

    function createModal() {
      const div = document.createElement('div')
      div.innerHTML = '登陆框'
      div.style.setProperty('width', '300px')
      div.style.setProperty('height', '300px')
      div.style.setProperty('border', '1px solid black')
      document.body.appendChild(div)
      return div
    }

    function showModal() {
      singleton(createModal)
    }

  </script>
</body>

</html>
image

资料

相关文章

  • [深入23] JS设计模式 - 代理,策略,单例

    导航 [react] Hooks[https://juejin.im/post/68449040453421137...

  • 前端设计模式

    JS设计模式一:工厂模式jS设计模式二:单例模式JS设计模式三:模块模式JS设计模式四:代理模式JS设计模式五:职...

  • iOS开发中的几种设计模式

    目前常用的几种设计模式:代理模式、观察者模式、MVC模式、单例模式、策略模式、工厂模式、MVVM (一)代理 场景...

  • iOS开发中的几种设计模式

    目前常用的几种设计模式:代理模式、观察者模式、MVC模式、单例模式、策略模式、工厂模式、MVVM (一)代理 场景...

  • 2020-09-29

    目前常用的几种设计模式:代理模式、观察者模式、MVC模式、单例模式、策略模式、工厂模式、MVVM (一)代理 场景...

  • (IOS)设计模式

    目前常用的几种设计模式:代理模式、观察者模式、MVC模式、单例模式、策略模式、工厂模式、MVVM (一)代理 场景...

  • 常用设计模式

    6.设计模式1> 常用的设计模式代理 观察者 工厂 单例 策略2> 代理属性的内存策略是什么,为什么?3...

  • ios 开发模式

    目前常用的几种设计模式:代理模式、观察者模式、MVC模式、单例模式、策略模式、工厂模式、MVVM 1.代理 场景:...

  • Retrofit源码解析

    设计模式:建造者模式、工厂方法模式、外观模式、代理模式、单例模式、策略模式、装饰模式、适配器模式、代理模式 一、R...

  • 设计模式(Swift) - 单例模式、备忘录模式和策略模式

    设计模式(Swift) - 单例模式、备忘录模式和策略模式 设计模式(Swift) - 单例模式、备忘录模式和策略模式

网友评论

      本文标题:[深入23] JS设计模式 - 代理,策略,单例

      本文链接:https://www.haomeiwen.com/subject/zzopnltx.html