Fork me on GitHub

策略模式的应用


用策略模式实现缓动动画

需求:让小球以各种缓动效果在页面中运动
动画开始前需要记录的:

  • 小球所在原始位置

  • 小球移动的目标位置

  • 动画开始时的时间点

  • 小球运动的时间点
    之后,用setInterval创建一个定时器,定时器每19秒循环一次。

//缓动算法,此算法移植于flash

var tween = {
    //t:动画已消耗的时间,b:小球原始位置,c:小球目标位置,d:动画持续时间
    linear: function(t, b, c, d) {
        return c*t/d + b;
    },
    reseIn: function(t, b, c, d) {
        return c * (t /= d) * t + b;
    },
    strongEaseIn: function(t, b, c, d) {
        return c * (t /= d) * t * t * t * t + b;
    },
    strongEaseOut: function(t, b, c, d) {
        return c * (( t = t / d - 1) * t * t * t * t + 1) + b;
    },
    sineaseIn: function(t, b, c, d) {
        return c * (t /= d) * t * t + b;
    },
    sineaseOur: function(t, b, c, d) {
        return c * ((t = t / d - 1) * t * t + 1 ) + b;
    }
};

接着Animation类

var Animate = function( dom ) {
    this.dom = dom;              //进行运动的dom节点
    this.startTime = 0;         //动画开始时间
    this.startPos = 0;           //动画开始时候dom位置
    this.endPos = 0;            //结束dom位置
    this.propertyName = null;     //dom节点需要被改变的属性名
    this.easing = null;    //缓动算法
    this.duration = null;   //动画持续时间
};

Animate.prototype.start方法负责启动这个动画
Animate.prototype.step代表小球运动的每一帧要做的事情

Animate.prototype.start = function( propertyName, endPos, duration, easing) {
    this.startTime = +new Date;  //动画启动时间
    this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; //dom节点初始位置
    this.endPos = endPos;
    this.duration = duration;
    this.easing = tween[ easing ]; 

    var self = this;
    var timeId = setInterval(function() { //启动定时器,开始执行动画
        if (self.step() === false) {
            clearInterval(timeId);
        }
    }, 19);
};


Animate.prototype.step = function() {
    var t = +new Date;
    if (t >= this.startTime + this.duration) {
        this.update(this.endPos);
        return false;
    }
    var pos = this.easing(t - this.startTime, this.startPos, this.endPos = this.startPos, this.duration);

    this.update(pos);
};

Animate.prototype.update = function(pos) {
    this.dom.style[this.propertyName] = pos + 'px';
};

最后,调用

var div = document.getElementById('div');
var animate = new Animate(div);
animate.start('left', 500, 1000, 'strongEaseOut');

表单验证

需求:

  • 用户名不能为空

  • 密码长度不能少于6位

  • 手机号码符合格式

常规,我们可能这么写:

var registerForm = document.getElementById('registerForm');

registerForm.onsubmit = function() {
  if (registerForm.userName.value === '') {
    alert("用户名不能为空");
    return false;
  }
  if (registerForm.password.value.length < 6) {
    alert("密码长度不能少于6位");
    return false;
  }
  if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
    alert("手机号码格式不正确");
    return false;
  }
}

当然,这个也有函数庞大,缺乏弹性,复用性差等问题,所以,开始突出本文的重点,策略模式的应用

var strategies = {
  isNonEmpty: function(value, errorMsg) {
    if (value == '') {
      return errorMsg;
    }
  },
  minLength: function(value, length, errorMsg) {
    if (value.length < length) {
      return errorMsg;
    }
  },
  isMobile: function(value, errorMsg) {
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      return errorMsg;
    }
  }
};

var Validator = function() {
  this.cache = [];
};

Validator.prototype.add = function(dom, rule, errorMsg) {
  var ary = rule.split(':');
  this.cache.push(function() {
    var strategy = ary.shift();
    ary.unshift(dom.value);
    ary.push(errorMsg);
    return strategies[strategy].apply(dom, ary);
  })
};

Validator.prototype.start = function() {
  for (var i = 0; validataFunc; validataFunc = this.cache[i++]) {
    var msg = validataFunc();
    if (msg) {
      return msg;
    }
  }
}

var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function() {
  var errorMsg = validataFunc();

  if (errorMsg) {
    alert(errorMsg);
    return false;
  }
};

var validataFunc = function() {
  var validator = new Validator();

  validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空');
  validator.add(registerForm.password, 'minLength:6', '密码长度不能少于6位');
  validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');

  var errorMsg = validator.start();
  return errorMsg;
}
我知道是不会有人点的,但万一有人想不开呢?