我們花點時間分析一下代碼,看看是否能用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語法有沒有其他的方法可以創建私有屬性。
用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
屬性是一個數組,可以進行任意的數組操作,比如從中間刪除或添加元素。我們操作的是棧,不應該出現這種行為。還有第三個方案。
用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; //{5} });
當
Stack
函數里的構造函數被調用時,會返回Stack
類的一個實例(行{5}
)。關於JavaScript閉包,請閱讀http://www.w3schools.com/js/js_function_closures.asp。
現在,
Stack
類有一個名為items
的私有屬性。雖然它很醜陋,但畢竟實現了私有屬性。然而,用這種方法的話,擴展類無法繼承私有屬性。魚與熊掌不可兼得!把上面的代碼跟本章最初實現的
Stack
類做個比較,我們會發現有一些相似之處:function Stack { let items = ; //其他方法 }
事實上,儘管ES6引入了類的語法,我們仍然不能像在其他編程語言中一樣聲明私有屬性或方法。有很多種方法都可以達到相同的效果,但無論是語法還是性能,這些方法都有各自的優點和缺點。
哪種方法更好?這取決於你在實際項目中如何使用本書中這些算法,要處理的數據量,要創建的實例個數,以及其他約束條件。最終,還是取決於你。
在本書提供下載的代碼中,所有的數據結構都會包含簡單的函數類,以及ES6的
WeakMap
和閉包的創建方法。