博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
重构与模式系列(一)简化函数
阅读量:6808 次
发布时间:2019-06-26

本文共 8431 字,大约阅读时间需要 28 分钟。

函数多长合适?

Functions should do one thing

每一个函数应该都只做一件事情,如果一个函数做了过多的事情,那么它极为不稳定,这种耦合性得到的是低内聚和脆弱的设计,且不方便维护与阅读。
在人的常规思维中,总是习惯把一组相关的行为放到一起,如何正确的进行分离并不容易。

Functions should only be one level of abstraction

如果这个函数内有多步操作,但这些操作都在同一个抽象层上,那么我们还是认为这个函数只做了一件事

看个不错的demo:

我们需要从我们的注册用户列表中查看我们的用户,并且筛选出活跃用户,并向他们发送一封邮件。

Bad:

// 一般我们的书写风格 按照逻辑顺序写下去, 几件事情杂糅在了一起function emailClients(clients) {  clients.forEach(function (client, index) {    var _clientRecord = database.lookup(client);    if(_clientRecord.isActive()){      email(client);    }  })}复制代码

Good

// 让一个函数只干一件事情 单一职责function emailClients(clients) {      clients.filter(isClientActive)           .forEach(email)    }function isClientActive(client) {  var _clientRecord = database.lookup(client);  return _clientRecord.isActive();}复制代码

拆解过长函数,有哪些可用模式

我们要简化过长的函数,那么我们可以使用哪些模式来优化?《重构与模式》一书中提到面对过长的函数,我们可以考虑使用下面几种模式:

  • 提炼函数
  • 策略模式(去替换过多的if条件分支)
  • 闭包与高阶函数
  • 模板方法模式

提炼函数

提炼函数大致的思想就是将我们过长的函数拆分为小的函数片段,确保改函数内的函数片段的处理在同一层面上。随便找了一个regular预览图片组件里面的例子。

HTML结构如下:

