typescript的類,與c#,java等語言的類類似。也是包含了一大部分的es6的實現。我會用最通俗的語言講一下對coding有用的地方。
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } let greeter = new Greeter("world");
這是一個簡單的類的定義。 Greeter為類名,這個類里面有greeting屬性和greet方法。constructor方法,熟悉es6的同學應該清楚,constructor是這個類的構造函數,即每次實例化這個類的時候都會執行一下這個構造函數。注意ts對於es6的提升,在greeting屬性定義的時候進行類型檢測。后面實例化類與js相同。
class的繼承
和大多數語言一樣,class的繼承用extends關鍵字。
class Animal { name:string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
可以看到Horse和Snake類是基類Animal的子類,並且可以訪問其屬性和方法。在這里,子類重寫了父類的move方法,這是繼承的多樣性。對於沒有其他語言基礎的人來說,值得一提的是,
super,super是代指父類。super()執行一遍父類的構造函數,而super.move則是調用父類的move方法。
接下來,講一下類中的一些修飾字,比如public,private,protected,static ,abstract,大體就這些了,這些東西什么用呢?
先寫出來:public,定義類的公有成員,就是說我們可以自由訪問這些成員,如果不寫,默認就是public。
private,定義類的私有成員,就是說不能在聲明它的類的外部訪問它,當然在實例里也不行。
protected,定義類的保護成員,與樓上的區別就是他在實例里可以訪問。
static,定義類的靜態屬性。存在於類本身上面而不是類的實例上,所以訪問的時候要加該類名。
abstract,定義抽象類,它們不會被實例化,僅提供繼承
1, public,公有成員,默認都是這個。
2,private
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // Error: 'name' is private;
當實例調用他的屬性就報錯。
這里有一點就是當我們比較兩種不同的類型時,並不在乎它們從哪兒來的,如果所有成員的類型都是兼容的,我們就認為它們的類型是兼容的。
然而,當我們比較帶有private或protected成員的類型的時候,情況就不同了。 如果其中一個類型里包含一個private成員,那么只有當另外一個類型中也存在這樣一個private成員, 並且它們是來自同一處聲明時,我們才認為這兩個類型是兼容的。
protected也一樣。
show個例子。
class Animal { private name: string; constructor(theName: string) { this.name = theName; } } class Rhino extends Animal { constructor() { super("Rhino"); } } class Employee { private name: string; constructor(theName: string) { this.name = theName; } } let animal = new Animal("Goat"); let rhino = new Rhino(); let employee = new Employee("Bob"); animal = rhino; animal = employee; // Error: Animal and Employee are not compatible
因為animal和employee不同源,所以不能相互賦值,雖然他們都長得一樣。Animal的子類的實例就可以賦值了,因為他們同源,也存在name屬性。
( 因為Animal和Rhino共享了來自Animal里的私有成員定義private name: string,因此它們是兼容的。 然而Employee卻不是這樣。當把Employee賦值給Animal的時候,得到一個錯誤,說它們的類型不兼容。 盡管Employee里也有一個私有成員name,但它明顯不是Animal里面定義的那個)
3.protected
改一下上面的例子
class Animal { protected name: string; constructor(theName: string) { this.name = theName; } } class Rhino extends Animal { constructor() { super("Rhino"); } getName():string { return this.name; } } let animal = new Rhino( ); console.log(animal.getName());
這樣可以取到animal的name屬性,但是你改成private就不行了,因為他不允許子類使用的。這就是protected與private的區別。
4.static
這是類的靜態屬性,這些屬性存在於類本身上面而不是類的實例上。這就明確了,調用的通過類名。
class Grid { static origin = {x: 0, y: 0}; calculateDistanceFromOrigin(point: {x: number; y: number;}) { let xDist = (point.x - Grid.origin.x); let yDist = (point.y - Grid.origin.y); return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale; } constructor (public scale: number) { } } let grid1 = new Grid(1.0); // 1x scale let grid2 = new Grid(5.0); // 5x scale console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10})); console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));
Grid.origin這樣調用。
5.abstract
抽象類,僅用來繼承,就像sass里面的占位符選擇器%,繼承才用到,下面的%ir
%ir{ color: transparent; text-shadow: none; background-color: transparent; border: 0; } #header{ h1{ @extend %ir; width:300px; } }
不同於接口,抽象類可以包含成員的實現細節。 abstract關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。
抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。抽象方法必須使用abstract關鍵字並且可以包含訪問符。
abstract class Department { constructor(public name: string) { } printName(): void { console.log('Department name: ' + this.name); } abstract printMeeting(): void; // 必須在派生類中實現 } class AccountingDepartment extends Department { constructor() { super('Accounting and Auditing'); // constructors in derived classes must call super() } printMeeting(): void { console.log('The Accounting Department meets each Monday at 10am.'); } generateReports(): void { console.log('Generating accounting reports...'); } } let department: Department; // ok to create a reference to an abstract type department = new Department(); // error: cannot create an instance of an abstract class department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass department.printName(); department.printMeeting(); department.generateReports(); // error: method doesn't exist on declared abstract type
AccountingDepartment 類繼承Department抽象類,printName抽象方法,在子類中重定義了,generateReports方法在抽象類中不存在,被拒絕了。當然,創建抽象類的子類也被拒絕了。
參數屬性:
不知道上面那個例子的constructor看懂沒,其實這個
constructor(public name: string) { }
就是
public name: string constructor(name: string) { this.name=name;}
的簡寫。知道就好。
存取器:
類的存取器,跟es6一樣,其實就是個內置的屬性攔截器,或者叫裝飾器。上一下例子就會了。
let passcode = "secret passcode"; class Employee { private _fullName: string; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (passcode && passcode == "secret passcode") { this._fullName = newName; } else { console.log("Error: Unauthorized update of employee!"); } } } let employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
在屬性面前加get是獲取的時候的攔截,加set是設置的時候攔截。 alert(employee.fullName)就是獲取了,就會觸發get后面的那個函數。employee.fullName = "Bob Smith";賦值就觸發了set,觸發后面的函數。這個例子是在設置fullName的時候先驗證密碼,錯誤就不允許設置。
注意:若要使用存取器,要求設置編譯器輸出目標為ECMAScript 5或更高。
高級技巧:
構造函數:當你在TypeScript里定義類的時候,實際上同時定義了很多東西。 首先是類的實例的類型。
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } let greeter: Greeter; greeter = new Greeter("world"); console.log(greeter.greet());
我們寫了let greeter: Greeter,意思是Greeter類實例的類型是Greeter。
我們也創建了一個叫做構造函數的值。 這個函數會在我們使用new創建類實例的時候被調用。 下面我們來看看,上面的代碼被編譯成JavaScript后是什么樣子的:
let Greeter = (function () { function Greeter(message) { this.greeting = message; } Greeter.prototype.greet = function () { return "Hello, " + this.greeting; }; return Greeter; })(); let greeter; greeter = new Greeter("world"); console.log(greeter.greet());
上面的代碼里,let Greeter將被賦值為構造函數。
當我們使用new並執行這個函數后,便會得到一個類的實例。 這個構造函數也包含了類的所有靜態屬性。
換個角度說,我們可以認為類具有實例部分與靜態部分這兩個部分。
改一下例子
class Greeter { static standardGreeting = "Hello, there"; greeting: string; greet() { if (this.greeting) { return "Hello, " + this.greeting; } else { return Greeter.standardGreeting; } } } let greeter1: Greeter; greeter1 = new Greeter(); console.log(greeter1.greet()); let greeterMaker: typeof Greeter = Greeter; greeterMaker.standardGreeting = "Hey there!"; let greeter2:Greeter = new greeterMaker(); console.log(greeter2.greet());
這個例子里,greeter1與之前看到的一樣。 我們實例化Greeter類,並使用這個對象。 與我們之前看到的一樣。
再之后,我們直接使用類。 我們創建了一個叫做greeterMaker的變量。 這個變量保存了這個類或者說保存了類構造函數。 然后我們使用typeof Greeter,意思是取Greeter類的類型,而不是實例的類型。
或者更確切的說,"告訴我Greeter標識符的類型",也就是構造函數的類型。 這個類型包含了類的所有靜態成員和構造函數。
之后,就和前面一樣,我們在greeterMaker上使用new,創建Greeter的實例。
