Typescript學習筆記(四)class 類


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);

 

可以看到HorseSnake類是基類Animal的子類,並且可以訪問其屬性和方法。在這里,子類重寫了父類的move方法,這是繼承的多樣性。對於沒有其他語言基礎的人來說,值得一提的是,

super,super是代指父類。super()執行一遍父類的構造函數,而super.move則是調用父類的move方法。

接下來,講一下類中的一些修飾字,比如public,private,protected,staticabstract,大體就這些了,這些東西什么用呢?

先寫出來: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;

 

 

 

當實例調用他的屬性就報錯。

這里有一點就是當我們比較兩種不同的類型時,並不在乎它們從哪兒來的,如果所有成員的類型都是兼容的,我們就認為它們的類型是兼容的。

然而,當我們比較帶有privateprotected成員的類型的時候,情況就不同了。 如果其中一個類型里包含一個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屬性。

( 因為AnimalRhino共享了來自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的實例。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM