很多时候学习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} ms</span>
</a>
</li>`
});
ul.innerHTML += child;
</script>
</html>
结果和想象的不一样, 看下打印信息
那个 nodeUrl 和 i 打印出来都看到 diff 有值,但是打印 i.diff 就是 0
这个问题就不用多说了,js的经典问题之异步 因为 ajax是异步的
等网络请求回来的时候 后面的代码早都执行了
这个问题就可以使用观察者模式来搞
什么是观察者模式?
用我自己的概述就是 可以对某些事件可以进行观察和通知,一旦观察目标有变化就立即执行对应的事件。
总体来说有几个环节:
- 存储事件
- 观察事件
- 触发对应的事件
来看下改造的代码
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} ms</span>
</a>
</li>`
});
ul.innerHTML += child;
}
}
window.onbeforeunload = function (event) {
clearInterval(timer)
timer = null;
};
</script>
就完美解决这个需求了