讀古今文學網 > 學習JavaScript數據結構與算法(第2版) > 3.2 ECMAScript 6和Stack類 >

3.2 ECMAScript 6和Stack類

我們花點時間分析一下代碼,看看是否能用ECMAScript 6(ES6)的新功能來改進。

我們創建了一個可以當作類來使用的Stack函數。JavaScript函數都有構造函數,可以用來模擬類的行為。我們聲明了一個私有的items變量,它只能被Stack函數/類訪問。然而,這個方法為每個類的實例都創建一個items變量的副本。因此,如果要創建多個Stack實例,它就不太適合了。

看看如何用ES6新語法聲明Stack類,並和前面的做法比較一下優缺點。

用ES6語法聲明Stack類

首先來分析下面的代碼:

class Stack {
  constructor  {
    this.items = ; //{1}
  }
  push(element){
    this.items.push(element);
  }
  //其他方法
}

  

我們只是用ES6的簡化語法把Stack函數轉換成Stack類。這種方法不能像其他語言(Java、C++、C#)一樣直接在類裡面聲明變量,只能在類的構造函數constructor裡聲明(行{1}),在類的其他函數里用this.nameofVariable就可以引用這個變量。

儘管代碼看起來更簡潔、更漂亮,變量items卻是公共的。ES6的類是基於原型的。雖然基於原型的類比基於函數的類更節省內存,也更適合創建多個實例,卻不能夠聲明私有屬性(變量)或方法。而且,在這種情況下,我們希望Stack類的用戶只能訪問暴露給類的方法。否則,就有可能從棧的中間移除元素(因為我們用數組來存儲其值),這不是我們希望看到的。

看看ES6語法有沒有其他的方法可以創建私有屬性。

  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中,以thisStack類自己的引用)為鍵,把代表棧的數組存入items

    • {3},從WeakMap中取出值,即以this為鍵(行{2}設置的)從items中取值。

    現在我們知道,itemsStack類裡是真正的私有屬性了,但還有一件事要做。items現在仍然是在Stack類以外聲明的,因此誰都可以改動它。我們要用一個閉包(外層函數)把Stack類包起來,這樣就只能在這個函數里訪問WeakMap

    let Stack = (function  {
      const items = new WeakMap;
      class Stack {
         constructor  {
           items.set(this, );
         }
         //其他方法
      }
      return Stack; //{5}
    });
    
     

    Stack函數里的構造函數被調用時,會返回Stack類的一個實例(行{5})。

     關於JavaScript閉包,請閱讀http://www.w3schools.com/js/js_function_closures.asp。

    現在,Stack類有一個名為items的私有屬性。雖然它很醜陋,但畢竟實現了私有屬性。然而,用這種方法的話,擴展類無法繼承私有屬性。魚與熊掌不可兼得!

    把上面的代碼跟本章最初實現的Stack類做個比較,我們會發現有一些相似之處:

    function Stack {
      let items = ;
      //其他方法
    }
    
      

    事實上,儘管ES6引入了類的語法,我們仍然不能像在其他編程語言中一樣聲明私有屬性或方法。有很多種方法都可以達到相同的效果,但無論是語法還是性能,這些方法都有各自的優點和缺點。

    哪種方法更好?這取決於你在實際項目中如何使用本書中這些算法,要處理的數據量,要創建的實例個數,以及其他約束條件。最終,還是取決於你。

     在本書提供下載的代碼中,所有的數據結構都會包含簡單的函數類,以及ES6的WeakMap和閉包的創建方法。