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

很多时候学习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>

就完美解决这个需求了

相关推荐
百万蹄蹄向前冲1 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5812 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路2 小时前
GeoTools 读取影像元数据
前端
ssshooter3 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友3 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry3 小时前
Jetpack Compose 中的状态
前端
dae bal4 小时前
关于RSA和AES加密
前端·vue.js
柳杉4 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog4 小时前
低端设备加载webp ANR
前端·算法
LKAI.5 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi