javascript常用设计模式介绍,实现及实际应用(一) - Go语言中文社区

javascript常用设计模式介绍,实现及实际应用(一)


javascript设计模式介绍,实现及实际应用(一)

本文将介绍javascript中常用的设计模式原理和实现,并结合实例讲解其应用。

本篇文章先介绍单例模式,策略模式,代理模式,发布订阅模式和命令模式,其它几种模式后续文章将继续介绍。

1、单例模式

单例模式就是一个实例在整个网页的生命周期里只创建一次,后续再调用实例创建函数的时候,返回的仍是之前创建的实例。在实际开发中应用十分广泛,例如页面中的登录框,显示消息的提示窗,都只需要一个实例即可。
我们以消息提示框为例,展示如何实现单例模式。代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>singleTon</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    /* 提示框动画和样式 */
    @keyframes appear{
      from {
        left: 100%;
      }
      to {
        left: 50%;
      }
    }
    .message-tip{
      animation: appear .5s linear;
      position: fixed;
      width: 200px;
      background: red;
      color: #fff;
      line-height: 1.5;
      padding-left: 5px;
      top: 20px;
      left: 50%;
      border: 1px solid #d3d3d3;
      transform: translateX(-50%);
    }
  </style>
</head>
<body>
  <button id="tipsBtn" onclick="showMsg()">提示消息</button>
  <script>
    //创建提示框单例
    let createTips = (function(){
      let tipDom = null;
      function createDom() {
        let div = document.createElement('div');
        div.setAttribute('class', 'message-tip');
        return div;
      }

      return function(msg) {
        if (!tipDom) {
          tipDom = createDom();
        }
        tipDom.innerHTML = msg;
        return tipDom;
      }
    })();

   //验证是够是单例,打印true,则是单例模式
  console.log(createTips('1') === createTips('2'));

   function showMsg() {
     let tips = createTips('这是消息提示');
     document.body.appendChild(tips);
      setTimeout(() => {
        document.body.removeChild(tips);
      }, 5000);
   }
  </script>
</body>
</html>

代码讲解:使用createTips函数创建的div时,如果原先已经创建了div,则直接调用原来的div,不再重新创建。除了提示框,网页中一些登录框等等也可以用单例模式实现,网页的整个生命周期里,只有一个dom被创建。或者js的代码中的某个实例如果共享的,只需要一次,就使用单例模式创建。window对象也是一个单例,整个生命周期里只有一个window对象。

2、策略模式

策略模式是指将策略(算法)封装起来,策略的目的是将算法和使用分离开。

我们假设有这样一个应用:

年终绩效评定,有S,A,B,C四种绩效,对应年终奖为4,3,2,1个月工资,设计函数,计算绩效。函数如下:

function calBonus(grade, salary) {
  if (grade === 'S') {
    return salary*4;
  } else if (grade === 'A') {
    return salary*3;
  } else if (grade === 'B') {
    return salary*2;
  } else {
    return salary;
  }
}

函数里各种判断条件,如果算法实现比较复杂,这个函数会异常庞大。我们用策略模式改造函数可如下:

//计算绩效为S的年终
function S(salary) {
  return salary * 4;
}

//策略使用
function calBonus(fn, salary) {
  return  fn(salary);
}

//计算绩效为S,工资为20000的年终
calBonus(S, 20000);

这种方法改写代码后,代码的扩展性就好了一些,实际的算法由函数封装起来,代码的扩展性强了,也方便复用。

这就是策略模式的应用之一,在代码中,将具体的算法或策略封装起来,和使用的场景分离,可以提高代码扩展性和复用率。

代理模式

代理模式很好理解,我们不能直接使用目标函数,而是通过调用代理函数来实现对目标函数的使用。

对于网络代理这个词语,每个同学应该都了解,就是无法直接上网,把上网的请求都发送到代理服务器,由代理服务器请求数据,然后转发给相应人员。

现在有一个场景,某个网页有很多请求,有部分请求是不被允许的,我们使用代理模式实现这一功能。代码如下:

//允许的请求地址
let urlArr = ['/aaa', '/bbb'];

//正常的ajax请求函数,url请求地址,method请求方法, data请求数据,callback回调函数
function commonAjax(url, method, data, callback) {
  //具体实现略过,这个大家应该都知道
}

//代理请求函数
function proxyAjax(url, method, data, callback) {
  if (urlArr.indexOf(url) > -1) {
    commonAjax(url, method, data, callback);
  } else {
    let data = {
      status: false,
      message: '该请求不被允许',
      code: 403
    }
    callback(data);
  }
}

代码解析:urlArr是我们允许的请求地址,其它请求不允许发送,commonAjax是正常的请求函数,proxyAjax是代理请求函数。

注意:代理接口和实际函数接口要保持一致

后续需求发生变化,不需要限制任何请求,直接调用请求函数即可。现在大部分公司联网都是有限制的,过滤一部分网站,只有公司白名单里的网站才能访问,其功能和上述代码逻辑是一致的。

发布订阅模式

发布订阅模式在实际应用中非常常见,例如,我们在微信App上关注了某个公众号,当该公众号有新文章发布时,就会通知我们。

发布订阅模式定义了一种一对多的依赖关系,当“一”发生变化,通知多个依赖。

为了好理解,先说明几个名词含义,我们以微信关注公众号为例讲解,如下:

subscriber: 订阅者,我们关注了公众号,我们就是订阅者。

subject:主题,微信公众号的唯一。

publisher: 消息发布者,公众号的维护者,不断发布新的文章。

当然还有一个调度中心,这样就是完整的发布订阅模式了。

