🐘让你再也不怕TS面向对象

🔥TypeScript面向对象

🐘让你再也不怕TS面向对象

💧一.类的基本书写和面向对象基本原则

  • 在这里默认你了解了面向对象的基本概念了,如果你不知道面向对象的基本概念,请先去了解下基本概念,或者学习下ES6,但是从我的认知而言,我觉得TS的面向对象和传统的编程语言,类似于Java,C#比较相似,同时也是JavaScript的超集,所以当你了解了基本概念之后,我建议你直接学习这个,当你把当前文章学会了之后,你再去学习ES6的面向对象的基本知识的话,是一种向下兼容,好了,说了这么多,看好大纲,搬好小板凳,让我们开始吧。

  • 类的基本使用

    • 成员变量:成员变量就是类中的属性,你可以理解为类中变量级别的内容。
    • 构造方法:在new BaseClass的时候会调用构造函数,你可以在构造函数里面初始化进行操作包括,变量赋值方法调用
    • 方法:就是我们日常写的方法,只不过区别在于声明的时候不需要再写function了。
    • 对象实例化:就是通过一个抽象的东西变为一个具体的东西,就比如建筑使用的图纸,在工人们的实际操作下变成了一栋房子。
    class BaseClass{
        //成员变量
        names:string;
        age:number;
        //构造方法(函数)
        constructor(names:string,age){
            this.names = names;
            this.age = age;
        }
        //方法
        printContent(){
            console.log(this.names,this.age);
            
        }
    }
    //对象实例化
    let cls = new BaseClass("zs",12);
    console.log(cls.age);
    console.log(cls.names);
    cls.printContent()
    ​
    
  • 面向对象编程的五个原则(了解即可)

    • 单一功能原则:一个类应该仅仅具有一种职责。
    • 开放封闭原则:一个类应该是可扩展的但不是可修改的。
    • 里氏替换原则:一个派生类类应该可以在程序的任意一处对基类进行替换。
    • 接口分离原则:一个类应该仅仅实现自己功能需要的那部分接口。
    • 依赖倒置原则:它的核心思想即是对功能的实现应该依赖于抽象层,即不同的逻辑通过实现不同的抽象类。

🌹二.访问私有成员而不是私有方法

  • 我们看到了标题是访问私有成员而不是私有方法,为什么这么说哪?因为在类里面子类可以通过get和set访问到父类里面的私有成员,而私有方法想访问是万万不可能的,那就看看如何进行私有成员的访问吧,从下面的代码我们可以看出来,对私有属性的访问是通过get和set进行访问的,get故名思义就是得到获取的意思而set顾名思义即使设置的意思,明白它们的意思你用起来就会比较的心应手了。

    class Foo{
        public static names:string 
        constructor(private a:string,public b:string,protected c:string,d:string){
            Foo.names = d
        }
        public foo(){
            console.log(this.a);
            console.log(this.b);
            console.log(this.c); 
        }
        public get aaa(){
            return this.a; //类内部需要使用this访问
        }
        public set aaa(data:string){
            this.a = data + "aaaaaa"
        }
    }
    let f = new Foo("aaa","bbb","ccc","ffffff")
    f.foo()
    f.aaa = "444"
    console.log(f.aaa);
    console.log(Foo.names)
    

📖三.访问修饰符

  • 什么是修饰符,其实上边代码我们已经看到了修饰符,其实修饰符有三个分别如下,先不要管为什么直接写在了构造方法中。

    • private:私有的,就是只能在本类中使用,限制范围在本类。
    • public:公共的,不限制范围。
    • protected:受保护的,限制使用范围在本类和其子类使用。
    class Foo{
        // private a:string;
        // public b:string;
        // protected c:string;
        // constructor(a:string,b:string,c:string){
        //     this.a = a;
        //     this.b = b;
        //     this.c = c;
        // }
        public static names:string 
        // 这里就用到了访问修饰符
        constructor(private a:string,public b:string,protected c:string,d:string){
            Foo.names = d
        }
        public foo(){
            console.log(this.a);
            console.log(this.b);
            console.log(this.c); 
        }
        public get aaa(){
            return this.a;
        }
        public set aaa(data:string){
            this.a = data + "aaaaaa"
        }
    }
    let f = new Foo("aaa","bbb","ccc","ffffff")
    f.foo()
    f.aaa = "444"
    console.log(f.aaa);
    console.log(Foo.names);
    
  • 成员赋值的简化写法

    // private a:string;
        // public b:string;
        // protected c:string;
        // constructor(a:string,b:string,c:string){
        //     this.a = a;
        //     this.b = b;
        //     this.c = c;
        // }
        public static names:string 
        // 这里就用到了访问修饰符
        constructor(private a:string,public b:string,protected c:string,d:string){
            Foo.names = d
        }
    
    • 相必大家都看到了这段代码,从我们基础类的定义中好像是注释的部分才是正常的代码,哈哈哈其实在TS里给了下方没有注释的等价写法,可以尝试着使用一下。