{#if showPreview}
{/if}复制代码
onTouchMove: function (e) {        // 触摸touchmove       var  _touches = e.touches,            _ret = isEdgeWillAway(_v),            _data = this.data;        e.preventDefault();        (!this.touchLength) && (this.touchLength = e.touches.length);        if (this.touchLength === 1) {            this.deltaX = _touches[0].pageX - this.initPageX;            this.deltaY = _touches[0].pageY - this.initPageY;            if (_ret.left) {                // 图片将要往右边移动                _data.wrapperOffsetX = this.startOrgX + this.deltaX;                _data.prevShow = true;            } else if (_ret.right) {                // 图片将要往左边移动                _data.wrapperOffsetX = this.startOrgX + this.deltaX;                _data.nextShow = true;            }            this.$update();        }else if (this.touchLength === 2) {           //如果是两个手指 进行缩放控制          ....        }    },复制代码

可以看到在touchMove的函数很长,我们需要对这个函数进行提炼, 大致应该是下面这样

onTouchMove: function(e){    // 触摸touchmove       var  _touches = e.touches,            _ret = isEdgeWillAway(_v),            _data = this.data;       e.preventDefault();       (!this.touchLength) && (this.touchLength = e.touches.length);       if ( this.touchLength === 1 ) {                  //  this.$emit('setTranslate');                 //   移动图片                     this.setMove(...);       }else if ( this.touchLength === 2) {                //  this.$emit('setScale');                //  缩放图片                  this.setScale(...);       }        }复制代码

包括一些事件的绑定,我们通过下面的书写方式相比于直接写cb function也能更好地解耦。

initEvent: function () {    if (!this.data.showPreview) return;    _wrapper.addEventListener('touchstart', this.onTouchStart.bind(this));    _wrapper.addEventListener('touchmove', this.onTouchMove.bind(this));    _wrapper.addEventListener('touchend', this.onTouchEnd.bind(this));    this.$on('animateLeft', this.onAnimateLeft.bind(this));    this.$on('animateRight', this.onAnimateRight.bind(this));    this.$on('animateReset', this.onAnimateReset.bind(this));},onTouchStart: function(){  .....},复制代码

策略模式

当我们代码中有较多的if条件分支时,我们一般会选择策略模式进行重构。

策略模式的核心就是封装变化的部分,把策略的使用与策略的实现隔离开来,一个策略类,一个上下文类,依据不同的上下文返回不同的策略。

例如:

// 比如小球的动画// 策略类var tween = {  //@params t: 已运行时间  b: 原始位置 c: 目标位置 d: 持续总时间  //@return 返回元素此时应该处于的位置  linear: function (t, b, c, d) {    return c * t / d + b;  },  easeIn: function (t, b, c, d) {    return c * (t/=d) * t + b  },  ....}var Animation = function () {}Animation.prototype.start = function (target, config) {  var _timeId;  this.startTime = +new Date;// 开始时间  this.duration = config.duration;// 动画持续时间  this.orgPos = target.getBoundingClientRect()[config.property];// 元素原始的位置  this.easing = tween[config.type];// 使用的动画算法  this.endPos = config.endPos;// 元素目标位置  _timeId = setInterval(function(){
// 启动定时器,开始执行动画 if(!this.step()){
// 如果动画已经结束,清除定时器 clearInterval(_timeId); } }.bind(this), 16);}Animation.prototype.step = function () { var _now = +new Date,// 当前时间 _dur = _now - this.startTime,// 已运行时间 _endPos; _endPos = this.easing(_dur, this.orgPos, this.endPos, this.duration);// 此时应该在的位置 this.update(_endPos);// 更新小球的位置}复制代码

类似的,其他经典的例子还有验证规则的策略模式的写法。

可以看下 《Javascript设计模式与开发实践》P84 表单规则校验的例子

善用高阶函数和闭包

闭包

避免声明许多全局变量,通过闭包我们来存储变量

// 利用高阶函数避免写全局变量pro.__isWeiXinPay = (function(){      var UA = navigator.userAgent;      var index = UA.indexOf("MicroMessenger");      var _isWeiXinPay = (index!=-1 && +UA.substr(index+15,3)>=5);      // window._isWeiXin = index!=-1;      return function(){        return _isWeiXinPay;      }})();复制代码

高阶函数

高阶函数是指至少满足下列条件之一的函数:

  • 函数可以作为参数被传递
  • 函数可以作为返回值输出

高阶函数在我们编码时无形中被使用,善用高阶函数可以使我们代码写的更加漂亮。

通过高阶函数实现AOP

在Js中实现AOP,都是指把一个函数动态织入到另一个函数中,比如

Function.prototype.before = function (beforefn) {  var _self = this;  return function () {    beforefn.apply(this, arguments);// 执行before函数    return _self.apply(this, arguments);// 执行本函数  }}Function.prototype.after = function (beforefn) {  var _self = this;  return function () {    var ret = _self.apply(this, arguments);// 执行本函数    afterfn.apply(this, arguments);// 执行before函数    return ret;  }}var func = function () {  console.log('hahaha');};func = func.before(function(){  console.log(1);}).after(function(){  console.log(2);})复制代码

有了aop以后,可以帮助我们把原来耦合在一起的长函数进行拆解,再利用模板模式我们可以达到意想不到的效果,见下节。

模板方法模式

如果我们有一些平行的子类, 各个子类之间有一些相同的行为,也有一些不同的行为。相同的行为可以被搬移到另外一个单一的地方。在模板方法模式中,子类实现中相同的部分可以上移到父类,而将不同的部分待由子类去实现。模板方法就是这样的模式。

模板方法模式由两部分组成,抽象类和实现类,我们把抽出来的共同部分放到抽象类中,变化的方法抽成抽象方法,方法的具体实现由子类去实现,先看《设计模式实践》书中的一个例子:

var Beverage = function(param){  var boilWater = function () {    console.log('把水煮开');// 共同的方法  };  var brew = param.brew || function(){    throw new Error('必选传递brew方法');// 需要子类具体实现  };  var pourInCup = param.pourInCup || function(){    throw new Error('必选传递pourInCup方法');  };  var addCondiments = param.addCondiments || function(){     throw new Error( '必选传递addCondiments方法' );  };  var F = function(){}  F.prototype.init = function(){    boilWater();    brew();    pourInCup();    addCondiments();  }  return F;}var Coffee = Beverage({  brew: function(){    console.log('用沸水泡咖啡');  },  pourInCup: function(){    console.log('把咖啡倒进杯子');  },  addCondiments: function(){    console.log('加糖和牛奶');  }});var Tea = Beverage({  brew: function(){    console.log('用沸水泡茶叶');  },  pourInCup: function(){    console.log('把茶倒进杯子');  },  addCondiments: function(){    console.log('加柠檬');  }});var coffee = new Coffee();coffee.init();var tea = new Tea();tea.init();复制代码

在业务中使用模板方法和上面的AOP我们可以将我们的代码有效解耦,例如下面的rgl.module.js 是所有自定义模块的基类。

var BaseList = BaseComponent.extend({        config: function () {            this.data.loading = true;        },    initRequestEvents: function () {          var data = this.data,               dataset = data.dataset||{};          data.requestUrl = this.getRequestUrl(dataset);          this.onRequestCustomModuleData(data.requestUrl);    },       onRequestCustomModuleData: function () {            if(!requestUrl) return;            var self = this,                data = this.data;            this.$request(requestUrl,{                method: 'GET',                type: 'json',                norest: true,                onload: this.cbRequestCustomModuleData._$bind(this)._$aop(function(){                    if(data.loadingElem && data.loadingElem[0])      e._$remove(data.loadingElem[0]);                },function(){                    self.finishRequestData();             }),// 这里就是模板模式方法与aop的结合使用             onerror: function(){                data.loading = false;             }            });          },          cbRequestCustomModuleData: f,// 提供给子类具体实现的接口 子类继承BaseComponent自己具体实现          finishRequestData: f    // 提供给子类具体实现的接口 子类继承BaseComponent自己具体实现 });复制代码
var BottomModule =  BaseModule.extend({            template: tpl,            config: function(data){                _.extend(data, {                    clickIndexArray:[],                    isStatic: false                });            },           init: function(){                this.initRequestEvents();            },            cbRequestCustomModuleData: function(data){              ......// 具体实现            }};复制代码

下一节将会看看重复代码的问题及可参考模式。

【参考书籍】

  • 《Javascript 设计模式与开发实践》

转载地址:http://detwl.baihongyu.com/

你可能感兴趣的文章
jvm学习--类加载器
查看>>
enlightenment提权的工具
查看>>
指针的意义和linux的内存回收艺术
查看>>
刚才调试了个mysql中文乱码的问题
查看>>
操作系统就是虚拟机--主内又主外
查看>>
Android官方开发文档Training系列课程中文版:线程执行操作之定义线程执行代码...
查看>>
Zookeeper 集群的安装与部署
查看>>
MySQL 5.6版本二进制包多实例安装
查看>>
Centos网络管理(五)-Bonding、网络组和网桥
查看>>
PyCharm----中文显示乱码的解决方法总结
查看>>
crontab使用环境变量
查看>>
“独立博客”为什么独立?
查看>>
Uber花了21亿元入驻上海自贸区 不叫优步叫雾博
查看>>
iptables应用
查看>>
软件包管理
查看>>
Java内存模型
查看>>
AppLinks使用详解
查看>>
JavaScript正则表达式19例(11)
查看>>
UNIX发展历史流程图
查看>>
负载均衡之LVS详解
查看>>