(设计模式)6.代理模式

272 阅读5分钟

说明:继续学习努力
PS:学习自---掘金的JavaScript设计模式核心原理与应用小册

一、概念

代理模式,式如其名————在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。

二、例子描述

  • 1.当我们网上冲浪的时候,我们都知道,国外有一些网站,如谷歌都被了,这时候我们就不能够访问了。但是我们可以通过使用VPN构建代理服务器然后就能完成访问谷歌
  • 2.普通用户在网址输入google.com时,就会进行DNS域名解析,但是呢,在域名解析获取ip地址然后发送给目标服务器,恰好这个ip地址属于被屏蔽的网站,这时候就不会进行下面操作,直接告诉你无法访问此网站。

image.png

  • 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.效果展示 image.png

虚拟代理

  • 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属性从而达到出现可视视图内的图片进行加载。

image.png

  • 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.效果展示

image.png

保护代理

  • 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.说明 当民众信息有攻击性或者携带武器,就不能访问皇帝的信息

image.png