😊四.静态成员和静态方法

  • 静态成员和静态方法无非就是在变量或者方法名的前边加上了令人疑惑的static但是请不要迷惑,这个其实就是一个纸老虎,真的非常简单记住这几个真言:要用类名去调用事实上你可以用this但是完全没有用,因为它不在实例上。

  • 下边看几个问题的回答

    在TS类中 静态成员,静态方法调用 有哪些规则,举例子说明?

    1. 静态成员只能通过类名来调用,不能通过实例对象来调用,否则会报错。

      class MyClass {
        static staticProperty = "静态属性";
      }
      ​
      console.log(MyClass.staticProperty); // 静态属性const myInstance = new MyClass();
      console.log(myInstance.staticProperty); // 报错:myInstance.staticProperty 不是一个函数
      
    2. 静态方法也只能通过类名来调用,不能通过实例对象来调用,否则会报错。

      class MyClass {
        static staticMethod() {
          console.log("我是静态方法");
        }
      }
      ​
      MyClass.staticMethod(); // 我是静态方法const myInstance = new MyClass();
      myInstance.staticMethod(); // 报错:myInstance.staticMethod 不是一个函数
      
    3. 静态成员和静态方法可以在类的内部使用this关键字来访问,但this指向的是类本身而不是实例对象。

      class MyClass {
        static staticProperty = "静态属性";
        static staticMethod() {
          console.log(this.staticProperty); // 静态属性
        }
      }
      ​
      MyClass.staticMethod(); // 静态属性
      

    在TS中静态属性 必须进行赋值吗?

    1. 在TS中,静态属性可以不进行赋值。当静态属性未被赋值时,默认值为undefined。但是,在使用静态属性时,最好先检查它是否为undefined,避免出现意外的错误

    在TS中静态属性可以在构造器里面进行赋值吗?

    1. 可以,在TS中静态属性可以在构造器里面进行赋值。但是需要注意的是,静态属性不依赖于类的实例,因此在构造器内赋值不会影响到类的其他实例。可以使用类名来直接访问静态属性,而不必使用实例。

    在TS里面静态成员能够通过this调用吗?

    1. 能够通过类名调用静态成员,但不能通过实例对象调用静态成员。在静态方法内部可以使用类名或者this关键字来调用静态属性或者静态方法。例如:

      class MyClass {
        static myStaticProp: number = 42;
        static myStaticMethod() {
          console.log(MyClass.myStaticProp); // 可以使用类名调用静态成员
          console.log(this.myStaticProp); // 也可以使用this关键字调用静态成员
        }
      }
      ​
      MyClass.myStaticMethod(); // 输出 42 42
      const myObj = new MyClass();
      myObj.myStaticMethod(); // 报错,实例对象无法调用静态方法
      

    在TS类中 静态方法和静态成员一般来讲都不能使用this调用?

    1. 是的,因为静态方法和静态成员属于类本身,而不是类的实例。因此,在静态方法和静态成员中,this指向的是类本身而不是实例。如果需要访问实例属性或方法,应该使用非静态方法或实例属性。

