用js來實現那些數據結構04(棧01-棧的實現)

  其實說到底,在js中棧更像是一種變種的數組,只是沒有數組那麼多的方法,也沒有數組那麼靈活。可是棧和隊列這兩種數據結構比數組更加的高效和可控。而在js中要想模擬棧,依據的主要形式也是數組。html

  從這篇文章開始,可能會接觸到一些原型,原型鏈,類,構造函數等相關的js概念,可是這裏並不會過多的介紹這些概念,必要的時候會進行一些簡要的說明,推薦你們去看看湯姆大叔的深刻理解Javascript系列,王福朋大神的深刻理解Javascript原型和閉包系列。都是極爲不錯的深度好文,推薦你們能夠深刻學習。es6

  要想實現一個數據結構,首先你要明白它的基本原理,那麼棧是什麼?又是如何工做的呢?數組

  棧(stack)是一種遵循後進先出(Last In First Out)原則的有序集合。新添加的元素和待刪除的元素都保存在棧的同一端,稱爲棧頂,另外一端就叫作棧底。在棧裏,新元素都接近棧頂,舊元素都靠近棧底。其實能夠把棧簡單理解成往一個木桶裏堆疊的放入物品,最後放進去的在桶的頂端,也是能夠最早拿出來的,而最早放進去的卻在桶的底部,只有把全部上面的物品拿出來以後才能夠拿走底部的物品。數據結構

  對於數組來講,能夠添加元素,刪除元素,獲取數組的長度以及返回對應下標獲得值,那麼在開始構造一個棧以前,咱們須要瞭解一下棧都有哪些基本操做。閉包

  一、壓棧,也稱之爲入棧,也就是把元素加入棧中。就像是數組中的push同樣。函數

  二、出棧,移除棧頂的元素。就像是數組中的pop同樣。學習

  三、獲取棧頂的元素,不對棧作任何其餘操做。就像是在數組中經過下標獲取對應的值同樣。測試

  四、判斷棧是否爲空。就像是判斷數組的長度是否爲0同樣。this

  五、清空棧,也就是移除棧裏的全部元素。就像是把數組的長度設置爲0同樣。spa

  六、獲取棧裏的元素個數。就像是數組的length屬性同樣。

  那麼,我相信我你們已經對棧有了一個基本的瞭解,那麼咱們接下來就看看如何經過構造函數來實現一個本身的js棧。

function Stack () {
    var items = [];

    //首先,咱們來實現一個入棧的方法,這個方法負責往棧里加入元素,要注意的是,該方法只能添加元素到棧頂,也就是棧的尾部。
    this.push = function (ele) {
        items.push(ele)
    }

}

var stack = new Stack();

  咱們聲明一個構造函數,而且在構造函數中生命一個私有變量items,做爲咱們Stack類儲存棧元素的基本支持。而後,加入一個push方法,經過this來使其指向調用該方法的實例。下面咱們還會經過這樣的方式依次添加其餘的方法。

function Stack () {
    var items = [];

    //首先,咱們來實現一個入棧的方法,這個方法負責往棧里加入元素,要注意的是,該方法只能添加元素到棧頂,也就是棧的尾部。
    this.push = function (ele) {
        items.push(ele);
    }

    //而後咱們再添加一個出棧的方法,一樣的,咱們只能移除棧頂的元素。
    this.pop = function (ele) {
        return items.pop();
    }

    //查看棧頂,也就是棧的尾部元素是什麼
    this.peek = function () {
        return items[items.length - 1];
    }

    //檢查棧是否爲空
    this.isEmpty = function () {
        return items.length == 0;
    }

    //檢查棧的長度
    this.size = function () {
        return items.length;
    }

    //清空棧
    this.clear = function () {
        items = [];
    }

    //打印棧內元素
    this.print = function () {
        console.log(items.toString())
    }
}

  這樣咱們就經過構造函數完整的建立了一個棧。咱們能夠經過new命令實例化一個Stack對象來測試一下咱們的棧好很差用。

var stack = new Stack();
console.log(stack.isEmpty());//true
stack.push(1);
stack.print();
stack.push(3);
stack.print();
console.log(stack.isEmpty());//false
console.log(stack.size());//2
stack.push(10);
stack.print();
stack.pop();
stack.print();
stack.clear();
console.log(stack.isEmpty());//true

  咱們發現咱們的Stack類執行的還算不錯。那麼還有沒有其餘的方式能夠實現Stack類呢?在ES6以前我可能會遺憾懵懂的對你Say No。可是如今咱們能夠一塊兒來看看ES6帶咱們的一些新鮮玩意。

   在開始改造咱們的Stack類以前,須要先說一下ES6的幾個概念。Class語法Symbol基本類型WeakMap。簡單解釋一下,以對後面的改造不會一臉懵逼,而你們想要更深刻的瞭解ES6新增的各類語法,能夠去自行查閱。

  Class語法簡單來講就是一個語法糖,它的功能ES5也是徹底能夠實現的,只是這樣看寫起來更加清晰可讀,更像是面向對象的語法。

  Symbol是ES6新增的一個基本類型,前面幾篇文章說過,ES5只有6中數據類型,可是在ES6中又新增一種數據類型Symbol,它表示獨一無二的值。

  WeakMap,簡單來講就是用於生成鍵值對的集合,就像是對象({})同樣,WeakMap的一個重要用處就是部署私有屬性。

  固然,上面的簡單介紹可不只僅是這樣的,真正的內容要比這些多得多。

  那麼在你們知道了它們的一些基本意義。我們開始改造一下Stack類

