初探 ES6(4)極簡的 Classes
ES6新增加了Classes語法的支援,不過對於Classes語法有所期望的人可能會失望…因為這是一個極其精簡的Classes語法(當初的提案叫做「maximally minimal classes」)。到底情況怎樣呢? Classes in ES6Javascript是一個物件導向的程式語言,基本上使用他的語法就可以實現封裝、繼承、多形等物件導向程式設計的特色。不過他的語法特性,往往會讓從Classical程式語言來的使用者困擾。例如我們要讓sub繼承base,在Javascript經典的做法是: function base() { this.a = 'a'; } base.prototype.getA = function() {return this.a}; function sub() { base.call(this); this.b = 'b'; } sub.prototype = new base(); 這樣做繼承,感覺有點饒舌…如果不想這樣使用prototype,那就需要在constructor函數中定義方法與屬性成員: function base() { this.a = 'a'; this.getA = function() {return this.a}; } function sub() { base.call(this); this.b = 'b'; } 等等。除了new看起來有點像Classical程式語言下的使用方式,其他都很不一樣。 在ES6,新增了class語法,可以讓這個過程稍微簡單一點: class base { constructor() { this.a = 'a'; } getA() {return this.a} } class sub extends base { constructor() { super(); this.b = 'b'; } } 比較舊的語法與ES6 Classes的語法,大致上可以看出:
測試環境在實際測試之前,需要注意:目前可以支援Class的測試環境,只有之前文章介紹過的Traceur Compiler。所以要執行這個範例,需要把他放在Traceur Compiler的環境中。例如: <html> <head> <script src="http://traceur-compiler.googlecode.com/git/bin/traceur.js" type="text/javascript"></script> <script src="http://traceur-compiler.googlecode.com/git/src/bootstrap.js" type="text/javascript"></script> <script> traceur.options.experimental = true; </script> <script type="module"> class Base { constructor() { this.a = 'a'; } getA() {return this.a} } class Sub extends base { constructor() { super(); this.b = 'b'; } } var a = new Base(); alert(a.a); var b = new Sub(); alert(b.a+','+b.b); </script> </head> <body> </body> </html>
ES6 Classes的語法maximally minimal class,顧名思義,就是用最少的新增語法來支援classes。新增的東西真的很少,而且基本上只是一個定義constructor函數與prototype繼承的不同寫法而已。我猜這還是會對已經熟悉class的使用,而初次嘗試ES6 class的人帶來一些困擾。不過還是來看一下包含哪些語法上的更新 * class宣告及class表達式class宣告:class [identifier] [extends 繼承表達式]opt {[class body]} class表達式:class [identifier]opt [extends 繼承表達式]opt {[class body]} 如果有看過規格草案,會發現裡面的定義是,執行class宣告時,會在當前的context中產生跟identifier同名的constructor函數。所以基本上這跟過去Javascript的做法是相等的。至於宣告及表達式的差別,就跟函數的宣告與函數的表達式差不多。如果要把class指派給一個變數時,就可以使用class表達式。例如: <html> <script src="http://traceur-compiler.googlecode.com/git/bin/traceur.js" type="text/javascript"></script> <script src="http://traceur-compiler.googlecode.com/git/src/bootstrap.js" type="text/javascript"></script> <script> traceur.options.experimental = true; </script> <script type="module"> var Base = class { constructor() { this.a = "I'm Base"; } } var a = new Base(); console.log(a.a); </script> <body> </body> </html> * class bodyclass body,是以class的「方法宣告」列表組成,不過跟物件實字不太一樣,只需要方法的identifier、參數、以及方法的body就可以組成列表中的元素。除了方法的宣告,列表元素可以包含分號「;」,不過這大概沒什麼實際作用。另外,也可以在方法宣告中使用在ES5新制定的getter / setter語法,間接定義成員屬性。 所以class body可以是:
* constructor與super過去Javascript是直接使用函數來定義constructor,使用ES6的classes語法,則使用constructor這個特定的方法名稱來定義constructor。如果要在constructor中呼叫父類的constructor,可以透過呼叫super()函數來達成。
* static宣告過去如果不透過prototype,直接用「.」加到constructor函數上的函數,就會變成一個「靜態」的方法,不會被繼承,只能透過consturctor名稱作為identifier來呼叫。同樣地,在classes語法中,可以為方法宣告加入static,達到一樣的效果。不過在ES6 classes規格中,除了static就沒有其他的scope宣告了(例如大家熟悉的private, public等等)。static方法必須透過class identifier來取用,透過實例是無法存取的。例如: <html> <script src="http://traceur-compiler.googlecode.com/git/bin/traceur.js" type="text/javascript"></script> <script src="http://traceur-compiler.googlecode.com/git/src/bootstrap.js" type="text/javascript"></script> <script> traceur.options.experimental = true; </script> <script type="module"> class Base { constructor() { this.a = ''; } static sBase() {console.log('static base.sBase')} } class Sub extends Base { constructor() { super(); } static sSub() {console.log('static sub.sSub')} } Base.sBase(); Sub.sSub(); Sub.sBase(); var a = new Sub(); a.sSub(); </script> <body> </body> </html> 從traceur跑的這個例子可以看出來,用Sub的實例a來呼叫sSub()靜態方法時,就會出錯。在Chrome的Dev Console會看到「TypeError: undefined is not a function」的錯誤訊息。不過在traceur中,static方法可以被繼承…這好像不太對XD * 繼承表達式(Inheritance Expression)前面的例子已經示範過extends的使用。不過接在extends之後的是一個繼承表達式,並不只是一個reference。所以只要執行的結果是函數,都可以放在這裡。例如: <html> <head> <script src="http://traceur-compiler.googlecode.com/git/bin/traceur.js" type="text/javascript"></script> <script src="http://traceur-compiler.googlecode.com/git/src/bootstrap.js" type="text/javascript"></script> <script> traceur.options.experimental = true; </script> <script type="module"> class A extends function(a, b) { this.a = a; this.b = b; this.avg = function() {return (this.a+this.b)/2;} } { constructor(a, b) { super(a, b); } sum() {return this.a+this.b;} } var a = new A(8, 6); alert(a.avg()); alert(a.sum()); </script> <body> </body> </html>
* 無法使用Classes宣告語法來定義Class的成員屬性另外,在class宣告中,只能宣告成員方法或使用getter/setter來宣告成員屬性,這其實有一些不方便,如果不在constructor中建立成員屬性,那還是得用prototype。例如: class Foo {} Foo.prototype.Bar = ''; 也因為這樣,雖然可以用getter/setter來宣告成員屬性,但是沒地方存… class Foo { get Bar(){}//沒東西可以返回 set Bar(v){}//沒地方存放 } 這時候,就只好走回Javascript的老路,利用scope來做出private變數: <html> <script src="http://traceur-compiler.googlecode.com/git/bin/traceur.js" type="text/javascript"></script> <script src="http://traceur-compiler.googlecode.com/git/src/bootstrap.js" type="text/javascript"></script> <script> traceur.options.experimental = true; </script> <script type="module"> function newFoo() { var _bar; return new (class { get Bar(){return _bar;} set Bar(v){_bar = v;} })(); } var a = newFoo(); a.Bar = 'abc'; var b = newFoo(); b.Bar = 'def'; console.log(a.Bar);//印出abc console.log(b.Bar);//印出def </script> <body> </body> </html> 這樣寫還是很煩人…而且不知道怎樣繼承XD * 內建物件Subclassing另外,雖然跟Classes的支援不一定直接相關,ES6對於內建的物件會有一個重大的修改,就是讓內建的物件支援subclassing。例如: class MyArray extends Array { constructor(...args) {super(...args)} average() {//回傳陣列元素的平均值} middle() {//排序陣列元素後,回傳中位數} } 不過目前的Javascript環境應該都還沒支援這個,所以還無法測試。(不過要支援這個,需要改很多東西…,有興趣的話,可以參考:Axel Rauschmayer的文章:Subclassing builtins in ECMAScript 6)。 總之用幾句話來勾勒ES6 Classes的重點:
不過至少在語法上,比過去利用constructor函數來說,是明確一些。 |
caterpillar
05/19
基本上是讓過去模擬類別的作法有個標準方式 …
用 class 來模擬 private 的作法的話,這種封裝風格如何?
class Foo {
constructor(bar) {
this.__bar__ = bar;
Object.defineProperty(this, '__bar__', {
writable : true,
enumerable : false
});
}
get bar(){ return this.__bar__; }
set bar(v){ this.__bar__ = v; }
}
從 Python 借過來的概念 … XD