初識前端中的棧

愛忘的旺仔發表於2019-06-04

什麼是棧?

  • 棧是一種遵從先進後出(Last In First Out )原則的有序集合。新新增的元素儲存在棧頂,越早新增的元素越接近棧底。刪除元素只能從棧頂刪除。

    • 舉慄: 吃瓜群眾圍觀原配打小三(嘿嘿)。越早來的群眾越接近事發現場(舊元素在棧底),剛來的群眾只能在外圍(新元素在棧頂)。這時候散場了,外圍的走完裡面的群眾才能走出來(刪除元素)。
    • 棧也被程式語言的編譯器和記憶體中儲存變數、方法呼叫等,也被用於瀏覽器歷史記錄(瀏覽器的返回按鈕)
    • 在javascript中有兩種建立棧的方式

    基於陣列的棧

    class StackArray {
        constructor() {
        this.items = [];
        }
    }
複製程式碼

這便是一個基於陣列建立的棧,然後我們要為它宣告一些方法。

1、新增元素

  • 要注意該方法只新增元素到棧頂,也就是這個陣列的末尾。因此我們可以用陣列的push方法來實現
    push(element) {
    this.items.push(element);
  }
複製程式碼

2、刪除元素

  • 同樣只能從棧頂刪除,因為是基於陣列建立的,所以可以使用陣列的pop方法
pop() {
    return this.items.pop();
  }
複製程式碼

3、檢視棧頂元素

  • 利用陣列的長度來檢視陣列最後一個元素
peek() {
   return this.items[this.items.length - 1];
 }
複製程式碼

4、檢查棧是否為空

  • 如果陣列的長度為零,那它就是空的
isEmpty() {
   return this.items.length === 0;
 }
複製程式碼

5、清空棧元素

  • 可以直接把陣列重置為空
clear() {
   this.items = [];
 }
複製程式碼

這是我舉慄建立的幾個簡單方法,接下來讓我使用這個Stack類

const stack = new StackArray();
console.log(stack.isEmpty()) // 此時輸出為true
stack.push(5);      // [] ==>[5]
stack.push(8);     //  [5] ==>[5,8]
console.log(stack.peek()); // 輸出為8
stack.push(11);    // [5,8] ==>[5,8,11]
console.log(stack.isEmpty()) // 這時的輸出為false
stack.pop();  // [5,8,11] ==>[5,8]
複製程式碼
  • 以上便是一個簡單的基於陣列的棧。建立一個Stack類最簡單的方式就是用一個陣列來儲存元素。在使用陣列時,大部分方法的時間複雜度是O(n)。意思是,我們需要迭代整個陣列直到找到要找的那個元素,如果它在棧底,就需要迭代陣列的所有位置,其中的n代表陣列的長度。如果陣列元素越多,所需時間就越長。另外,陣列是元素的一個有序集合,為了保證元素排列有序,它會佔用更多的記憶體空間。
  • 如果我們能直接獲取元素,佔用較少的記憶體空間,並且仍然保證所有元素按照我們的需要排列,那不是更好嗎?我們可以使用一個javascript物件來儲存所有的棧元素。

基於物件的棧

class Stack{
    constructor(){
        this.count=0;
        this.items = {};
    }
}
複製程式碼

這就是一個基於物件的棧,因為物件沒有length屬性,我們要使用一個count屬性來記錄棧的大小。下面是物件棧的一些簡單的方法。

1、新增元素

  • 我們用count變數來作為物件的鍵名,插入的元素就是它的值,同時在向棧插入元素後,我們遞增count變數
push(element) {
   this.items[this.count] = element;
   this.count++;
 }
複製程式碼

2、檢查棧是否為空

  • 這個直接判斷count的值就可以了