class Stack {
  constructor() {
    this.items = [];
  }

  push(element) {
    this.items.push(element);
  }

  pop() {
    return this.items.pop();
  }

  peek() {
    return this.items[this.items.length - 1];
  }

  isEmpty() {
    return this.items.length === 0;
  }

  size() {
    return this.items.length;
  }

  clear() {
    this.items = [];
  }

  toString() {
    return this.items.toString();
  }

  print() {
      console.log(this.items.toString())
  }

}

  這是用class來實現的Stack類,其實咱們能夠看一下,除了使用了constructor構造方法之外,其實並無什麼本質上的區別。

  那麼咱們還可使用Symbol數據類型來實現,簡單改造一下:

const _items = Symbol('stackItems');

class Stack {
  constructor() {
    this[_items] = [];
  }

  push(element) {
    this[_items].push(element);
  }

  pop() {
    return this[_items].pop();
  }

  peek() {
    return this[_items][this[_items].length - 1];
  }

  isEmpty() {
    return this[_items].length === 0;
  }

  size() {
    return this[_items].length;
  }

  clear() {
    this[_items] = [];
  }

  print() {
    console.log(this.toString());
  }

  toString() {
    return this[_items].toString();
  }
}

  使用Symbol也沒有大的變化,只是聲明瞭一個獨一無二的_items來代替構造方法中的數組。

  可是這樣的實現方式有一個弊端,那就是ES6新增的Object.getOwnPropertySymbols方法能夠讀取到類裏面聲明的全部Symbols屬性。

const stack = new Stack();
const objectSymbols = Object.getOwnPropertySymbols(stack);
stack.push(1);
stack.push(3);
console.log(objectSymbols.length); // 1
console.log(objectSymbols); // [Symbol()]
console.log(objectSymbols[0]); // Symbol()
stack[objectSymbols[0]].push(1);
stack.print(); // 1, 3, 1

  不知道你們注意沒有,咱們定義的Symbol是在構造函數以外的,所以誰均可以改動它。因此這樣的方式還不是很完善的。那麼咱們還可使用ES6的WeakMap,而後用閉包實現私有屬性。

//經過閉包把聲明的變量變成私有屬性
let Stack = (function () {
//聲明棧的基本依賴
const _items = new WeakMap();
//聲明計數器
const _count = new WeakMap();

class Stack {
  constructor() {
//初始化stack和計數器的值,這裏的set是WeakMap的自身方法,經過set和get來設置值和取值,這裏用this做爲設置值的鍵名,那this又指向啥呢?自行console!
    _count.set(this, 0);
    _items.set(this, {});
  }

  push(element) {
//在入棧以前先獲取長度和棧自己
    const items = _items.get(this);
    const count = _count.get(this);
//這裏要注意_count但是從0開始的噢
    items[count] = element;
    _count.set(this, count + 1);
  }

  pop() {
//若是爲空,那麼則沒法出棧
    if (this.isEmpty()) {
      return undefined;
    }
//獲取items和count,使長度減小1
    const items = _items.get(this);
    let count = _count.get(this);
    count--;
//從新爲_count賦值
    _count.set(this, count);
//刪除出棧的元素,並返回該元素
    const result = items[count];
    delete items[count];
    return result;
  }

  peek() {
    if (this.isEmpty()) {
      return undefined;
    }
    const items = _items.get(this);
    const count = _count.get(this);
//返回棧頂元素
    return items[count - 1];
  }

  isEmpty() {
    return _count.get(this) === 0;
  }

  size() {
    return _count.get(this);
  }

  clear() {
    /* while (!this.isEmpty()) {
        this.pop();
      } */
    _count.set(this, 0);
    _items.set(this, {});
  }

  toString() {
    if (this.isEmpty()) {
      return '';
    }
    const items = _items.get(this);
    const count = _count.get(this);
    let objString = `${items[0]}`;
    for (let i = 1; i < count; i++) {
      objString = `${objString},${items[i]}`;
    }
    return objString;
  }

  print() {
      console.log(this.toString());
  }
}

return Stack;
})()

const stack = new Stack();
stack.push(1);
stack.push(3);
stack.print(); // 1, 3, 1

  這是最終比較完善的版本了。那麼不知道你們注沒注意到一個小細節,前面咱們只是聲明一個變量,先無論他是否是私有的,就是數組,整個Stack構造函數都是基於items數組來進行各類方法的。

可是這裏經過WeakMap做爲基本,咱們卻多用了一個_count,前面說了_count是計數器,那麼爲啥要用計數器?由於WeakMap是鍵值對的「對象類型」,自己是沒有像數組這樣的長度之說的,因此須要一個計數器來代替數組的下標,以實現基於Stack的各類方法。

   到這裏基本上就完成了咱們的棧,下一篇文章會看看如何用咱們寫好的棧去作一些有趣事情。

 

  最後,因爲本人水平有限,能力與大神仍相差甚遠,如有錯誤或不明之處,還望你們不吝賜教指正。很是感謝!

相關文章
相關標籤/搜索