⚠️五.继承

  • 如何实现子类继承父类?直接上代码,用代码驱动思考

    class Animal{
        constructor(private a:string){
    ​
        }
        public log(){
            console.log("我是父类的animal"); 
        }
        public getNames(){
            return this.a
        }
    }
    //子类进行继承
    // 如果子类继承父类的私有变量不能使用修饰符进行简化赋值,这是因为它的赋值
    // 必须在this之前进行调用super
    class Dog extends Animal{
        constructor(a:string,public b:string){
            super(a);
        }
        log(){
            console.log(" 重写了这个方法");
            
        }
        public gerNames(){
            return super.getNames()
        }
    }
    let ani = new Animal("animal bark")
    ani.log()
    let dog:Animal = new Dog("dog bark","assasasaasa")
    dog.log()
    let animal:Animal = new Animal("test animal bark")
    animal.log()
    ​
    
    1. 子类继承父类后,可以重写父类的方法和属性。

    2. 子类的构造函数必须调用父类的构造函数,以便初始化父类中的属性。

    3. TS中的访问修饰符(public、private、protected)在类的继承中也有作用,如若子类中需要访问父类中的属性或方法,需要将它们定义为protected或public。

    4. 子类可以调用super关键字来访问父类中的方法和属性,并可以传递参数给父类的构造函数

    5. 如果我们遇到了想要使用父类私有变量的情况怎么办?

      在 TypeScript 中,子类是可以继承父类的私有成员的。但是,由于父类的私有成员只能在父类中使用,子类无法直接访问父类的私有成员。因此,子类也不能使用 super 关键字来访问和继承父类的私有成员。

      例如,假设有一个父类 Person,其中包含一个私有成员 name,子类 Student 继承自父类 Person。我们希望子类 Student 也可以访问父类的 name 成员,可以先在父类中定义一个公共的方法来获取私有成员,然后子类继承并使用该方法。

      示例代码如下所示:

      class Person {
        private name: string;
      ​
        constructor(name: string) {
          this.name = name;
        }
      ​
        getName(): string {
          return this.name;
        }
      }
      ​
      class Student extends Person {
        constructor(name: string) {
          super(name);
        }
      ​
        getStudentName(): string {
          return super.getName();
        }
      }
      ​
      const student = new Student('Tom');
      console.log(student.getStudentName()); // 输出:Tom
      

      在上面的示例中,父类 Person 中定义了一个私有成员 name 和一个公共方法 getName,用于获取私有成员 name 的值。子类 Student 继承自父类 Person,并在自己的公共方法 getStudentName 中通过 super.getName() 调用父类的 getName 方法,从而间接地访问并继承了父类的私有成员 name

      值得注意的是,虽然子类无法直接访问和继承父类的私有成员,但是在父类中使用 protected 关键字来定义成员,则子类就可以使用 super 关键字访问和继承了。因为 protected 成员是可以被继承的,而且只有子类和父类内部可以使用。

      注意:如果派生类中写了 constructor() 方法,必须在 this 之前调用 super 方法,它会调用基类的构造函数。

      class Person {
        protected name: string;
      ​
        constructor(name: string) {
          this.name = name;
        }
      ​
        eat() {
          console.log('eat...');
        }
      }
      
      class Student extends Person {
        constructor(name: string) { super(name); }
      ​
        eat() {
          console.log('今天作业有点多,再加一块肉!');
          super.eat();
        }
      ​
        study() {
          this.eat();
          console.log(`${this.name} 开始学习...`);
        }
      }
      ​
      const s1 = new Student('张三');
      s1.study();
      ​
      // 今天作业有点多,再加一块肉!
      // eat...
      // 张三 开始学习...
      
  • 对比直接覆盖父类方法和使用override关键字的区别,废话少说直接上测试代码

    • 直接对父类内容进行重写

      class Animal{
          constructor(private a:string){
      ​
          }
          public log(){
              console.log("我是父类的animal"); 
          }
          public getNames(){
              return this.a
          }
      }
      //子类进行继承
      // 如果子类继承父类的私有变量不能使用修饰符进行简化赋值,这是因为它的赋值
      // 必须在this之前进行调用super
      class Dog extends Animal{
          constructor(a:string,public b:string){
              super(a);
          }
          log(){
              console.log(" 重写了这个方法");
              
          }
          public gerNames(){
              return super.getNames()
          }
      }
      let ani = new Animal("animal bark")
      ani.log()
      let dog:Animal = new Dog("dog bark","assasasaasa")
      dog.log()
      let animal:Animal = new Animal("test animal bark")
      animal.log()
      ​
      ​
      
    • 使用override关键字进行重写

      class Animals{
          constructor(private a:string){
      ​
          }
          public log(){
              console.log("我是父类的animal"); 
          }
          public getNames(){
              return this.a
          }
      }
      //子类进行继承
      // 如果子类继承父类的私有变量不能使用修饰符进行简化赋值,这是因为它的赋值
      // 必须在this之前进行调用super
      class Dogs extends Animals{
          constructor(a:string,public b:string){
              super(a);
          }
          override log(){ //主要区别在于加入基类中没有这个方法的时候overide会给出提示,而直接覆盖的不会有,很可能没有覆盖到或者仅仅相当于是重写。
              console.log(" 重写了这个方法");
              
          }
          public gerNames(){
              return super.getNames()
          }
      }
      let anis = new Animals("animal bark")
      anis.log()
      let dogs:Animals = new Dogs("dog bark","assasasaasa")
      dogs.log()
      let animals:Animals = new Animals("test animal bark") //子类可以使用父类进行类型标注,就可以理解为object 和object array function
      animals.log()
      ​
      ​
      
    • 通过对比以上代码的运行我们可以发现完全看不出有任何区别,那么区别在什么地方哪?

      主要的区别在于当我们给子类方法增加了overide字段系统就会认为我们一定是在重写,当父类没有这个方法就会报错,但是如果我们没有使用这个字段的话假如父类中没有这个方法,其实就相当于在子类中进行了新增自己的方法。

