用一个案例彻底搞懂观察者模式

很多时候学习js的设计模式,都是看别人写的优雅的代码和一些源码里面的实现。

到了自己写代码的时候,却经常想不起来。。。

可能是因为对设计模式理解的还不够透彻

来,看需求

现有一个html和纯js写一个非常简单的页面,如图所示

每个节点对应的都是一个链接,点击可以跳转

现在的需求是要在 每个节点后面加一个 网站测速(比较简单的功能,就是测试以下从去请求链接到接口相应的时间差),就像这样

思路就是:

使用ajax来请求链接,在发送请求的时候,记录一个时间戳,接口有响应的时候,再给一个时间戳,然后两者相减,就是这个时间差了

html 复制代码
<!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>前端测试和设计模式</title>
</head>
<style>
  html,
  body {
    min-width: 1200px;
    min-height: 600px;
    height: 100vh;
    width: 100%;
    margin: 0;
    padding: 0;
    font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
      Microsoft YaHei, SimSun, sans-serif;
    -webkit-font-smoothing: antialiased;
  }

  .box {
    width: 100%;
    height: 100vh;
    display: flex;
    box-sizing: border-box;
    justify-content: center;
    align-items: center;
    position: relative;
  }

  .bg {
    position: absolute;
    width: 100%;
    height: 100vh;
  }

  ul {
    margin-right: 160px;
  }

  li {
    list-style: none;
    margin-bottom: 24px;
  }

  li:first-child {
    font-size: 24px;
    font-family: PingFangSC-Semibold, PingFang SC;
    font-weight: 600;
    color: #ff7e0c;
    line-height: 33px;
    margin-bottom: 40px;
  }

  .logo1 {
    margin-left: 4px;
  }

  a {
    display: flex;
    text-decoration: none;
    width: 364px;
    height: 49px;
    align-items: center;
    background: #ffffff;
    border-radius: 10px;
    font-size: 16px;
    font-family: PingFangSC-Regular, PingFang SC;
    font-weight: 400;
    color: #4e4e4e;
  }

  .new_a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-right: 2vw;
    box-sizing: border-box;
  }

  .new_a .flex {
    display: flex;
    align-items: center;
  }

  .diff {
    color: #0eb388;
  }

  .circle {
    width: 20px;
    height: 20px;
    margin: 0 15px;
  }

  .content {
    position: absolute;
    z-index: 99;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 188px;
    box-sizing: border-box;
  }
</style>

<body>
  <div class="box">
    <img src="./assets/bg.png" alt="" class="bg" />
    <div class="content">
      <ul class="ul">
        <li>
          节点选择
          <img src="./assets/logo1.png" alt="" class="logo1" />
        </li>
      </ul>
    </div>
  </div>

</body>
 <script>
    let nodeUrl = [
      {
        name: '节点1',
        skipUrl: 'https://xxxxx.com/', // 跳转链接
        requestUrl: 'https://xxxxx.com/login', // 跳转链接是不会响应的 所以就干脆用这个链接下的登录接口也是一样的
        method: 'POST',
        startNum: 0,
        endNum: 0,
        diff: 0,
        ContentType: 'application/json'
      },
      {
        name: '节点2',
        skipUrl: 'https://xxxxx1.com/',
        requestUrl: 'https://xxxxx1.com/login',
        method: 'POST',
        startNum: 0,
        endNum: 0,
        diff: 0,
        ContentType: 'application/json'
      },
      {
        name: '节点3',
        skipUrl: 'https://xxxxx2.com/',
        requestUrl: 'https://xxxxx2.com/login',
        method: 'POST',
        startNum: 0,
        endNum: 0,
        diff: 0,
        ContentType: 'application/json'
      },
    ];
    let timer = null
    const ul = document.querySelector('ul')
    let child = ''
    nodeUrl.forEach(i => {
      child += ` <li>
          <a href="${i.skipUrl}">
            <img src="./assets/yonghu.png" alt="" class="circle" /> ${i.name}</a>
        </li>`
    });
    ul.innerHTML += child;


    nodeUrl.map(i => {
      const xhr = new XMLHttpRequest();
      xhr.open(i.method, i.requestUrl);
      if ((i.method === 'POST' || i.method === 'post')) {
        // xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.setRequestHeader("Content-type", i.ContentType);
      }
      i.startNum = Date.now();
      xhr.send();
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          i.endNum = Date.now();
          i.diff = i.endNum - i.startNum;
          return i
        }
      }
    })

    console.log('nodeUrl', nodeUrl);
    child = '';
    ul.innerHTML = ` <li>
          节点选择
          <img src="./assets/logo1.png" alt="" class="logo1" />
        </li>`;
    nodeUrl.forEach(i => {
      console.log('i', i);
      console.log('i.diff', i.diff);
      child += ` <li>
          <a href="${i.skipUrl}" class='new_a'>
            <span class='flex'>
              <img src="./assets/yonghu.png" alt="" class="circle" />
            <span>${i.name}</span>
            </span>

            <span class='diff'>${i.diff}&nbsp;ms</span>
            </a>
        </li>`
    });
    ul.innerHTML += child;

  </script>
