typescript類(學習筆記非幹貨)
我們聲明一個 Greeter類。這個類有3個成員:一個叫做greeting的屬性,一個構造函數和一個greet方法。
We declare a Greeter class. This class has three members: an attribute called greeting, a constructor, and a green method
我們在引用任何一個類成員的時候都用了this。它表示我們訪問的是類的成員。
We use this when we refer to any class member. It means that we are visiting members of the class.
In the last line, we use new to construct an instance of the Greeter class. It calls the previously defined constructor,
creates a new object of Greeter type, and executes the constructor to initialize it.
class Greeter{ greeting:string; constructor(message:string){ this.greeting=message } greet(){ return "hello"+this.greeting; } } let greeter=new Greeter("world");
繼承
inherit
在TypeScript裏,我們可以使用常用的面向對象模式。當然,基於類的程序設計中最基本的模式是允許使用繼承來擴展現有的類。
In TypeScript, we can use commonly used object-oriented patterns. Of course, the most basic
pattern in class-based programming is to allow inheritance to extend existing classes.
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);
上面這個例子演示了如何在子類裏可以重寫父類的方法。 Snake類和Horse類都創建了move方法,它們重寫了從Animal繼承來的move方法,
使得move方法根據不同的類而具有不同的功能。 註意,即使 tom被聲明為Animal類型,
但因為它的值是Horse,tom.move(34)會調用Horse裏的重寫方法:
The example above demonstrates how to override a parent class in a subclass. Both Snake and Horse classes create motion methods,
which override the motion methods inherited from Animal.It makes the move method have different functions according to
different classes. Notice that even if Tom is declared as an Animal type,But because its value is Horse,
tom. move (34) calls the rewrite method in Horse:
公共,私有與受保護的修飾符
Public, Private and Protected Modifiers
默認為public
Default to public
我們可以用下面的方式來重寫上面的 Animal類:
We can override the Animer class above in the following way:
class Animal{
public name:string;
public constructor(theName:string){this.name=theName}
public move(distanceInMeters:number){
console.log(`${this.name}moved${distanceInMeters}m`);
}
}
理解private
Understand private
當成員被標記成private時,它就不能在聲明它的類的外部訪問。比如:
When a member is marked private, it cannot be accessed externally by declaring its class. For example:
class Animal{
private name:string;
constructor(theName:string){this.name=theName};
}
new Animal("Cat").name;
TypeScript使用的是結構性類型系統。 當我們比較兩種不同的類型時,並不在乎它們從何處而來,
如果所有成員的類型都是兼容的,我們就認為它們的類型是兼容的。
TypeScript uses a structured type system. When we compare two different types, we don‘t care where they come from.
If all members‘types are compatible, we think their types are compatible.
然而,當我們比較帶有private或protected成員的類型的時候,情況就不同了。 如果其中一個類型裏包含一個 private成員,
那麽只有當另外一個類型中也存在這樣一個private成員, 並且它們都是來自同一處聲明時,我們才認為這兩個類型是兼容的。
對於 protected成員也使用這個規則。
However, when we compare types with private or protected members, the situation is different. If one of
the types contains a private member,Then only when there is such a private member in another type, and
they all come from the same declaration, do we think the two types are compatible.This rule is also used for protected members.
class Animal{
private name:string;
constructor(theName:string){this.name=theName}
}
class Bob extends Animal{
constructor(){super("Bob");}
}
class Employee{
private name:string;
constructor(theName:string){this.name=theName}
}
let animal= new Animal("Goat");
let bob=new Bob();
let employee=new Employee("Bob");
animal=bob;
animal=employee;//Animal and Employee are not compatible
因為 Animal和Rhino共享了來自Animal裏的私有成員定義private name: string,因此它們是兼容的。
Because Animal and Rhino share private member definition private name: string from Animal, they are compatible.
然而 Employee卻不是這樣。當把Employee賦值給Animal的時候,得到一個錯誤,說它們的類型不兼容。
Employee, however, is not. When Employee is assigned to Animal, an error is made stating that their types are incompatible.
盡管 Employee裏也有一個私有成員name,但它明顯不是Animal裏面定義的那個。
Although Employee also has a private member name, it is clearly not the one defined in Animal.
理解protected
Understanding protected
protected修飾符與private修飾符的行為很相似,但有一點不同,protected成員在派生類中仍然可以訪問。例如:
Protected modifiers behave very similar to private modifiers, but one difference is that protected members
are still accessible in derived classes. For example:
class Person{
protected name:string;
constructor(name:string){this.name=name;}
class Employee extends Person{
private department:string;
constructor(name:string,department:string){
super(name)
this.department=department;
}
public getElevatorPitch(){
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard=new Employee("Howard","Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name);//error
註意,我們不能在Person類外使用name,但是我們仍然可以通過Employee類的實例方法訪問,
因為Employee是由Person派生而來的。
Note that we can‘t use name outside the Person class, but we can still access it through the instance
method of the Employee class, because Employee is derived from Person.
構造函數也可以被標記成protected。 這意味著這個類不能在包含它的類外被實例化,但是能被繼承。比如,
Constructors can also be marked protected. This means that the class cannot be instantiated outside
the class that contains it, but can be inherited. For example,
class Person{
protected name:string;
protected constructor(theName:string){this.name=theName;}
}
class Employee extends Person{
private department:string;
constructor(name:string,department:string){
super(name);
this.department=department;
}
public getElevatorPitch(){
return `Hello,my name is ${this.name}and i work in ${this.department}`
}
}
let howard=new Employee("Howard","Sales");
let john=new Person("John");
readonly修飾符
readonly Modifier
你可以使用readonly關鍵字將屬性設置為只讀的。 只讀屬性必須在聲明時或構造函數裏被初始化。
You can use the readonly keyword to set the properties to read-only. Read-only
properties must be initialized at declaration time or in constructors.
class Octopus{
readonly name:string;
readonly numberOfLegs:number=8;
constructor(theName:string){
this.name=theName
}
}
let dad=new Octopus("Man with the 8 strong legs");
dad.name="Man with the 3-picec suit";// error! name is readonly.
參數屬性可以方便地讓我們在一個地方定義並初始化一個成員。 下面的例子是對之前 Animal類的修改版,使用了參數屬性:
Parameter attributes make it easy for us to define and initialize a member in one place. The following example is a
modified version of the previous Animal class, using parameter attributes:
class Animal{
constructor(private name:string){}
move(distanceInMeters:number){
console.log(`${this.name}moved${distanceInMeters}m.`);
}
}
註意看我們是如何舍棄了theName,僅在構造函數裏使用private name: string參數來創建和初始化name成員。 我們把聲明和賦值合並至一處。
Notice how we abandoned the Name and only used the private name: string parameter in the constructor to create and initialize name
members. We merge declarations and assignments in one place.
參數屬性通過給構造函數參數添加一個訪問限定符來聲明。 使用 private限定一個參數屬性會聲明並初始化一個私有成員;對於public和protected來說也是一樣。
The parameter attribute is declared by adding an access qualifier to the constructor parameter. Using private to qualify a parameter attribute
declares and in
itializes a private member; the same is true for public and protected
存取器
Accessor
TypeScript支持通過getters/setters來截取對對象成員的訪問。 它能幫助你有效的控制對對象成員的訪問。
TypeScript Support for intercepting access to object members through getters/setters. It can help you
effectively control access to the members of the object.
下面來看如何把一個簡單的類改寫成使用get和set。 首先,我們從一個沒有使用存取器的例子開始。
Let‘s see how to rewrite a simple class to use get and set. First, let‘s start with an example of not using an accessor.
class Employee{
fullName:string;
}
let employee=new Employee();
employee.fullName="Bob Smith";
if(employee.fullName){
console.log(employee.fullName);
}
下面這個版本裏,我們先檢查用戶密碼是否正確,然後再允許其修改員工信息。 我們把對 fullName的直接訪問改成了可以檢查密碼的set方法。
我們也加了一個 get方法,讓上面的例子仍然可以工作。
In the following version, we first check whether the user‘s password is correct, and then allow it to modify employee information.
We changed direct access to fullName to a set method that checks passwords. We also added a get method to make the above example work.
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);
}
我們可以修改一下密碼,來驗證一下存取器是否是工作的。當密碼不對時,會提示我們沒有權限去修改員工。
We can modify the password to verify that the accessor works. When the password is incorrect,
we will be prompted that we do not have permission to modify employees.
對於存取器有下面幾點需要註意的:
For accessors, there are the following points to note:
首先,存取器要求你將編譯器設置為輸出ECMAScript 5或更高。 不支持降級到ECMAScript 3。
其次,只帶有 get不帶有set的存取器自動被推斷為readonly。 這在從代碼生成 .d.ts文件時是有幫助的,
因為利用這個屬性的用戶會看到不允許夠改變它的值。
First, the accessor requires you to set the compiler to output ECMAScript 5 or higher. Degradation to ECMAScript 3
is not supported. Secondly, an access with only get and without set is automatically inferred as readonly. This is helpful
when generating. D. TS files from code, because users using this property will see that it is not allowed to change its value.
靜態屬性
Static attribute
我們使用 static定義origin,因為它是所有網格都會用到的屬性。 每個實例想要訪問這個屬性的時候,都要在 origin前面加上類名。
如同在實例屬性上使用 this.前綴來訪問屬性一樣,這裏我們使用Grid.來訪問靜態屬性。
We use static to define origin, because it is an attribute that all grids will use. When each instance wants to access this property,
it must precede the origin with the class name. Just as we use this. prefix to access attributes on instance attributes,
we use Grid. to access static attributes.
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);
let grid2=new Grid(5.0);
console.log(grid1.calculateDistanceFromOrigin({x:10,y:10}));
console.log(grid2.calculateDistanceFromOrigin({x:10,y:10}));
抽象類
abstract class
抽象類做為其它派生類的基類使用。 它們一般不會直接被實例化。 不同於接口,
Abstract classes are used as base classes for other derived classes. They are generally not instantiated directly. Unlike interfaces,
抽象類可以包含成員的實現細節。abstract關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。
Abstract classes can contain implementation details of members. Abstract keywords are used to define
abstract classes and to define abstract methods within Abstract classes.
abstract class Animal{
abstract makeSound():void;
move():void{
console.log("roaming the earch...");
}
}
抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。 抽象方法的語法與接口方法相似。 兩者都是定義方法簽名但不包含方法體。
然而,抽象方法必須包含 abstract關鍵字並且可以包含訪問修飾符。
Abstract methods in abstract classes do not contain concrete implementations and must be implemented in derived classes.
The syntax of abstract methods is similar to that of interface methods. Both define method signatures but do not contain
method bodies. However, abstract methods must contain Abstract keywords and can contain access modifiers.
abstract class Department{
constructor(public name:string){}
printName():void{
console.log('Department name'+this.name);
}
abstract printMeeting():void;//必須在派生類中實現 Must be implemented in a derived class
}
class AccountingDepartment extends Department{
constructor(){
super("Account and Auditing");
}
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
高級技巧Advanced Skills
當你在TypeScript裏聲明了一個類的時候,實際上同時聲明了很多東西。 首先就是類的 實例的類型。
When you declare a class in TypeScript, you actually declare a lot of things at the same time. The first is the type of instance of the class.
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創建類實例的時候被調用.
We wrote let greeter: Greeter, meaning that the type of instance of the Greeter class is Greeter.
We also created a value called a constructor. This function will be called when we use new to create class instances.
讓我們稍微改寫一下這個例子,看看它們之前的區別:
Let‘s rewrite this example a little and see the difference between them before:
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 greeterMarker:typeof Greeter=Greeter;
greeterMarker.standardGreeting="Hey there";
let greeter2:Greeter=new greeterMarker();
console.log(greeter2.greet());
我們創建了一個叫做 greeterMaker的變量。這個變量保存了這個類或者說保存了類構造函數。然後我們使用 typeof Greeter,
意思是取招待員類的類型,而不是實例的類型。或者更確切的說, “我告訴 Greeter標識符的類型”,也就是構造函數的類型。
這個類型包含了類的所有靜態成員和構造函數。之後,就和前面一樣,在我們 greeterMaker上使用new,創建33 Greeter的實例。
We created a variable called greeterMaker. This variable holds the class or class constructor. Then we use typeof Greeter,
which means the type of the receptionist class, not the type of the instance. Or rather, "I tell Greeter the type of identifier,"
which is the type of constructor. This type contains all the static members and constructors of the class. Then, as before,
we use new on our greeterMaker to create an instance of 172
把類當做接口使用
Use classes as interfaces
類定義會創建兩個東西:類的實例類型和一個構造函數。因為類可以創建出類型,所以你能夠在允許使用接口的地方使用類。
Class definitions create two things: the instance type of the class and a constructor. Because classes
can create types, you can use classes where interfaces are allowed.
class Point{
x:number;
y:number;
}
interface Point3d extends Point{
z:number;
}
let point3d:Point3d={x:1,y:2,x:3};
typescript類(學習筆記非幹貨)