棧:不論是在哪一個語言,c/c++,java,python,還有我們的javascript都有這個數據結構,不少從別的行業轉開發的朋友都會通過這個學習過程。javascript
棧:一種聽從後進先出(LIFO)原則的有序集合。新添加的或待刪除的元素都保存在棧的
同一端,稱做棧頂,另外一端就叫棧底。在棧裏,新元素都靠近棧頂,舊元素都接近棧底。java
在現實生活中也能發現不少棧的例子。例如,下圖裏的一摞書或者餐廳裏堆放的盤子。
棧也被用在編程語言的編譯器和內存中保存變量、方法調用等。python
先熟悉一下棧的一些方法:c++
push(element(s)) :添加一個(或幾個)新元素到棧頂。 pop() :移除棧頂的元素,同時返回被移除的元素。 peek() :返回棧頂的元素,不對棧作任何修改(這個方法不會移除棧頂的元素,僅僅返回它)。 isEmpty() :若是棧裏沒有任何元素就返回 true ,不然返回 false 。 clear() :移除棧裏的全部元素。 size() :返回棧裏的元素個數。這個方法和數組的 length 屬性很相似。
class Stack { constructor () { this.items = []; //{1} } push(element) { this.items.push(element); } pop() { this.items.pop(); } peek() { return items[items.length-1]; } isEmpty() { return items.length == 0; } size() { return items.length; } clear() { items = []; } print() { console.log(items); } }
儘管代碼看起來更簡潔、更漂亮,變量 items 倒是公共的。ES6的類是基於原型的。雖然基
於原型的類比基於函數的類更節省內存,也更適合建立多個實例,卻不可以聲明私有屬性(變量)
或方法。並且,在這種狀況下,咱們但願 Stack 類的用戶只能訪問暴露給類的方法。不然,就有
可能從棧的中間移除元素(由於咱們用數組來存儲其值),這不是咱們但願看到的。git
1. 用ES6的限定做用域 Symbol 實現類算法
ES6新增了一種叫做 Symbol 的基本類型,它是不可變的,能夠用做對象的屬性。看看怎麼用
它來在 Stack 類中聲明 items 屬性:編程
let _items = Symbol(); //{1} class Stack { constructor () { this[_items] = []; //{2} } //Stack方法 }
在上面的代碼中,咱們聲明瞭 Symbol 類型的變量 _items (行 {1} ),在類的 constructor 函
數中初始化它的值(行 {2} )。要訪問 _items ,只需把全部的 this.items 都換成 this[_items] 。
這種方法建立了一個假的私有屬性,由於ES6新增的 Object.getOwnPropertySymbols 方
法可以取到類裏面聲明的全部 Symbols 屬性。下面是一個破壞 Stack 類的例子:數組
let stack = new Stack(); stack.push(5); stack.push(8); let objectSymbols = Object.getOwnPropertySymbols(stack); console.log(objectSymbols.length); // 1 console.log(objectSymbols); // [Symbol()] console.log(objectSymbols[0]); // Symbol() stack[objectSymbols[0]].push(1); stack.print(); // 輸出 5, 8, 1
從以上代碼能夠看到,訪問 stack[objectSymbols[0]] 是能夠獲得 _items 的。而且,
_items 屬性是一個數組,能夠進行任意的數組操做,好比從中間刪除或添加元素。咱們操做的
是棧,不該該出現這種行爲。數據結構
2. 用ES6的 WeakMap 實現類閉包
有一種數據類型能夠確保屬性是私有的,這就是 WeakMap 。咱們會在第7章深刻探討 Map 這
種數據結構,如今只須要知道 WeakMap 能夠存儲鍵值對,其中鍵是對象,值能夠是任意數據類型。
若是用 WeakMap 來存儲 items 變量, Stack 類就是這樣的:
const items = new WeakMap(); //{1} class Stack { constructor () { items.set(this, []); //{2} } push(element) { let s = items.get(this); //{3} s.push(element); } pop() { let s = items.get(this); let r = s.pop(); return r; } //其餘方法 }
行 {1} ,聲明一個 WeakMap 類型的變量 items 。
行 {2} ,在 constructor 中,以 this ( Stack 類本身的引用)爲鍵,把表明棧的數組存入 items。
行 {3} ,從 WeakMap 中取出值,即以 this 爲鍵(行 {2} 設置的)從 items 中取值。
如今咱們知道, items 在 Stack 類裏是真正的私有屬性了,但還有一件事要作。 items 如今
仍然是在 Stack 類之外聲明的,所以誰均可以改動它。咱們要用一個閉包(外層函數)把 Stack
類包起來,這樣就只能在這個函數裏訪問 WeakMap :
let Stack = (function () { const items = new WeakMap(); class Stack { constructor () { items.set(this, []); } //其餘方法 } return Stack; //{4} })();
當 Stack 函數裏的構造函數被調用時,會返回 Stack 類的一個實例(行 {4} )。
一個例子(把十進制的數轉換成任意進制的數)
function baseConverter(decNumber, base){ var remStack = new Stack(), rem, baseString = '', digits = '0123456789ABCDEF'; //{5} while (decNumber > 0){ rem = Math.floor(decNumber % base); remStack.push(rem); decNumber = Math.floor(decNumber / base); } while (!remStack.isEmpty()){ baseString += digits[remStack.pop()]; //{6} } return baseString; }
咱們只須要改變一個地方。在將十進制轉成二進制時,餘數是0或1;在將十進制轉成八進制
時,餘數是0到7之間的數;可是將十進制轉成16進制時,餘數是0到9之間的數字加上A、B、C、
D、E和F(對應十、十一、十二、1三、14和15)。所以,咱們須要對棧中的數字作個轉化才能夠(行
{5} 和行 {6} )。
參考:《Javascript數據結構與算法》