一、前言
开发的过程中遇到一个需求,实现用户的撤销操作。撤销操作在各种编辑软件是常见的功能。
但我还是第一次接到这样的需求,这里记录下详细的实现过程。
二、需求分析
需求是这样的,当用户点击操作中的 确认、删除、待定、词库等操作后,希望能够还原到原来的状态。(这里讲一下,当用户点击操作的按钮后是需要和后端进行交互,进行数据的更改。)
因为涉及到和后端进行数据交互,我第一反应是在后端进行记录数据的变动,在前端每次操作并且把操作的内容传到后端,但是后端把这些记录存在哪里,又什么时候把这些存的数据进行清除呢,想想有点头大,好像后端记录有点麻烦。
那么后端不好记录,就只能在前端实现了。
操作记录一般应该是链式的,如下图,那么这种数据结构的存储我们应该想到用队列和栈。
1、这里扩展介绍下队列和栈
介绍队列和栈之前还需要学习下js中的数组,因为js中没有队列和栈这种数据结构,需要我们自己来实现
a、数组介绍
详细介绍参考如下地址:
https://www.w3school.com.cn/jsref/jsref_obj_array.asp
原生js并没有这种队列和栈数据结构,那么就需要我们自己去实现,队列和栈其实都是线性数据接口,那么我们可以用数组来实现。
这里主要介绍几个在队列和栈中需要用到的几个数组对象方法,下面实现队列和栈时需要使用
pop() 删除并返回数组的最后一个元素
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
shift() 删除并返回数组的第一个元素
b、队列
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表,队列是一种先进先出的线性表,简称FIFO,允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。向队中插入元素称为进队,新元素进队后成为新的队尾元素;向队中删除元素称为出队,元素出队后,其后继元素就成为新的队头元素。
原生js实现队列,队列主要的特性是先进先出,所有需要使用到数据的push()方法从队尾插入数据,然后用shift()方法从队头取数据。实现如下
function Queue() {
//初始化队列(使用数组实现)
var items = [];
//向队列(尾部)中插入元素
this.push= function(element) {
items.push(element);
}
//从队列(头部)中弹出一个元素,并返回该元素
this.pop= function() {
return items.shift();
}
//查看队列最前面的元素(数组中索引为0的元素)
this.front = function() {
return items[0];
}
//查看队列是否为空,如果为空,返回true;否则返回false
this.isEmpty = function() {
return items.length == 0;
}
//查看队列的长度
this.size = function() {
return items.length;
}
// 移除栈里所有的元素
this.clear = function () {
items = [];
};
//查看队列
this.print = function() {
//以字符串形势返回
return items.toString();
}
}
c.栈
栈是限定仅在表尾进行插入和删除操作的线性表,我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈,栈又称后进先出的线性表,简称LIFO结构。(和队列正好相反)
原生js实现栈,栈主要的特性是后进先出,所有需要使用到数据的push()和pop() 方法实现。在数组的一端进行存和取数据。
function Stack() {
var items = [];
// 添加一个(或几个)新元素到栈顶
this.push = function (ele) {
items.push(ele);
};
// 移除栈顶的元素,同时返回被移除的元素
this.pop = function () {
return items.pop();
};
// 返回栈顶的元素,不对栈做任何修改
this.peek = function () {
return items[items.length - 1];
};
// 判断栈里使是否还有元素
this.isEmpty = function () {
return items.length === 0;
};
// 移除栈里所有的元素
this.clear = function () {
items = [];
};
// 返回栈里的元素个数
this.size = function () {
return items.length;
};
// 移除一个元素
this.shift = function () {
return items.shift()
}
}
三、需求的实现:
上面扩展学习了栈和队列后,下面就是用栈来实现我们的需求。
回到我们的需求上,撤销操作时,取出的数据应该是后进入到容器中的,所以我们可以简单的用栈实现这一需求。也就是用户点击 确认、删除、待定、词库等操作后,把用户进行的操作的数据记录在栈里面,当撤回时再将其状态改为之前的状态。
这里主要注意的是记录还原状态需要的值,我这里记录了术语的id,目前的状态,改变后的状态、删除的html。
下面是一个简单的demo,因为我这里涉及的具体业务,还涉及到后端交互,就不贴原始代码了,使用时只需要在入栈时,把还原时需要记录的数据加入栈内就可以了。
下面是简单代码演示。
http://jsrun.net/JQ2Kp/edit
demo代码:
<!DOCTYPE html>
<html>
<body>
<button style="color: green">撤销</button>
<div id='box'>
<table>
<tr>
<td>数据</td>
<td>操作</td>
</tr>
<tr>
<td>1</td>
<td><button>删除</button></td>
</tr>
<tr>
<td>2</td>
<td><button>删除</button></td>
</tr>
<tr>
<td>3</td>
<td><button>删除</button></td>
</tr>
<tr>
<td>4</td>
<td><button>删除</button></td>
</tr>
<tr>
<td>5</td>
<td><button>删除</button></td>
</tr>
<tr>
<td>6</td>
<td><button>删除</button></td>
</tr>
<tr>
<td>7</td>
<td><button>删除</button></td>
</tr>
</table>
</div>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.12.4/jquery.js"></script>
<script type="text/javascript">
function Stack(num=5) {
var items = [];
// 添加一个(或几个)新元素到栈顶
this.push = function (ele) {
if(num.length >=num)
{
items.shift()
}
items.push(ele);
};
// 移除栈顶的元素,同时返回被移除的元素
this.pop = function () {
return items.pop();
};
// 返回栈顶的元素,不对栈做任何修改
this.peek = function () {
return items[items.length - 1];
};
// 判断栈里使是否还有元素
this.isEmpty = function () {
return items.length === 0;
};
// 移除栈里所有的元素
this.clear = function () {
items = [];
};
// 返回栈里的元素个数
this.size = function () {
return items.length;
};
// 移除一个元素
this.shift = function () {
return items.shift()
}
}
var stack = new Stack(4)
$(document).on('click','.del', function(){
var _dict = {}
var $this = $(this)
var _html = '<tr>' + $this.parent().parent().html() + '</tr>'
_dict['_html'] = _html
stack.push(_dict)
$this.parent().parent().remove()
})
$('.back').on('click', function(){
if (stack.isEmpty()) {
alert('无撤销内容')
} else {
_html = stack.pop()._html
$('#box table').find('tr:first-child').after(_html)
}
})
</script>
</html>