🥱六.抽象类

  • 抽象类。抽象类是对类结构与方法的抽象,简单来说,一个抽象类描述了一个类中应当有哪些成员(属性、方法等)一个抽象方法描述了这一方法在实际实现中的结构。我们知道类的方法和函数非常相似,包括结构,因此抽象方法其实描述的就是这个方法的入参类型返回值类型。注意:抽象类无法抽象静态抽象成员,学习抽象类的时候可以把它和接口放在一块理解。

    abstract class AbsFoo {
      abstract absProp: string;
      abstract get absGetter(): string;
      abstract absMethod(name: string): string
    }
    
  • 实现上边抽象类

    class Foo implements AbsFoo {
      absProp: string = "linbudu"
    ​
      get absGetter() {
        return "linbudu"
      }
    ​
      absMethod(name: string) {
        return name
      }
    }
    
  • 使用接口描述类

    interface FooStruct {
      absProp: string;
      get absGetter(): string;
      absMethod(input: string): string
    }
    ​
    class Foo implements FooStruct {
      absProp: string = "linbudu"
    ​
      get absGetter() {
        return "linbudu"
      }
    ​
      absMethod(name: string) {
        return name
      }
    }
    

🥱七.接口多继承-接口继承类

接口多继承实现

上面讲了在 TS 中类之间只能实现单继承,但是在接口里是可以实现单继承和多继承的。

interface Person1 {
  nickname: string;
}
interface Person2 {
 age: number;
}
interface Person3 extends Person1, Person2 {
 sex: string;
}
​
function study(obj: Person3) {
 console.log(obj.nickname, obj.age, obj.sex);
}
​
study({ nickname: 'may', age: 20, sex: 'man' });

接口继承类(很少用)

接口可以通过 extends 关键词继承一个类,如果类成员包含实现,则不会被继承其实现。

class Person1 {
 nickname: string;
 
 test(): string {
  return 'Hello';
 }
}
interface Person2 extends Person1 {
 age: number;
}
​
class Study implements Person2 {
 nickname: string;
 age: number;
 constructor(nickname: string, age: number) {
  this.nickname = nickname;
  this.age=age;
 }
​
 test(): string {
  console.log(this.nickname, this.age)
  return 'Hi';
 }
}
​
const lisi = new Study('李四', 20);
console.log(lisi.test());

🀄️八.多态

  • 多态是面向对象中的一个重要概念,可以通过继承和接口实现。多态的意思是同一种行为对于不同的对象产生不同的结果,即同一种方法可以具有多个不同的实现方式。

    举个例子,我们可以定义一个动物类 Animal,里面有一个 eat() 方法,可以吃食物。然后我们再定义两个子类 Cat 和 Dog,它们都继承自 Animal 父类,并重写了 eat() 方法。Cat 类的 eat() 方法是吃鱼,Dog 类的 eat() 方法是吃肉。

    当我们在代码中创建一个 Animal 类型的对象 a,然后调用它的 eat() 方法时,它会根据具体的对象类型调用对应的实现方式。比如如果 a 是一个 Cat 类型的对象,那么它的 eat() 方法会调用 Cat 类的实现,也就是吃鱼;如果 a 是一个 Dog 类型的对象,那么它的 eat() 方法会调用 Dog 类的实现,也就是吃肉。通过多态,我们可以更加灵活地使用对象,降低代码的耦合性,提高代码的可维护性和可拓展性。

    class Animal {
        eat() {
            console.log("Animal is eating food");
        }
    }
    ​
    class Cat extends Animal {
        eat() {
            console.log("Cat is eating fish");
        }
    }
    ​
    class Dog extends Animal {
        eat() {
            console.log("Dog is eating meat");
        }
    }
    ​
    let animal: Animal;
    animal = new Cat();
    animal.eat(); // 输出: "Cat is eating fish"
    ​
    animal = new Dog();
    animal.eat(); // 输出: "Dog is eating meat"
    

🍑九.补充

  • 在TS中子类实例化对象能够使用父类进行类型标注,但是父类不能使用子类进行类型标注是为什么?

    这是因为子类继承父类的所有属性和方法,所以一个子类实例化对象也是一个父类实例化对象,它们拥有相同的结构和属性。父类没有办法使用子类进行类型标注是因为子类可能会添加自己特有的属性和方法,而父类是无法识别这些特有的属性和方法的,因此在父类中使用子类进行类型标注是没有意义的。

  • 但是这个不要和接口的继承混淆哦,一个类实现接口需要实现接口的全部,接口之间的继承不具有这种情况。

原文链接:https://juejin.cn/post/7223673824528252984 作者:举个栗子儿

(0)
上一篇 2023年4月20日 上午11:02
下一篇 2023年4月20日 上午11:12

相关推荐

发表回复

登录后才能评论