说明:继续学习努力
PS:学习自---掘金的JavaScript设计模式核心原理与应用小册
一、概念
代理模式,式如其名————在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。
二、例子描述
- 1.当我们网上冲浪的时候,我们都知道,国外有一些网站,如
谷歌都被墙了,这时候我们就不能够访问了。但是我们可以通过使用VPN构建代理服务器然后就能完成访问谷歌 - 2.普通用户在网址输入
google.com时,就会进行DNS域名解析,但是呢,在域名解析获取ip地址然后发送给目标服务器,恰好这个ip地址属于被屏蔽的网站,这时候就不会进行下面操作,直接告诉你无法访问此网站。
- 3.聪明的程序员搭建了一个代理服务器,当程序员访问
google的时候先完成ip地址解析,然后先指向了代理服务器,代理服务器先访问了目标服务器并且返回了结果给代理服务器。然后代理服务器将内容转发给了你,你就可以看到网站的内容了。
三、代理模式案例
事件代理
- 1.案例: 它的场景是一个父元素下面有多个子元素,如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>事件代理</title>
</head>
<body>
<div id="father">
<a href="#">链接1号</a>
<a href="#">链接2号</a>
<a href="#">链接3号</a>
<a href="#">链接4号</a>
<a href="#">链接5号</a>
<a href="#">链接6号</a>
</div>
</body>
</html>
现在的需求是,希望鼠标点击每个a标签,都可以弹出“我是xxx”这样的提示。比如点击第一个a标签,弹出“我是链接1号”这样的提示。
-
2.第一步思路 正常情况下,我们一般人的思维是这样子,点击了谁就输出谁,在所有a链接上都绑定点击事件,然后点击谁就提示相应的文案
-
3.利用冒泡事件完成代理 说明:当我们点击a标签的时候,然后会因为冒泡机制触发它父类的标签,这个时候我们就可以绑定a标签的父类元素来绑定点击事件,然后获取对应子元素的内容就知道是谁点击的了。代码如下
<script>
// 利用冒泡 点击子元素会触发父元素
const father = document.getElementById('father');
// 给父元素添加监控方法
father.addEventListener('click', function(e) {
// 识别是否是目标子元素: a链接
if(e.target.tagName === 'A') {
e.preventDefault(); // 阻止a链接的默认事件
alert(`我是${e.target.innerText}`);
}
})
</script>
- 4.效果展示
虚拟代理
-
1.说明:
虚拟代理这里介绍两个常用的案例,一个是图片懒加载,一个是图片预加载。 -
2.图片懒加载
<!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>
<style>
.pic {
margin-bottom:20px;
text-align: center;
}
.pic img {
width: 200px;
height:200px;
background-color: gray;
}
</style>
</head>
<body>
<div class="pic">
<img data-src="./img/pic-1.jpg">
</div>
<div class="pic">
<img data-src="./img/pic-2.jpg">
</div>
<div class="pic">
<img data-src="./img/pic-3.jpg">
</div>
<div class="pic">
<img data-src="./img/pic-4.jpg">
</div>
<div class="pic">
<img data-src="./img/pic-5.jpg">
</div>
<div class="pic">
<img data-src="./img/pic-6.jpg">
</div>
<script>
// 说明:实现图片懒加载
// 实现思路:计算出页面可视区域内图片标签进行加载
const imgs = document.getElementsByTagName('img');
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
// num 用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
let num = 0
function lazyload() {
for(let i = num; i<imgs.length; i++) {
// 用可视区域高度减去元素顶部距离可视区域顶部的高度
let distance = viewHeight - imgs[i].getBoundingClientRect().top;
if(distance >= 0) {
// 给元素写入真实的src,展示图片
imgs[i].src = imgs[i].getAttribute('data-src')
// 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
num = i + 1
}
}
}
// 初始化的时候加载图片
lazyload()
// 监听鼠标发生滚动
window.addEventListener('scroll', lazyload, false)
</script>
</body>
</html>
效果展示:当我们进行鼠标滑动的时候,当图片出现在可视视图范围内,那么就给其设置src属性从而达到出现可视视图内的图片进行加载。
- 3.图片预加载 说明:预加载主要是为了避免网络不好、或者图片太大时,页面长时间给用户留白的尴尬。常见的操作是先让这个 img 标签展示一个占位图,然后创建一个 Image 实例,让这个 Image 实例的 src 指向真实的目标图片地址、观察该 Image 实例的加载情况 —— 当其对应的真实图片加载完毕后,即已经有了该图片的缓存内容,再将 DOM 上的 img 元素的 src 指向真实的目标图片地址。此时我们直接去取了目标图片的缓存,所以展示速度会非常快,从占位图到目标图片的时间差会非常小、小到用户注意不到,这样体验就会非常好了。
class PreLoadImage {
constructor(imgNode) {
// 获取真实的DOM节点
this.imgNode = imgNode
}
// 操作img节点的src属性
setSrc(imgUrl) {
this.imgNode.src = imgUrl
}
}
class ProxyImage {
// 占位图的url地址
static LOADING_URL = 'xxxxxx'
constructor(targetImage) {
// 目标Image,即PreLoadImage实例
this.targetImage = targetImage
}
// 该方法主要操作虚拟Image,完成加载
setSrc(targetUrl) {
// 真实img节点初始化时展示的是一个占位图
this.targetImage.setSrc(ProxyImage.LOADING_URL)
// 创建一个帮我们加载图片的虚拟Image实例
const virtualImage = new Image()
// 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
virtualImage.onload = () => {
this.targetImage.setSrc(targetUrl)
}
// 设置src属性,虚拟Image实例开始加载图片
virtualImage.src = targetUrl
}
}
补充:现在很多企业都在用骨架屏来解决这一类问题,后面会补充介绍这个。
缓存代理
- 1.概念 说明:它应用于一些计算量较大的场景里。在这种场景下,我们需要“用空间换时间”---当我们需要用到某个已经计算过的值的时候,不想再耗时进行二次计算,而是希望能从内存里去取出现成的计算结果。
- 2.代码案例
<!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>
<style>
</style>
</head>
<body>
<script>
// 正常求和方法
const addAll = function() {
console.log('进行一次新计算');
let result = 0;
const len = arguments.length;
for(let i = 0; i < len; i++) {
result += arguments[i]
}
return result;
}
// 为求和方法创建代理
const proxyAddAll = (function(){
// 求和结果的缓存池
const resultCache = {}
return function() {
// 将入参转化为一个唯一的入参字符串
const args = Array.prototype.join.call(arguments, ',')
// 检查本次入参是否有对应的计算结果
if(args in resultCache) {
// 如果有,则返回缓存池里现成的结果
return resultCache[args]
}
return resultCache[args] = addAll(...arguments)
}
})()
</script>
</body>
</html>
- 3.效果展示
保护代理
-
1.概念 举例说明:古代皇帝出游体察民情慰问百姓时,皇帝不能直接接见当地的每个人,这时候需要我们护卫统领结合当地官员给的信息做一个筛选,就是得筛选掉不安全的人(携带武器、有异常行为、等等)这样子才能保护皇帝的安全
-
2.代码案例
// 构建皇帝信息
const emperor = {
// 姓名
name: '康泽',
// 身高
height: '175',
// 体重
weight: '60kg',
// 职位
job: 'leader',
// 介绍
intro: '中国封建社会统治者'
}
// 皇帝基本信息 大众都知道的
const baseInfo = ['name', 'job', 'intro'];
// 私密信息
const privateInfo = ['height', 'weight'];
// 民众信息
const person = {
isAttacked: false, // 是否有攻击性
ownArms: false, // 是否携带武器
}
// 护卫进行保护
const saveProxy = new Proxy(emperor, {
get: function(emperor, key){
if(person.isAttacked || person.ownArms) {
alert('该用户不能见皇帝')
return;
}
return emperor;
}
})
console.log(saveProxy.height);
- 3.说明 当民众信息有攻击性或者携带武器,就不能访问皇帝的信息