现在我们就实现发布订阅模式。代码如下:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>发布订阅模式实例</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    .main{
      display: flex;
    }
    .msg-item{
      width: 300px;
      border-right: 3px solid #d3d3d3;
      min-height: 400px;
    }
    
  </style>
</head>
<body>
  <div class="main">
    <div class="msg-item">
      <div>
        <input type="text" id="subject" placeholder="输入订阅主题" style="width: 150px">
        <button onclick="subscribe()">订阅消息</button>
      </div>
      <h3>已订阅消息列表</h3>
      <ul id="subscribe_list">
      </ul>
      <h3>接收到消息列表</h3>
      <ul id="message_list">
      </ul>
    </div>
    <div class="msg-item">
        <input type="text" id="pub_subject" placeholder="输入发布主题" style="width: 150px">
        <input type="text" id="pub_message" placeholder="输入发布消息" style="width: 200px">
        <button onclick="publishMsg()">发布消息</button>
        <h3>发布消息列表</h3>
        <ul id="publish_list">
        </ul>
    </div>
  </div>
  <script>
    //调度中心算法实现
    function pubsub() {
      //主题列表
      this.subjects = {};
    };

    //发布消息
    pubsub.prototype.publish = function(type, message) {
      if (this.subjects[type]) {
        this.subjects[type].list.map(item => {
          //异步调用
          setTimeout(function(){
            item.fn && item.fn(type, message);
          }, 0);
        });
      }
    }

    //订阅消息
    pubsub.prototype.subscribe = function(type, callback) {
      if (!this.subjects[type]) {
        this.subjects[type] = {
          count: 0,
          list: []
        }
      }
      let subId = type + '-' + this.subjects[type].count++;
      this.subjects[type].list.push({
        fn: callback,
        id: subId
      })
      return subId;
    }

    //移除订阅,参数为订阅时返回的id
    pubsub.prototype.remove = function(id) {
      let arr = id.split('-');
      if (arr.length > 1) {
        if (this.subjects[arr[0]]) {
          let len =  this.subjects[arr[0]].list.length;
          for (let i = 0; i < len; i++) {
            if (this.subjects[arr[0]].list[i].id === id) {
              this.subjects[arr[0]].list.splice(i, 1);
              return true;
            }
          }
        }
      }
      return false;
    }

    ///
    //以上是调度中心的代码

    /**
     * 订阅函数
     * 三个参数,pubsub调度中心实例,type订阅主题,callback订阅回调函数
     * */
    function subscriber(pubsub, type, callback) {
      if (pubsub && pubsub.subscribe) {
        return pubsub.subscribe(type, callback);
      }
      return false;
    }

     /**
     * 发布消息函数
     * 三个参数,pubsub调度中心实例,type发布主题,message发布消息
     * */
     function publish(pubsub, type, message) {
      if (pubsub && pubsub.publish) {
        pubsub.publish(type, message);
      }
    }
  </script>
  <script>
    //调度中心实例
    const pubsubInstance = new pubsub();
    //数据全局类,存储页面要展现的变量等数据
    const global = {
      subscribeList: [], //订阅消息列表
      publishList: []
    };

    //订阅按钮处理函数
    function subscribe() {
      let type = document.getElementById('subject').value;
      if (global.subscribeList.indexOf(type) === -1) {
        if (subscriber(pubsubInstance, type, handleMsg)) {
          global.subscribeList.push(type);
          let subscribeList = document.getElementById('subscribe_list');
          subscribeList.innerHTML += `<li>${type}</li>`;
        }
      }
    }

    //发布按钮处理函数
    function publishMsg() {
      let type = document.getElementById('pub_subject').value;
      let message = document.getElementById('pub_message').value;
      publish(pubsubInstance, type, message);
      let publishList = document.getElementById('publish_list');
      publishList.innerHTML += `<li>${type}: ${message}</li>`;
    }

    //订阅消息的回调函数
    function handleMsg(type, msg) {
      let messageList = document.getElementById('message_list');
      messageList.innerHTML += `<li>${type}: ${msg}</li>`;
    }
  </script>
</body>
</html>

以上是发布订阅模式的实现和测试代码,代码中有注释,不难理解,就不再解释了,测试结果如下:


左侧上方是订阅的消息主题列表,订阅news和food这两个主题。

左侧下方是接收到的消息列表,接收到两条消息,主题分别是news和food。

右侧是发布的消息列表,发布了3个主题的消息,每个主题一条(分别是news,vedio,food)。

订阅者只订阅了news和food新闻,所以右侧发布了3条新闻,但是关于news和food只有两条,订阅者只接收了两条关注的新闻。

命令模式

所谓命令模式就是将下要执行的业务逻辑封装到一个函数或类中,不需要具体谁来执行该命令的。在javascript中,命令模式最好的体现就是回调函数。

//ajax请求函数
function httpRequest(url, params, method, success, error) {
  //具体实现省略
  $.ajax({
    url: url,
    data: params,
    method: method,
    success: function(res) {
      success(res);
    },
    error: funtion(err) {
      error(err);
    }
  })
}

//具体调用,假设有登录请求,url为"/login",post方法
httpRequest('/login', {}, 'POST', loginSucess, loginFail);
//实现请求和具体操作的解耦,所有请求均可使用httpRequest方法,具体的success和error可单独定义。

//登录成功
function loginSuccess(data) {
  //登录成功需要做的业务处理。
} 
//登录失败
function loginFail(err){
  //登录失败操作
}

本文就介绍到这,其它几种模式将在第二篇文章进行介绍。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u011350550/article/details/84430880
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