数组是 JS 中最经常使用的数据结构,它能够在任意位置添加或删除数据。栈是另一种数据结构,相似于数组,可是在添加或删除数据时更加灵活。git
栈是一种 后进先出(LIFO) 的数据结构。新添加或待删除的元素都保存在栈的一端,叫 栈顶 ,另外一端就叫作 栈底 。在栈中,新元素都靠近栈顶,就元素都靠近栈底。算法
能够用数组来模拟一个栈结构:数组
function Stack() { let items = [] // 栈的属性和方法 }
须要实现的方法:数据结构
// 向栈中添加元素 this.push = function (element) { items.push(element) } // 从栈中移除元素 this.pop = function () { return items.pop() } // 查看栈顶元素 this.peek = function () { return items[item.length - 1] } // 检查栈是否为空 this.isEmpty = function () { return !!item.length } // 清空栈中的元素 this.clear = function () { items = [] } // 返回栈的大小 this.size = function () { return items.length } // 打印栈 this.print = function () { console.log(items.toString()) }
ES6 的写法:闭包
class Stack { constructor() { this.items = [] } push (element) { this.items.push(element) } // ... 其余方法 }
ES6 的类是基于原型的,虽然基于原型的类比基于函数的类更节省内存,可是却不能声明私有变量,因此变量 items 是公共的。这种状况下,能够直接经过修改 items 来修改栈中的数据,这是没法避免的。ide
ES6 新增了 Symbol 基础类型,它是不可变的,也能够做用对象的属性。函数
let _items = Symbol() class Stack { constructor() { this[_items] = [] } // ... 其余方法 }
上面这个例子建立了一个假的私有属性,不能彻底规避上面提到的问题,由于 ES6 新增的 Object.getOwnPropertySymbols
方法可以取到类里面声明的全部 Symbols 属性,好比:this
let stack = new Stack() stack.push(66) stack.push(88) let objectSymbols = Object.getOwnPropertySymbols(stack) console.log(objectSymbols.length) // 1 console.log(objectSymbols[0]) // Symbol() stack[objectSymbols[0]].push(1) stack.print() // 66 88 1
经过访问 stack[objectSymbols[0]] 是能够访问 _items 的,而且能够对 _items 进行任意操做。
有一种数据类型能够确保属性是私有的,这就是 WeakMap 。WeakMap 能够存储键值对,其中键是对象,值能够是任意数据类型。code
const items = new WeakMap() class Stack { constructor() { items.set(this, []) } push(element) { let s = items.get(this) s.push(element) } pop() { let s = items.get(this) return s.pop() } // ... 其余方法 }
如今,Stack 中的 items 是私有的了,可是 items 是在 Stack 类之外声明的,仍是能够被改动,因此须要借助闭包来实现一层封装:对象
let Stack = (function () { const items = new WeakMap() class Stack { constructor() { items.set(this, []) } // ... 其余方法 return Stack } })()
### 用栈解决实际问题
栈在 JS 中应用仍是十分普遍的,好比 调用栈 。进制转换也是很常见的例子,能够用 栈 来处理,好比要把十进制转化成二进制,能够将该十进制数字和2整除,直到结果是 0 为止。
function divideBy2 (decNumber) { var remStack = new Stack(), rem, binaryString = '' while (decNumber > 0) { rem = Math.floor(decNumber % 2) remStack.push(rem) decNumber = Math.floor(decNumber / 2) } while (!remStack.isEmpty()) { binaryString += remStack.pop().toString() } return binaryString }
这个例子中,当结果知足和2作整除的条件是,会取得当前结果和2的余数,放到栈里,而后让结果继续和2作整除。
#### 改进算法
把上面的例子改为十进制转成任意进制的:
function baseConverter(decNumber, base) { var remStack = new Stack(), rem, binaryString = '', digits = '0123456789ABCDEF' while (decNumber > 0) { rem = Math.floor(decNumber % base) remStack.push(rem) decNumber = Math.floor(decNumber / base) } while (!remStack.isEmpty()) { binaryString += digits[remStack.pop()] } return binaryString }