简单的历史记录实现

时间:2015-03-17 作者:剧中人

近来小剧又在发疯似的造一个轮子:mditor一款轻量的markdown编辑器。既然是编辑器,肯定少不了撤销Ctrl+Z 和 重做Ctrl+Y 功能。今天小剧就来说一说我是如何处理撤销、重做功能的。

其实撤销与重做,只是围绕历史记录在做的两个常用功能,下面小剧用比较逗比科幻的角度来解释与历史记录相关的几个概念。

历史记录 ,其自身是一组数据,用于记录一定时间内的操作,你可以把它理解为你自己最近几年主要活动的时空轨迹。

撤销,回到到上一步的操作,一款现实生活绝对买不到的后悔药。是穿越时空的超能力,撤销可以带你回到你最想去更改的某个时间点,也可以抹去刚刚不小心犯下的错误。

重做,也称之为恢复,假如你只是想回到过去看一眼,那你还有机会选择重做立即回到未来,继续现在的生活。

最大历史记录,最多可允许恢复的历史记录步数。每时空旅行者的超能力都有限度,只能在一定范围内的时空游走。

这四个概念给了时空穿梭极大的自由度,但有一点需要注意,历史记录仅仅是一条单行的线,你可以在时间线上前后穿越。一旦你回到某一个过去,改变了某一件小事,之后的时空便不复存在。相信看过《蝴蝶效应》的小伙伴都会了解,这里没有平行世界的概念呦!

小剧的胡言乱语了这么多,用这个疯癫的逻辑建立一个历史记录的模型就不是难事了,代码祭出。

简单的历史记录类

  /**
   * 历史记录
   **/
  function LOG(max){
    //历史记录存储字段
    this.data = [];
    //当前所处在的时间点
    this.current = -1;
    //最大历史记录
    this.maxLength = max || 20;
  }
  LOG.prototype = {
    //新的一步
    push: function(item){
      //已经有新的改变,若有时间线后的操作全部作废
      if(this.data.length > this.current + 1){
        this.data.length = this.current + 1;
      }
      this.current++;
      this.data.push(item);
      //超出最大历史纪录数,删除最早的历史记录
      if(this.data.length > this.maxLength){
        this.data = this.data.slice( -this.maxLength);
        this.current = this.maxLength - 1;
      }
    },
    //撤销
    undo: function(){
      if(this.current < 0){
        return null;
      }
      return this.data[--this.current];
    },
    //重做
    redo: function (){
      if(this.data.length > this.current + 1){
        return this.data[++this.current];
      }else{
        return null;
      }
    }
  };

有了这么一个简单的历史操作的类,就可以在自己的功能里包装历史记录功能了。规划好每次存储历史记录需要记下的字段,以及撤销、重做的时候,如何把存储的数据重新渲染到视图里。

简单的使用demo

/**
 * getPosition 和 setPosition为获取、设置光标位置的方法,细节不具体展开
 **/
var log = new LOG(20),
    $textarea = $('textarea');

$textarea.on('change',function(){
  //向历史记录列表追加记录
  log.push({
    selection : getPosition($textarea),
    content: $(this).val()
   });
});
$('.undo').click(function(){
  //撤销
  var step = log.undo();
  writeStep(step);
});
$('.redo').click(function(){
  //重做
  var step = log.redo();
  writeStep(step);
});
//绘制当前步骤
function writeStep(step){
 if(!step || !step.content){
   return
 }
 $textarea.val(step.content);
 setPosition($textarea,step.selection[0],step.selection[1]);
}

PS:为什么要有最大历史记录的限制?

这个限制一般软件都会有,特别因为这里是JS,完全是内存存储,自然不允许无限制存储历史记录。