1 历史栈原理
1.1 初始化
现在想要实现
undo
和redo
的功能首先
准备一个栈,用来保存所有历史数据,最开始存储一个数据,指向最新数据
然后,再准备一个指针,描述当前指向历史栈中的哪个位置,初始值为0
1.2 进栈操作
当状态发生修改之后,需要把旧的状态存入历史栈中,只需要做两件事情
- 入栈
- pointer+1
1.3 undo操作
撤销,只需要干两件事情
- pointer-1
- pointer指向的位置就是当前要在页面上显示的位置
1.4 redo操作
重做,只需要干两件事情
- pointer+1
- pointer指向的位置就是当前要在页面上显示的位置
1.5 undo之后进栈
比如现在在1位置
做了新的修改,需要进栈,那么要把1后面的栈全部清除
1.6 栈大小控制
历史栈不能无限大,需要做控制,比如限制大小为5个,那么逻辑如下
2 封装库
2.1 PointerDoubleLinkedList
class LinkedNode {
/** 上一个节点 */
prev = null
/** 当前节点存储的数据 */
item
/** 下一个节点 */
next = null
constructor(item) {
this.item = item
}
}
/** 双向链表 */
class PointerDoubleLinkedList {
/** 头节点 */
#head
/** 末尾节点 */
#tail
/** 当前指向的node */
#pointerNode
/** 链表长度 */
length = 0
/**
* @param firstNode 第一个节点
*/
constructor(firstNode) {
firstNode !== undefined && this.append(firstNode)
}
/** 添加节点 */
append(node) {
const newNode = new LinkedNode(node)
// 当前链表中一个元素都没有
if (!this.length) {
this.#head = this.#pointerNode = this.#tail = newNode
this.length++
}
// #pointerNode 等于最后一个节点
else if (this.#pointerNode === this.#tail) {
this.#tail.next = newNode
newNode.prev = this.#tail
this.#tail = this.#pointerNode = newNode
this.length++
}
// #pointerNode 不能最后一个节点,就要把 #pointerNode 之后的所有节点全部删除,再插入
else {
// 计算#pointerNode之后还有几个节点
let nextNode = this.#pointerNode.next
let size = 0
while (nextNode) {
size++
nextNode = nextNode.next
}
// 获取被移除的节点,清除联系
this.#pointerNode.next.prev = null
// 重置 #tail 与 this.#pointerNode
newNode.prev = this.#pointerNode
this.#tail = this.#pointerNode.next = newNode
this.#pointerNode = newNode
// 重写长度
this.length = this.length - size + 1
}
}
/** 移除开头节点 */
removeHead() {
if (this.length === 0) return
if (this.#pointerNode === this.#head) {
this.#pointerNode = this.#head.next
if (this.length === 1) this.#tail = null
}
this.#head = this.#head.next
this.#head.prev = null
this.length--
}
/** 是否存在上一个节点 */
isPrev = () => this.#pointerNode !== this.#head
/** 指向前一个节点 */
prev = () => {
if (!this.length) return false
return this.#pointerNode = this.#pointerNode.prev
}
/** 是否存在下一个节点 */
isNext = () => {
if (!this.length) return false
return this.#pointerNode !== this.#tail
}
/** 指向下一个节点 */
next = () => this.#pointerNode = this.#pointerNode.next
/** 获取当前指针 */
getPointerNode = () => this.#pointerNode.item
test() {
console.log(this.#head)
console.log(this.#tail)
}
}
const list = new PointerDoubleLinkedList()
try {
list.append(0)
list.append(1)
list.append(2)
list.append(3)
list.append(4)
assert.equal(list.getPointerNode(), 4, '错误1')
assert.equal(list.isNext(), false, '错误2')
assert.equal(list.isPrev(), true, '错误3')
// 指向3
list.isPrev() && list.prev()
assert.equal(list.getPointerNode(), 3, '错误4')
// 指向2
list.isPrev() && list.prev()
assert.equal(list.getPointerNode(), 2, '错误5')
// 指向1
list.isPrev() && list.prev()
assert.equal(list.getPointerNode(), 1, '错误6')
// 指向0
list.isPrev() && list.prev()
assert.equal(list.getPointerNode(), 0, '错误7')
// 指向0
list.isPrev() && list.prev()
assert.equal(list.getPointerNode(), 0, '错误8')
assert.equal(list.isNext(), true, '错误9')
assert.equal(list.isPrev(), false, '错误10')
list.append(10)
list.next()
assert.equal(list.isNext(), false, '错误11')
assert.equal(list.getPointerNode(), 10, '错误12')
assert.equal(list.length, 2, '错误13')
// 验证 removeHead
list.removeHead()
assert.equal(list.length, 1, '错误14')
// 全部移除
list.removeHead()
assert.equal(list.length, 0, '错误15')
assert.equal(list.isNext(), false, '错误16')
assert.equal(list.isPrev(), false, '错误17')
} catch (e) {
console.log(e.message)
}
2.2 HistoryStack
class HistoryStack {
/** 历史栈最大可以存储多少数据 */
#stackMaxSize
#linkedList
/**
* @param firstNode 初始化的item
* @param stackMaxSize 历史栈最大可以存储多少数据
*/
constructor(firstNode, stackMaxSize = 20) {
if (stackMaxSize < 1 && stackMaxSize > 100) throw new Error('stackMaxSize只能在1-100区间内')
this.#stackMaxSize = stackMaxSize
this.#linkedList = new PointerDoubleLinkedList(firstNode)
}
/** 进栈 */
pushStack(item) {
this.#linkedList.append(item)
// console.log(this.#linkedList.length)
if (this.#linkedList.length > this.#stackMaxSize) {
this.#linkedList.removeHead()
}
// console.log(this.#linkedList.test())
}
/** 能否撤销 */
canUndo() {
return this.#linkedList.isPrev()
}
/** 能否重做 */
canRedo() {
return this.#linkedList.isNext()
}
/** 撤销 */
undo() {
try {
return this.#linkedList.prev(), this.getCurrent()
} catch (e) {
console.error('undo fail', e)
}
}
/** 重做 */
redo() {
try {
return this.#linkedList.next(), this.getCurrent()
} catch (e) {
console.error('redo fail', e)
}
}
/** 获取当前项 */
getCurrent = () => this.#linkedList.getPointerNode()
/** 获取栈大小 */
getStackSize = () => this.#linkedList.length
}
try {
const historyStack = new HistoryStack(0, 3)
assert.equal(historyStack.canUndo(), false, '错误1')
assert.equal(historyStack.canRedo(), false, '错误2')
historyStack.pushStack(1)
assert.equal(historyStack.canUndo(), true, '错误3')
assert.equal(historyStack.canRedo(), false, '错误4')
historyStack.pushStack(2)
assert.equal(historyStack.getStackSize(), 3, '错误5')
historyStack.pushStack(3)
assert.equal(historyStack.getStackSize(), 3, '错误6')
assert.equal(historyStack.getCurrent(), 3, '错误7')
historyStack.undo()
assert.equal(historyStack.getCurrent(), 2, '错误8')
historyStack.undo()
assert.equal(historyStack.getCurrent(), 1, '错误9')
assert.equal(historyStack.canUndo(), false, '错误10')
} catch (e) {
console.log(e.message)
}
2.3 内存泄露检测
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="test()">测试是否存在内存泄露</button>
<h1></h1>
<script>
class HistoryStack {
/** 历史栈最大可以存储多少数据 */
#stackMaxSize
#linkedList
/**
* @param firstNode 初始化的item
* @param stackMaxSize 历史栈最大可以存储多少数据
*/
constructor(firstNode, stackMaxSize = 20) {
if (stackMaxSize < 1 && stackMaxSize > 100) throw new Error('stackMaxSize只能在1-100区间内')
this.#stackMaxSize = stackMaxSize
this.#linkedList = new PointerDoubleLinkedList(firstNode)
}
/** 进栈 */
pushStack(item) {
this.#linkedList.append(item)
// console.log(this.#linkedList.length)
if (this.#linkedList.length > this.#stackMaxSize) {
this.#linkedList.removeHead()
}
// console.log(this.#linkedList.test())
}
updatePointer(item) {
this.#linkedList.updatePointer(item)
}
/** 能否撤销 */
canUndo() {
return this.#linkedList.isPrev()
}
/** 能否重做 */
canRedo() {
return this.#linkedList.isNext()
}
/** 撤销 */
undo() {
try {
return this.#linkedList.prev(), this.getCurrent()
} catch (e) {
console.error('undo fail', e)
}
}
/** 重做 */
redo() {
try {
return this.#linkedList.next(), this.getCurrent()
} catch (e) {
console.error('redo fail', e)
}
}
/** 获取当前项 */
getCurrent = () => this.#linkedList.getPointerNode()
getStackSize = () => this.#linkedList.length
}
class LinkedNode {
/** 上一个节点 */
prev = null
/** 当前节点存储的数据 */
item
/** 下一个节点 */
next = null
constructor(item) {
this.item = item
}
}
/** 双向链表 */
class PointerDoubleLinkedList {
/** 头节点 */
#head
/** 末尾节点 */
#tail
/** 当前指向的node */
#pointerNode
/** 链表长度 */
length = 0
/**
* @param firstNode 第一个节点
*/
constructor(firstNode) {
firstNode !== undefined && this.append(firstNode)
}
/** 添加节点 */
append(node) {
const newNode = new LinkedNode(node)
// 当前链表中一个元素都没有
if (!this.length) {
this.#head = this.#pointerNode = this.#tail = newNode
this.length++
}
// #pointerNode 等于最后一个节点
else if (this.#pointerNode === this.#tail) {
this.#tail.next = newNode
newNode.prev = this.#tail
this.#tail = this.#pointerNode = newNode
this.length++
}
// #pointerNode 不能最后一个节点,就要把 #pointerNode 之后的所有节点全部删除,再插入
else {
// 计算#pointerNode之后还有几个节点
let nextNode = this.#pointerNode.next
let size = 0
while (nextNode) {
size++
nextNode = nextNode.next
}
// 获取被移除的节点,清除联系
this.#pointerNode.next.prev = null
// 重置 #tail 与 this.#pointerNode
newNode.prev = this.#pointerNode
this.#tail = this.#pointerNode.next = newNode
this.#pointerNode = newNode
// 重写长度
this.length = this.length - size + 1
}
}
/** 更新节点 */
updatePointer(node) {
if (!this.length) return
const newNode = new LinkedNode(node)
// 只有一个节点
if (this.length === 1) {
this.#head = this.#tail = this.#pointerNode = newNode
}
// 更新pointer节点数据
else {
// 如果当前节点是第一个节点
if (this.#pointerNode === this.#head) {
this.#head.next.prev = newNode
this.#head = this.#pointerNode = newNode
}
// 如果当前节点是最后一个节点
else if (this.#pointerNode === this.#tail) {
this.#tail.prev.next = newNode
this.#tail = this.#pointerNode = newNode
}
// 中间节点
else {
const prevNode = this.#pointerNode.prev
const nextNode = this.#pointerNode.next
prevNode.next = this.#pointerNode = nextNode.prev = newNode
}
}
}
/** 移除开头节点 */
removeHead() {
if (this.length === 0) return
if (this.#pointerNode === this.#head) {
this.#pointerNode = this.#head.next
if (this.length === 1) this.#tail = null
}
this.#head = this.#head.next
this.#head.prev = null
this.length--
}
/** 是否存在上一个节点 */
isPrev = () => this.#pointerNode !== this.#head
/** 指向前一个节点 */
prev = () => {
if (!this.length) return false
return this.#pointerNode = this.#pointerNode.prev
}
/** 是否存在下一个节点 */
isNext = () => {
if (!this.length) return false
return this.#pointerNode !== this.#tail
}
/** 指向下一个节点 */
next = () => this.#pointerNode = this.#pointerNode.next
/** 获取当前指针 */
getPointerNode = () => this.#pointerNode.item
test() {
console.log(this.#head)
console.log(this.#tail)
}
}
const historyStack = new HistoryStack(0, 100)
// historyStack.pushStack(1)
// historyStack.undo()
// historyStack.pushStack(2)
// console.log(historyStack.canRedo())
// console.log(historyStack)
function test() {
for (let i = 0; i < 10000; i++) {
historyStack.pushStack([i])
historyStack.updatePointer([Math.random()])
}
document.querySelector('h1').innerText = '插入成功' + Math.random()
// console.log(historyStack)
}
test()
</script>
</body>
</html>