使用合适的设计模式一步步优化前端代码

做者:晓飞 本文原创,转载请注明做者及出处javascript


在后端语言中,设计模式应用的较为普遍。如Spring中常见的工厂模式、装饰者模式、单例模式、迭代器模式。可是在平常的前端开发中,设计模式使用的较少,或者你们的代码已经遵循了某某设计模式可是咱们并不知道。常见的设计模式有23种,若是单纯的按照模式名称+名词解释的方式来写这篇文章,可能太枯燥了或者很难理解记忆,因此我打算换一种方式。下面咱们以一个例子开始咱们今天的文章。前端

假设咱们有一个这样的需求:
let page = {
  init: ()=>{
    //此处(placeA)有不少业务代码或者调用了不少page中的其余初始化函数
  },
  ....
};
复制代码

如今业务迭代,须要咱们在page.init()初始化代码块的最后增长一些功能,同时不影响原先的功能。按照正常的写法,咱们可能会像下面这样写:vue

let page = {
  init: ()=>{
    //placeA
    page.newFunction();
  },
  newFunction: ()=>{
    ...
  }
};
复制代码

这样写是能够解决咱们的需求,可是这样的代码是具备侵略性的,咱们不得不在原先的代码的合适位置新增咱们须要的代码。但咱们思考一个问题,若是咱们用了某个插件或者某个被ungly、minify以后的代码呢,咱们怎么在找到合适的位置添加咱们须要的功能呢?你们能够先本身思考一下,再看下面的内容。java

首先咱们先看解决方案,再思考其背后的东西。
//咱们能够在Function的原型链上定义一个扩展函数,以实现咱们的需求。
Function.prototype.fnAfter = function(fn) {
  var _self = this;
  return function() {
    _self.apply(this, arguments);
    fn.apply(this, arguments);
  }
};

page.init  = (page.init || function() {}).fnAfter(function() {
  console.log('咱们要追加的功能成功啦~');
});

page.init();
复制代码

上面的代码已经可以实现咱们的须要了,可是其实仍是不够好或者能够写的更灵活一些。由于我但愿能够能够作到像jquery的链式调用那样,能够一直日后面追加新的功能。那么咱们在上面代码的基础上再扩展下,其实很简单,咱们只要再Function.prototype.fnAfter中再返回自身就行了。jquery

Function.prototype.fnAfter = function(fn) {
  var _self = this;
  return function() {
    var fnOrigin = _self.apply(this, arguments);
    fn.apply(this, arguments);
    return fnOrigin;
  }
};
复制代码

其实上面的代码写法仍是能够优化的。好比:redux

//每次扩展的时候咱们都须要这么写
page.init  = (page.init || function() {}).fnAfter(function() {
  //...
});
//咱们能不能再优化下,好比容错代码 || function(){} 在一个地方统一处理 
//或者咱们新建一个工厂函数来帮咱们统一作这样的事情,这里咱们就不展开了,文章篇幅有限。
复制代码
咱们上面的扩展其实就是遵循的是面向对象程序设计中的开放-封闭原则(OCP)。官方对OCP的解释是:软件实体(类、模块、函数...)应该是能够扩展的,可是不可修改。设计模式中有不少模式都遵循了开发-封闭原则,好比:发布-订阅者模式、模板方法模式、策略模式、代理模式。

有的时候咱们经过扩展来提升代码的灵活性并不能解决全部的场景须要,在不可避免发生修改的时候,咱们能够经过增长配置文件,让用户修改配置文件以实现个性化需求也是合理的。修改配置远比修改源代码要简单的多。小程序

