代理模式

代理模式会创造一个对象作为目标对象的代理,来控制对目标对象的访问。

代理模式在生活中的场景很多,比如很多公司都想找明星帮他们进行商业演出,他们不可能都联系明星,而是会联系明星的经纪人,经纪人会安排好明星应该在什么时候去参加什么活动,之后再将行程安排告诉明星即可。在这个例子中,经纪人就可以理解为明星的代理。

代理可分为保护代理和虚拟代理两种。保护代理会过滤掉一些请求,比如过滤掉费用小于10万元的活动。虚拟代理会将请求保存和整理,等到需要的时候再执行,比如经纪人在接到很多活动需求并做好下周的活动的安排后,在本周五花半小时的事件告诉明星即可,而不是每接到一个活动需求就告诉明星。

虚拟代理的应用

在前端领域,我认为虚拟代理的应用范围要广于保护代理,在web页面中,虚拟代理主要用于延迟加载、延迟初始化、短期内多个请求的合并等。下面我们来使用虚拟代理实现一个弹窗功能。

需求描述

由于弹窗并不是用户一定会看到的内容(比如当你浏览一个电商网站但不购买任何商品时,你不会看到购买成功的弹框),所以我们认为在页面加载时就家在弹框的内容是一种对带宽的浪费,尤其是当弹框内容包含一些图片时,就更增加了可能非必要的网络请求。因此,我们需要当用户触发弹窗时再去加载弹窗资源并动态生成弹框。

本项目需要在点击一个按钮时加载资源并弹框,弹框宽高为150px,内容为如下图片。弹框在出现后三秒自动关闭。

在加载弹框资源完成之前,需要在弹框位置提示用户资源正在加载中。

需求分析

通过对需求的理解我们发现,实现本过程大致可以分为资源加载未完成和已完成两部分。资源加载完成之前,我们需要加载资源并提示用户“资源正在加载中”,资源加载完成后,我们可以生成弹框,并让弹框在三秒后关闭。

刚刚我说过,延迟初始化是虚拟代理的一个重要应用。在本例中,如果没等到图片加载完毕就生成弹框,弹框中难免会出现一张图片未加载的图样。

代理与本体接口的一致性

在正式编写代码之前,我想我们还需要了解代理的一些重要的特性。在公司请明星参与商业演出的例子中,经纪人可以让整个流程更流畅,但是并不是说没有经纪人明星就无法参与演出。在代理模式中,我们需要本体和代理对于请求者同样可用,这就要求代理和本体的接口应该是一致的,这样如果我们某一天要取消使用代理,则可以直接改变请求目标,直接请求本体即可。

实现

必要的html文档

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo6</title>
</head>
<body>
    <button id="btn">弹窗按钮</button>
    <!--弹窗前的默认提示-->   
    <div id="alertDefault">加载中...</div>
</body>
</html>

本体的函数

//弹窗的函数
function myAlert(){
    //创造元素
    var div = document.createElement('div');
    //设置元素样式
    div.setAttribute("class","myAlert");
    //设置背景图片
    div.style.backgroundImage = "url(6.png)";
    //将创造的元素加入文档
    document.body.appendChild(div)
    //3秒后弹框消失
    setTimeout(function(){
        document.body.removeChild(div)
    },3000)
}

代理的函数

//弹框的代理函数
function myAlertProxy(){
    //先显示弹框
    var divDefault = document.getElementById("alertDefault");
    divDefault.style.display = "block"
    //加载图片
    var myImage = new Image();
    myImage.src="6.png";
    //当图片加载完成
    myImage.onload = function(){
        //隐藏默认图片
        divDefault.style.display = "none";
        //调用本体
        myAlert()
    }
}

绑定事件并请求

//选中按钮并添加事件
var btn = document.querySelector("#btn");
btn.addEventListener('click',function(){
    myAlertProxy()
})

全部代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo6</title>
    <style>
        .myAlert{
            width: 300px;
            height: 150px;
            background:no-repeat center/cover;
            position: absolute;
            left: 50%;
            top: 50%;
            margin-left: -150px;
            margin-top: -75px;
        }
        #alertDefault{
            width: 300px;
            border:1px solid #000;
            text-align: center;
            line-height: 150px;
            display: none;
            position: absolute;
            left: 50%;
            top: 50%;
            margin-left: -150px;
            margin-top: -75px;

        }
    </style>
</head>
<body>
    <button id="btn">弹窗按钮</button>
    <div id="alertDefault">加载中...</div>
    <script>
        window.onload = function(){
            //选中按钮并添加事件
            var btn = document.querySelector("#btn");
            btn.addEventListener('click',function(){
                myAlertProxy()
            })
            //弹框的代理函数
            function myAlertProxy(){
                //先显示弹框
                var divDefault = document.getElementById("alertDefault");
                divDefault.style.display = "block"
                //加载图片
                var myImage = new Image();
                myImage.src="6.png";
                //当图片加载完成
                myImage.onload = function(){
                    //隐藏默认图片
                    divDefault.style.display = "none";
                    //调用本体
                    myAlert()
                }
            }
            //弹窗的函数
            function myAlert(){
                //创造元素
                var div = document.createElement('div');
                //设置元素样式
                div.setAttribute("class","myAlert");
                //设置背景图片
                div.style.backgroundImage = "url(6.png)";
                //将创造的元素加入文档
                document.body.appendChild(div)
                //3秒后弹框消失
                setTimeout(function(){
                    document.body.removeChild(div)
                },3000)
            }
        }
    </script>
</body>
</html>

总结

  1. 这是一个简单的例子,不使用代理也可以完成,那么我们为什么要使用代理呢?
    的确本例是一个无需代理也可以完成的例子,但是如果使用一个函数去做全部的工作,这个函数无疑会变的十分巨大,这就对后期的维护埋下了坑。假如如果某一天,用户的网速足够快已经可以忽略掉请求一张图片的时间,那么我们完全可以取消这个预加载的过程,如果在当前的代理模式下,直接请求本体函数即可。但如果不使用代理模式,你不得不翻开代码仔细定位到底是哪些代码做了预加载,再一边删掉代码,一边担心删掉了代码可能会影响到其他部分进而导致的程序报错。

  2. 在在本例中,我们为了使逻辑更简单,在CSS中使用了id选择器。这种做法增加了javascript 和css的耦合程度,是一种非常不好的习惯,请牢记,任何时候都要用class控制样式。

  3. 弹框的弹出动画和全局的阴影读者可以自行拓展。