TypeScript與物件導向

Qsy發表於2021-09-22

1、引

簡而言之就是程式之中所有的操作都需要通過物件來完成。一切操作都要通過物件,也就是所謂的物件導向,在程式中物件被分成了兩個部分資料和功能,以人為例,人的姓名、性別、年齡、身高等屬於資料,人可以說話、走路、吃飯、睡覺這些都屬於人的功能,資料在物件中被稱為屬性,而功能被稱為方法,所以簡而言之在程式中一切皆物件

2、類(class)

// 使用class關鍵字來定義一個類
/*
*   物件中主要包含了兩個部分:
*       屬性
*       方法
* */
class Person{

    /*
    *   直接定義的屬性是例項屬性,需要通過物件的例項去訪問:
    *       const per = new Person();
    *       per.name
    *
    *   使用static開頭的屬性是靜態屬性(類屬性),可以直接通過類去訪問
    *       Person.age
    *
    *   readonly開頭的屬性表示一個只讀的屬性無法修改
    * */

    // 定義例項屬性
    // readonly name: string = '孫悟空';
    name = '孫悟空';

    // 在屬性前使用static關鍵字可以定義類屬性(靜態屬性)
    // static readonly age: number = 18;
    age = 18;


    // 定義方法
    /*
    * 如果方法以static開頭則方法就是類方法,可以直接通過類去呼叫
    * */
    sayHello(){
        console.log('Hello 大家好!');
    }

}

const per = new Person();

// console.log(per);
// console.log(per.name, per.age);

// console.log(Person.age);

// console.log(per.name);
// per.name = 'tom';
// console.log(per.name);

// per.sayHello();

// Person.sayHello();
per.sayHello();

readonly 表示一個只讀屬性,不能修改,是在例項上的

static 表示一個靜態屬性,通過類的點語法

3、建構函式和this

class Dog{
    name: string;
    age: number;

    // constructor 被稱為建構函式
    //  建構函式會在物件建立時呼叫
    constructor(name: string, age: number) {
        // 在例項方法中,this就表示當前當前的例項
        // 在建構函式中當前物件就是當前新建的那個物件
        // 可以通過this向新建的物件中新增屬性
        this.name = name;//第一個name是類的屬性,第二個name是傳進的引數的型別
        this.age = age;
    }

    bark(){
        // alert('汪汪汪!');
        // 在方法中可以通過this來表示當前呼叫方法的物件
        console.log(this.name);
    }
}

const dog = new Dog('小黑', 4);
const dog2 = new Dog('小白', 2);

// console.log(dog);
// console.log(dog2);

dog2.bark();

4、繼承

(function (){

    // 定義一個Animal類
    class Animal{
        name: string;
        age: number;

        constructor(name: string, age: number) {
            this.name = name;
            this.age = age;
        }

        sayHello(){
            console.log('動物在叫~');
        }
    }

    /*
    * Dog extends Animal
    *   - 此時,Animal被稱為父類,Dog被稱為子類
    *   - 使用繼承後,子類將會擁有父類所有的方法和屬性
    *   - 通過繼承可以將多個類中共有的程式碼寫在一個父類中,
    *       這樣只需要寫一次即可讓所有的子類都同時擁有父類中的屬性和方法
    *       如果希望在子類中新增一些父類中沒有的屬性或方法直接加就行
    *   - 如果在子類中新增了和父類相同的方法,則子類方法會覆蓋掉父類的方法,方法可以重寫!!
    *       這種子類覆蓋掉父類方法的形式,我們稱為方法重寫
    *
    * */
    // 定義一個表示狗的類
    // 使Dog類繼承Animal類
    class Dog extends Animal{

        run(){
            console.log(`${this.name}在跑~~~`);
        }

        sayHello() {
            console.log('汪汪汪汪!');
        }

    }

    // 定義一個表示貓的類
    // 使Cat類繼承Animal類
    class Cat extends Animal{
        sayHello() {
            console.log('喵喵喵喵!');
        }
    }

    const dog = new Dog('旺財', 5);
    const cat = new Cat('咪咪', 3);
    console.log(dog);
    dog.sayHello();
    dog.run();
    console.log(cat);
    cat.sayHello();


})();

5、super

子類寫了constructor建構函式必須使用super繼承父類constructor建構函式的屬性

(function () {
    class Animal {
        name: string;
        constructor(name: string) {
            this.name = name;
        }

        sayHello() {
            console.log('動物在叫~');
        }
    }

    class Dog extends Animal{
        age: number;
        constructor(name: string, age: number) {
            // 如果在子類中寫了建構函式,在子類建構函式中必須對父類的建構函式進行呼叫
            //super(name) === this.name = name,虛擬碼
            super(name); // 呼叫父類的建構函式
            //如果在子類的建構函式直接super父類建構函式
            this.age = age;
        }
        // 重寫父類方法
        sayHello() {
            // 在類的方法中 super就表示當前類的父類
            // super.sayHello();
            console.log('汪汪汪汪!');
        }
    }

    const dog = new Dog('旺財', 3);
    dog.sayHello()
})();


6、抽象類

(function () {

    /*
    *   以abstract開頭的類是抽象類,
    *       抽象類和其他類區別不大,只是不能用來建立物件
    *       抽象類就是專門用來被繼承的類
    *
    *       抽象類中可以新增抽象方法
    * */
    abstract class Animal {
        name: string;

        constructor(name: string) {
            this.name = name;
        }

        // 定義一個抽象方法
        // 抽象方法使用 abstract開頭,沒有方法體
        // 抽象方法只能定義在抽象類中,子類必須對抽象方法進行重寫
        abstract sayHello():void;
    }

    class Dog extends Animal{
        sayHello() {
            console.log('汪汪汪汪!');
        }

    }

    class Cat extends Animal{
        sayHello() {
            console.log('喵喵喵喵!');
        }

    }

    const dog = new Dog('旺財');
    dog.sayHello();

})();