</html>

结果和想象的不一样, 看下打印信息

那个 nodeUrl 和 i 打印出来都看到 diff 有值,但是打印 i.diff 就是 0

这个问题就不用多说了,js的经典问题之异步 因为 ajax是异步的

等网络请求回来的时候 后面的代码早都执行了

这个问题就可以使用观察者模式来搞

什么是观察者模式?

用我自己的概述就是 可以对某些事件可以进行观察和通知,一旦观察目标有变化就立即执行对应的事件。

总体来说有几个环节:

  1. 存储事件
  2. 观察事件
  3. 触发对应的事件

来看下改造的代码

js 复制代码
<script>
  let pool = []; // 使用一个事件池来存储事件
  let nodeUrl = [
      {
        name: '节点1',
        skipUrl: 'https://xxxxx.com/', // 跳转链接
        requestUrl: 'https://xxxxx.com/login', // 跳转链接是不会响应的 所以就干脆用这个链接下的登录接口也是一样的
        method: 'POST',
        startNum: 0,
        endNum: 0,
        diff: 0,
        ContentType: 'application/json'
      },
      {
        name: '节点2',
        skipUrl: 'https://xxxxx1.com/',
        requestUrl: 'https://xxxxx1.com/login',
        method: 'POST',
        startNum: 0,
        endNum: 0,
        diff: 0,
        ContentType: 'application/json'
      },
      {
        name: '节点3',
        skipUrl: 'https://xxxxx2.com/',
        requestUrl: 'https://xxxxx2.com/login',
        method: 'POST',
        startNum: 0,
        endNum: 0,
        diff: 0,
        ContentType: 'application/json'
      },
    ];
  let timer = null
  const ul = document.querySelector('ul')
  let child = ''
  nodeUrl.forEach(i => {
    child += ` <li>
        <a href="${i.skipUrl}">
          <img src="./assets/yonghu.png" alt="" class="circle" /> ${i.name}</a>
      </li>`
  });
  ul.innerHTML += child;



  getDiff()

  function getDiff() {
    pool = [];
    nodeUrl.forEach(i => {
      const xhr = new XMLHttpRequest();
      xhr.open(i.method, i.requestUrl);
      if ((i.method === 'POST' || i.method === 'post')) {
        // xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.setRequestHeader("Content-type", i.ContentType);
      }
      i.startNum = Date.now();
      xhr.send();
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          i.endNum = Date.now();
          i.diff = i.endNum - i.startNum;
          pool.push(i) // 向事件池里面存储事件
          generrate() // 主动去触发 
        }
      }
    })
  }

  timer = setInterval(() => {
    getDiff()
  }, 3000)

  function generrate(diff = false) {
    if (pool.length === 3) { // 我不管你是异步还是什么情况 我只看住我自己的底线  pool的length为3的时候 就是所有异步执行完了
      child = '';
      ul.innerHTML = ` <li>
        节点选择
        <img src="./assets/logo1.png" alt="" class="logo1" />
      </li>`
      nodeUrl.forEach(i => {
        child += ` <li>
        <a href="${i.skipUrl}" class='new_a'>
          <span class='flex'>
            <img src="./assets/yonghu.png" alt="" class="circle" />
          <span>${i.name}</span>
          </span>

          <span class='diff'>${i.diff}&nbsp;ms</span>
          </a>
      </li>`
      });
      ul.innerHTML += child;
    }

  }


  window.onbeforeunload = function (event) {
    clearInterval(timer)
    timer = null;
  };
</script>

就完美解决这个需求了

相关推荐
逐·風2 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫3 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦3 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子4 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山4 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享5 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
从兄5 小时前
vue 使用docx-preview 预览替换文档内的特定变量
javascript·vue.js·ecmascript
捕鲸叉6 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
wrx繁星点点6 小时前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
凉辰6 小时前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式