isEmpty() {
   return this.count === 0;
複製程式碼

3、刪除元素

  • 首先要判斷它是否為空,(如果為空那還有啥可刪的嘛!) ==>然後將count屬性減1==>找到棧頂的值並儲存==>然後用物件的delete方法將其刪除==>最後做一個像陣列的pop方法一樣的功能,將刪除的元素返回
pop() {
   if (this.isEmpty()) { // 判斷是否為空
     return undefined;
   }
   this.count--; // 棧的大小減1
   const result = this.items[this.count]; // 儲存棧頂值
   delete this.items[this.count]; // 刪除棧頂值
   return result; // 返回刪除值
 }
複製程式碼

4、建立toString方法

  • 在陣列版本中,我們不需要關心toString方法的實現,因為資料結構可以直接使用陣列原生的toString方法。對於使用物件的版本,我們來建立一個toString方法來像陣列一樣列印出棧的內容。首先,判斷是否為空==>如果不空就用棧底的第一個元素作為字串的初始值==>然後迭代整個棧的鍵==>用上一個相加好的字串加上當前的字串,中間用逗號隔開。這樣寫的好處是如果棧只有一個元素,迴圈將不會執行(畢竟能不迴圈最好不要嘛)。
toString() {
   if (this.isEmpty()) {
     return '';
   }
   let objString = `${this.items[0]}`;
   for (let i = 1; i < this.count; i++) {
     objString = `${objString},${this.items[i]}`;
   }
   return objString;
 }
複製程式碼
  • 好了,實現完toString方法後,我們就完成了一個簡單的基於物件的棧類。對於使用這個棧的開發者,選擇使用基於陣列或者基於物件的版本並不重要。兩者都可以實現相同的功能,只是內部實現很不一樣!!!
除了toString方法,我們建立其它物件棧方法的複雜度均為O(1),代表我們可以直接找到目標元素並對其進行操作(push、pop)
複製程式碼

如何保護資料結構內部元素?

  • 在建立需要別的開發者也可以使用的資料結構或物件時,我們希望保護內部的元素,只有我們暴露出的方法才能修改內部結構。要確保元素只會被新增到棧頂,而不是其它任何位置。但是我們上面所宣告的兩種方式並不是。那我們基於物件建立的類來說。舉慄說明:
const stack = new Stack()
console.log(Object.keys(stack));  // ['count','items']
console.log(stack.items); // 可以直接訪問這個類的內部屬性
複製程式碼

我們可以通過Object.keys方法來直接獲取這個物件上的所有屬性,然後我們就能對暴露出來的屬性賦新的值。這是不允許的!接下來我們來看如何使用Javascript來實現私有屬性的方法。

1、下劃線命名約定

  • 有的開發者喜歡在Javascript中使用下劃線命名約定來標記一個屬性為私有屬性。
class Stack {
    constructor() {
      this._count = ();
      this._items = {};
    }
}
複製程式碼
  • 但這種方式只是一種約定,並不能保護資料,而且只能依賴於使用我們程式碼的開發者所具備的常識

2、用ES6的WeakMap實現類

  • ES6的WeakMap可以儲存鍵值對,其中鍵是物件,值可以是任意資料型別。我們來寫一個用WeakMap來儲存items屬性的陣列版本的類
const items = new WeakMap();  // 宣告一個WeakMap型別的變數items
class Stack {
    constructor () {
        items.set(this,[]); // 在constructor中,以this為鍵,把代表棧的陣列存入items
    }
    push(element){
        const s = items.get(this); // 從WeakMap中取出值,即以this為鍵從items中取值
        s.push(element);
    }
    pop(){
        const s = items.get(this);
        const r = s.pop();
        return r;
    }
}
複製程式碼
  • 這樣做,items在Stack類裡是真正的私有屬性。採用這種方法,程式碼的可讀性不強,而且在擴充套件該類時無法繼承私有屬性。兩種方式也是都有各自的優缺點,魚與熊掌不可兼得呦。

最後

  • 本文的內容來自本人閱讀過《學習Javascript資料結構與演算法》後按照個人的理解對第四章的部分內容做得一個簡單的整理,如果各位看官覺得還可以,請給小弟點個贊。我是一個努力幹活還不磨人的小妖精~~~

相關文章