有了上面的引入,咱们来看几个前端开发中常见的设计模式。
  • 单例模式后端

    单例模式顾名思义:保证一个类仅有一个实例,  
    而且对外暴露一个可以访问到它的访问点。
    复制代码

    实现单例模式的核心就是保证一个类仅有一个实例,那么意思就是当建立一个对象时,咱们须要判断下以前有没有建立过该实例,若是建立过则返回以前建立的实例,不然新建。微信小程序

    var fn = function() {
      this.instance = null;
    };
    fn.getInstance = function() {
      //写法1
      if (!this.instance) {
        this.instance = new fn();
      }
      return this.instance;
      
      //写法2
      return this.instance || (this.instance = new fn());
    };
    
    var fnA = fn.getInstance();
    var fnB = fn.getInstance();
    console.log(fnA === fnB); //true
    复制代码

    平常的业务场景中,单例模式也比较常见,好比:一个页面中的模态框只有一个,每次打开与关闭的都应该是同一个,而不是重复新建。并且为了性能优化,咱们应该在须要时再建立,而不是页面初始化时就已经存在于dom中,这个就是惰性单例模式设计模式

    //假设咱们须要点击某个按钮时就显示出模态框,那么咱们能够像下面这么实现。
    var createModal = (function(){
      var modal = null;
      return function() {
        if (!modal) {
          modal = document.createElement('div');
          //...
          modal.style.display = 'none';
          document.getElementById('container').append(modal);
        }
        return modal;
      }
    })();
    
    document.getElementById('showModal').click(function() {
      var modal = createModal();
      modal.style.display = 'block';
    });
    复制代码

    上面的代码中,咱们将建立对象和管理实例的逻辑都放在一个地方,违反了单一职责原则,咱们应该单独新建一个用于建立单例的方法,这样咱们不只能建立惟一的modal实例,也能建立其余的,职责分开。

    var createSingleInstance = function(fn) {
      var instance = null;
      return function() {
        if (!instance) {
          instance = fn.apply(this, arguments);
        }
        return instance;
      }
    };
    
    var createModal = function() {
      var modal = docuemnt.createElement('div');
      //...
      modal.style.display = 'none';
      document.getElementById('container').append(modal);
      return modal;
    };
    
    var modal = createSingleInstance(createModal);
    复制代码

  • 观察者模式

    定义了对象与其余对象之间的依赖关系,  
    当某个对象发生改变的时候,全部依赖到这个对象的地方都会被通知。
    复制代码

    像knockout.js中的ko.compute以及vue中的computed函数其实就是这个模式的实践。实现观察者模式的核心就是咱们须要有一个变量来保存全部的依赖,一个listen函数用于向变量中添加依赖,一个trigger函数用于触发通知。

    var observal = {
      eventObj: {},
      listen: function(key, fn) {
        this.eventObj[key] = this.eventObj[key] || [];
        this.eventObj[key].push(fn);
      },
      trigger: function(key) {
        var eventList = this.eventObj[key];
        if (!eventList || eventList.length < 1) {
          return;
        }
        var length = eventList.length;
        for (var i = 0; i < length; i++) {
          var event = eventList[i];
          event.apply(this, arguments);
        }
      }
    };
    
    //定义要监听的事件
    observal.listen('command1', function() {
      console.log('黑夜给了我夜色的眼睛~');
    });
    observal.listen('command1', function() {
      console.log('我却用它寻找光明~');
    });
    observal.listen('command2', function() {
      console.log('一花一世界~');
    });
    observal.listen('command2', function() {
      console.log('一码一人生~');
    });
    
    //触发某个监听的事件
    observal.trigger('command1');//黑夜给了我夜色的眼睛~ 我却用它寻找光明~
    observal.trigger('command2');//一花一世界~ 一码一人生~
    复制代码

    使用观察者模式(发布-订阅模式)咱们可使得代码更灵活、健壮性更高。订阅者不须要了解消息来自哪个发布者,发布者也不须要知道消息会发送给哪些订阅者。

    一样的咱们能够建立一个公用的函数库,里面存放建立observal的工具方法,须要用到的地方咱们就用这个方法建立一个发布订阅对象。

  • 其余设计模式及设计原则

    设计模式有不少,这里篇幅有限就再也不展开。GoF在1995年提出了23种设计模式。诸如策略者模式优化表单验证、代理模式、组合模式、装饰者模式、适配器模式...这些后期能够再简单探讨或者你们后面本身了解。经常使用的设计模式及设计原则能够参考下面的思惟导图。

    经常使用设计模式

    六大设计原则

看了上面的文章,相信你们对设计模式的好处有了直观的了解,也大体掌握了单例模式及观察者模式。

设计模式都是通过了大量的代码、软件实践而总结出来的优秀的组织实践方案。每种设计模式都有它的适应场景,有的场景也会使用多种设计模式。只有了解了更多的设计模式,掌握各个设计模式本身的适应场景,才能更好的为咱们所用。

可是***过早的优化不必定是好事或者不是必须的***,有时候咱们能够一开始并不去优化,等到某个应用场景下出现了代码组织混乱、须要额外扩展等问题,咱们再优化重构,以防过早优化致使的没必要要性或者只是增长了代码没必要要的复杂性。就像redux,若是一个页面组件与组件之间有数据共享、须要在任意组件内部拿到某个数据、任意一个组件中某个行为致使的数据变化须要通知到全部用到的地方,那么这个时候可使用redux,一些简单的表单页面或者展现页彻底能够不用redux。

看到这里不容易,最后给你们讲一个笑话轻松一下:
从前有只麋鹿,它在森林里玩儿,不当心走丢了。  
因而它给它的好朋友长颈鹿打电话:“喂…我迷路辣。”  
长颈鹿听见了回答说:“喂~我长颈鹿辣~”
复制代码

参考:曾探《javascript设计模式与开发实践》


iKcamp官网:www.ikcamp.com

访问官网更快阅读所有免费分享课程:《iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享》。 包含:文章、视频、源代码

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

iKcamp最新活动

报名地址:www.huodongxing.com/event/54099…

“每天练口语”小程序总榜排名第4、教育类排名第一的研发团队,面对面沟通交流。


2019年,iKcamp原创新书《Koa与Node.js开发实战》已在京东、天猫、亚马逊、当当开售啦!

相关文章
相关标签/搜索