资讯专栏INFORMATION COLUMN

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

alin / 805人阅读

摘要:修改配置远比修改源代码要简单的多。在年提出了种设计模式。常用的设计模式及设计原则可以参考下面的思维导图。每种设计模式都有它的适应场景,有的场景也会使用多种设计模式。包含文章视频源代码原创新书移动前端高效开发实战已在亚马逊京东当当开售。

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

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

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

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

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

这样写是可以解决我们的需求,但是这样的代码是具有侵略性的,我们不得不在原先的代码的合适位置新增我们需要的代码。但我们思考一个问题,如果我们用了某个插件或者某个被ungly、minify之后的代码呢,我们怎么在找到合适的位置添加我们需要的功能呢?大家可以先自己思考一下,再看下面的内容。

首先我们先看解决方案,再思考其背后的东西。
//我们可以在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中再返回自身就好了。

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

其实上面的代码写法还是可以优化的。比如:

//每次扩展的时候我们都需要这么写
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种设计模式。诸如策略者模式优化表单验证、代理模式、组合模式、装饰者模式、适配器模式...这些后期可以再简单探讨或者大家后面自己了解。常用的设计模式及设计原则可以参考下面的思维导图。

![常用设计模式](https://user-gold-cdn.xitu.io/2017/10/27/f7ab0664b5fd4e34dbb16eaab5813c9d)

![六大设计原则](https://user-gold-cdn.xitu.io/2017/10/27/038d561c77aba1ff095fa0c3cfc8c113)

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

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

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

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

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

---

iKcamp官网:http://www.ikcamp.com

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

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

iKcamp最新活动

报名地址:http://www.huodongxing.com/ev...

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89345.html

相关文章

  • 前端每周清单第 34 期:Vue 现状盘点与 3.0 展望,React 代码迁移与优化,图片优化详论

    摘要:工程实践立足实践,提示实际水平内联函数与性能很多关于性能优化的文章都会谈及内联函数,其也是常见的被诟病为拖慢性能表现的元凶之一不过本文却是打破砂锅问到底,论证了内联函数并不一定就会拖慢性能,过度的性能优化反而会有损于应用性能。 showImg(https://segmentfault.com/img/remote/1460000011481413?w=1240&h=825); 前端每周...

    CoderStudy 评论0 收藏0
  • 前端每周清单第 38 期: Node 9 发布,Kotlin 与 React,Netflix 架构解

    摘要:发布本周正式发布,包含了一系列的特性提升与问题修复,同时也在不断致力于将打造地更为轻巧与高性能。当然,姜振勇老师还会介绍的多种服务,包括大数据网络和安全,展现弹性安全和高可扩展性的全方位能力。 showImg(http://upload-images.jianshu.io/upload_images/1647496-2ce7598e6987d9af.jpg?imageMogr2/aut...

    Carbs 评论0 收藏0
  • 前端性能优化

    摘要:端优谈谈关于前端的缓存的问题我们都知道对页面进行缓存能够有利于减少请求发送,从而达到对页面的优化。而作为一名有追求的前端,势必要力所能及地优化我们前端页面的性能。这种方式主要解决了浅谈前端中的过早优化问题过早优化是万恶之源。 优化向:单页应用多路由预渲染指南 Ajax 技术的出现,让我们的 Web 应用能够在不刷新的状态下显示不同页面的内容,这就是单页应用。在一个单页应用中,往往只有一...

    Dean 评论0 收藏0
  • 2017-10-29 前端日报

    摘要:作者众成翻译只写的禅众成翻译如何使更高效如何用构建前端架构小米直达服务介绍与开发实战搭建一个多页面的无依赖的工程化项目掘金计算机网络概念和结构掘金英文 2017-10-29 前端日报 精选 【译】图与例解读Async/AwaitVue + TypeScript 新项目起手式 React-Router动态路由设计最佳实践Vue2 原理浅谈使用合适的设计模式一步步优化前端代码The 14 ...

    hzx 评论0 收藏0
  • 前端每周清单第 51 期: React Context API 与模式变迁, Webpack 与 W

    摘要:前端每周清单第期与模式变迁与优化界面生成作者王下邀月熊编辑徐川前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。 showImg(https://segmentfault.com/img/remote/1460000013279448); 前端每周清单第 51 期: React Context A...

    Jiavan 评论0 收藏0

发表评论

0条评论

alin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<