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

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

就完美解决这个需求了

相关推荐
z-robot6 分钟前
Nginx 配置代理
前端
用户479492835691514 分钟前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
没有故事、有酒25 分钟前
Ajax介绍
前端·ajax·okhttp
火车叨位去194928 分钟前
软件设计模式(tyutJAVA 状态模式实验)
设计模式·状态模式
朝新_29 分钟前
【SpringMVC】详解用户登录前后端交互流程:AJAX 异步通信与 Session 机制实战
前端·笔记·spring·ajax·交互·javaee
裴嘉靖31 分钟前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw28242634 分钟前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 小时前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
蒜香拿铁1 小时前
Angular【router路由】
前端·javascript·angular.js
brzhang2 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构