抽象方法只能定義在抽象類中,子類必須對抽象方法進行重寫

7、介面

介面的作用類似於抽象類,不同點在於介面中的所有方法和屬性都是沒有實值的,換句話說介面中的所有方法都是抽象方法。介面主要負責定義一個類的結構,介面可以去限制一個物件的介面,物件只有包含介面中定義的所有屬性和方法時才能匹配介面。同時,可以讓一個類去實現介面,實現介面時類中要保護介面中的所有屬性。

  • 示例(檢查物件型別):

    • interface Person{
          name: string;
          sayHello():void;
      }
      
      function fn(per: Person){
          per.sayHello();
      }
      
      fn({name:'孫悟空', sayHello() {console.log(`Hello, 我是 ${this.name}`)}});
      
      
  • 示例(實現)

    • interface Person{
          name: string;
          sayHello():void;
      }
      
      class Student implements Person{
          constructor(public name: string) {
          }
      
          sayHello() {
              console.log('大家好,我是'+this.name);
          }
      }
      

8、屬性的封裝

  • public
    • 修飾的屬性可以再任意位置訪問修改預設值
  • private
    • 私有屬性,私有屬性只能在類內部進行訪問修改
  • protected
    • protected受包含的屬性,只能在當前類和當前類的子類中訪問
  • getter方法用來讀取屬性
  • setter方法迎來設定屬性
    • getter和setter被統一稱為屬性的儲存器,定義時在方法之前新增get和set,呼叫的時候直接通過點語法呼叫
(function (){
    // 定義一個表示人的類
    class Person{
        // TS可以在屬性前新增屬性的修飾符
        /*
        *   public 修飾的屬性可以在任意位置訪問(修改) 預設值
        *   private 私有屬性,私有屬性只能在類內部進行訪問(修改)
        *       - 通過在類中新增方法使得私有屬性可以被外部訪問
        *   protected 受包含的屬性,只能在當前類和當前類的子類中訪問(修改)
        *
        * */
        private _name: string;
        private _age: number;

        constructor(name: string, age: number) {
            this._name = name;
            this._age = age;
        }

        /*
        *   getter方法用來讀取屬性
        *   setter方法用來設定屬性
        *       - 它們被稱為屬性的存取器
        * */
        // TS中設定getter方法的方式
        get name(){
            // console.log('get name()執行了!!');
            return this._name;
        }
        set name(value){
            this._name = value;
        }
        get age(){
            return this._age;
        }
        set age(value){
            if(value >= 0){
                this._age = value
            }
        }
    }

    const per = new Person('孫悟空', 18);

    /*
    * 現在屬性是在物件中設定的,屬性可以任意的被修改,
    *   屬性可以任意被修改將會導致物件中的資料變得非常不安全
    * */
    // per.setName('豬八戒');
    // per.setAge(-33);
    per.name = '豬八戒';
    per.age = -33;
    // console.log(per);
    class A{
        //protected是保護的屬性,只能在當前類和子類中設定
        //protected只能在當前類和當前類的子類中設定
        protected num: number;
        constructor(num: number) {
            this.num = num;
        }
    }
    class B extends A{
        test(){
            console.log(this.num);
        }
    }
    const b = new B(123);
    // b.num = 33;
   /* class C{

        name: string;
        age: number

        // 可以直接將屬性定義在建構函式中
        constructor(name: string, age: number) {
            this.name = name;
             this.age = age;
        }
    }*/
    class C{
        // 直接將屬性定義在建構函式中
        constructor(public name: string, public age: number) {
        }
    }
    const c = new C('xxx', 111);
    console.log(c);
})();

9、泛型

定義一個函式或類時,有些情況下無法確定其中要使用的具體型別(返回值、屬性、引數的型別不能確定),此時泛型便能通夠發揮作用

  • function test(arg:any):any{
    	return arg
    }
    
  • 在這個例子中,test函式又一個引數型別不確定,但是能確定的是其返回值的型別和引數的型別是相同的,由於型別不確定所有引數和返回值均使用了any,但是很明顯這樣做是不合適的,首先使用any會關閉TS的型別檢查,其次這樣設定也不能體現出引數和返回值是相同的型別

  • 通過泛型來確認引數和返回值的型別相同

    • function test<T>(arg:T):T{
      	return arg
      }
      
    • 這裡的<T>就是泛型,T是我們給這個型別起的名字(不一定必須叫T),設定泛型後即可在函式中使用T來表示該型別。所以泛型其實很好理解,就表示某個型別

    • 那麼如何使用上面的函式呢?

      • 方式一(直接使用)

      • test(10)//直接使用
        
      • 使用時直接傳遞引數使用,型別會由TS自動推斷出來,但有時編譯器無法自動判斷時還需要使用下面的方式

      • 方式二(指定型別)

      • 也可以在函式後手動指定泛型

    • 可以同時指定多個泛型,泛型間使用逗號隔開

      • function test<T,K>(a:T,B:K):K {
        	return b
        }
        test<number,string>(10,"hello")
        
      • 使用泛型時,完全可以將泛型當成是一個普通的類去使用

    • 類中同樣可以使用泛型

      • class MyClass<T>{
        	prop:T
        	constructor(prop:T){
        		this.prop = prop
        	}
        }
        
    • 除此之外,也可以對泛型的範圍進行約束

      • interface MyInter{
            length: number;
        }
        
        function test<T extends MyInter>(arg: T): number{
            return arg.length;
        }
        
      • 使用T extends MyInter表示泛型T必須是MyInter的子類,不一定非要使用介面類和抽象類同樣適用。

相關文章