TypeScript简明教程5---面向对象、类、构造函数、继承、抽象类、接口、属性的封装、泛型

简介:本篇博客对于TS的开发思想面向对象编程和基本语法类、构造函数、继承、抽象类、接口、类中属性的封装和泛型进行简单的介绍,并列举使用的实例代码以供参考。借此博客也对于近一个星期所学习关于TypeScript进行一个简单的总结归纳。

一、什么是面向对象编程?

JavaScript是一门面向过程的程序语言,首先我们来说一说面向过程的编程语言,这是一种编程思维,是以过程为中心的编程。主要的特点是先分析出解决问题需要的步骤,然后把每一步定义成一个一个方法,通过逐个调用的方式来完成整个功能。这样的好处是十分直接,要解决什么问题就写什么代码,但是复用性会比较差。就好像你准备去出版一本书,从最初的纸张制作,购买各类参考资料文献,每个章节的书写,汇总整理校对,联系出版商协商出版等事情都需要你一个人去完成,在编程开发中,就是一个方法解决一个问题,通过调用不同的方法解决不同的问题。这就意味着在进行复杂应用程序开发的时候,会存在着成千上万个函数方法和全局变量,要理清楚每个函数会调用哪些全局变量并不是一件十分容易的事情,在后期项目维护的时候也很麻烦。

而作为JavaScript的超集TypeScript则采用了面向对象的编程思想,对象由类创造,一切通过对象来操作。面向对象编程的思想就是应用中的不同模块都抽象成一个类,在类中声明其所具有的属性和功能方法。拿上面列举的出版一本书来说,每个模块都单独有一个类去负责,在其他的类中去调用属性和方法即可实现问题的解决。当后期我们需要更换不同的纸张质量,开版大小,文献资料修改的时候,我们在通过对应类去创建对象的时候只需要传入我们需要的参数即可。在项目后期的维护容易度比面向过程的语言高很多。存在的不足之处就是前期需要写的代码量比较多。

二、TypeScript中的类

     如下面代码中声明的一个动物类,对属性进行类型声明,并通过new操作符创建一个新的对象和通过对象实现方法的调用。

class Animal{
    //声明两个属性,记得带上类型声明
    weight:number;
    name:string;
    //构造函数,用于把传入的参数创建新的对象,对于传入的参数也要做类型的声明
    constructor(weight:number,name:string){
         this.name=name;
         this.weight=weight;    
     }
    //类中创建一个方法
     run(){
       console.log(`${this.name} is running!`);
     }
}

//通过类新建一个对象,其实类的目的就是方便我们批量化的创建对象。
//创建对象的同时需要传入类型声明的参数,否则会报错。
let pig=new Animal(300,"小猪");

//通过创建的对象调用类中的run方法
pig.run();   //控制台会输出  小猪is running!

//通过上述的代码,我们创建了一个简单的动物类,并实现了新的对象的创建和方法的调用。

2.1 静态成员属性和方法、类的继承与方法重写

         在类中的属性和方法,当我们使用static进行修饰时,则称之为静态属性或方法,此时只能通过类去调用,类创建的实例对象无法调用此属性或方法。

//创建一个Person类
class Person{
    name:string;
    age:number;
    //用readonly修饰的属性只读无法修改。
    readonly home:string;
    //静态的属性和方法仅可以通过类原型来调用,而其他的均为实例属性和方法。
    static address:string="成都";
    constructor(name:string,age:number,home:string){
        this.name=name;
        this.age=age;
        this.home=home;
    }
   
    running(){
        console.log(`${this.name} is running`);
    }
}

//创建一个对象,此对象中仅可传入name,age,home,address属性无法获取调用。
let p1=new Person('小名',22,"上海");
p1.running();  //小名 is running;
//通过类原型来调用静态属性
console.log(Person.address);   //成都

//类的继承
//声明一个类继承自Person,会将Person类中所有的属性和方法继承到自身,无需再进行重写
//对于需要改写的属性方法,可以再自身进行同名重写。
//继承存在的目的就是为了对象中属性和方法的尽可能复用。
class Man extends Person{
    gender:string;
    constructor(name:string,age:number,home:string,gender:string){
        //需要特别注意的是,在子类中写构造器函数必须使用super()对于父类进行继承,否则会因为方法重写覆盖掉父类构造器函数,进而导致无法使用父类中的属性和方法。
        //导致子类无法继承父类中的属性方法。
        super(name,age,home)
        this.gender=gender;
    }
    running(): void {
        //子类中的super就相当于当前的父类,可以使用其调用父类的属性和方法。
        super.running()
        //console.log(`${this.name} 在晨跑`);
    }
    eating(){
        console.log(`${this.name} is eating dinner`);
    }
}

//被继承的类称为父类
let p2=new Man("小黄",23,"beijing",'男');
p2.eating();   //小黄 is eating dinner

三、TypeScript中的抽象类和接口

       类是对象的模型,对象可以通过类来实现批量的创建,并直接可以调用类中的非静态属性和方法。正如上一小节中所述,类与类之间存在着继承关系。在有些时候,我们不希望部分类能直接创建出对象,而是仅仅用于作为父类被继承,这就是抽象类的作用了,在类的声明前添加abstract即可创建抽象类,其不可被用于创建对象,只能用来被继承。抽象类中可以使用abstract声明抽象方法,抽象方法没有方法体,其作用是提醒子类中父类存在此方法,并需要在子类中进行重写补充具体的方法体。

//作为比较高层次通用性较强的类,我们一般不希望通过其创建对象实例,因为不够语义化很难理解所创建的对象
//的作用,此时可以使用关键字abstract创建抽象类,抽象类不可以通过new创建对象实例,仅能用于被继承。
abstract class Phone{
    series:number;
    value:number;
    constructor(series:number,value:number){
           this.series=series;
           this.value=value;
    }
    //抽象类中可以写抽象方法,抽象方法并没有具体的方法体,方法体需要在子类中进行重写,不进行重写则会发生报错
    abstract playGame():void;
}

//创建Iphone用于继承上面的抽象类
class Iphone extends Phone{
    madeAddress:string;
    constructor(series:number,value:number,madeAddress:string){
        super(series,value);
        this.madeAddress=madeAddress;
    }
    //对抽象类中的抽象方法进行重写,返回值的类型声明为void
    playGame(): void {
        console.log(`${this.series} sale highly at ${this.value}`)
    }
}
let p1=new Iphone(12,5499,"china");
p1.playGame();  //12 sale highly at 5499
console.log(p1); //此处可以看到一个完整的p1对象,并具有父类中的属性和方法。

3.1 接口

      什么是所谓的接口呢?在JavaScript中并没有这个概念,接口interface通俗的来说就是对类中的属性和方法进行统一的类型声明,哪个类调用此接口,在一般情况下必须具有接口中相应类型声明的属性和方法,接口中的属性或方法名后添加?则表示此属性或方法是可选项,调用接口的类中可以根据具体的需要进行声明,一个类可以实现多个接口的调用,不同的接口用逗号隔开。需要注意的是,接口中声明的方法默认是抽象方法,也就是不具备方法体,需要我们在调用接口的时候进行方法的重写。

//可以使用type关键字对于对象的结构进行类型声明,所创建的对象需要满足此类型条件。
type myObj={
    name:string,
    age:number
}
//声明一个满足上面type的对象
const obj:myObj={
    name:"你好",
    age:12
}

//什么是所谓的接口?就是对类结构中的属性和方法进行限定,应用此接口的类必须满足接口的条件
//也就是称之为实现一个接口
//接口中的属性没有值,方法并不具备方法体,具体实现依靠应用的类。
interface Animals{
    name?:string|number,  //问号的作用是表示此属性是可选项,类中不进行声明也不会报错。或运算符表示两个类型声明满足一个即可。
    run():void,
}

class Cat implements Animals{
    //子类中必须对接口中无具体值的属性和无方法体的方法进行重写,否则会报错。
    //接口并不会出现在编译完成后的js文件中。
    name:string;
    constructor(name:string){
        this.name=name;
    }
    run(): void {
        console.log(`${this.name} is running`)
    }
}
const cat=new Cat("小黑");
cat.run();

四、TypeScript中的属性的封装和泛型

           由类创建的对象可以直接调用类中的属性,并可以通过自身调用重新赋值进行修改,这是存在一定风险的,我们一般不希望外界可以直接对类中的属性进行随意的修改。类中属性的修改权掌握在自身对于程序的可靠性很重要,因此我们需要对类中的属性使用关键字private,protected,public(默认值)进行封装。存在一个默认的规则,就是类中的属性一般以下划线开头。属性封装后,要对类中的属性进行修改时,我们可以设置一些判断条件以阻止属性值的随意变化。

//关于属性的封装,在没有对属性进行封装的条件下,属性值是由创建对象实例的时候传入所决定的
//但是这样会给类中具体的属性值带来很大的不确定性,因此需要对属性进行权限的封装
interface Boat{
    _square:number,
    //接口中的属性和方法结构声明默认具有公开开放特性,如果类中属性为protect或者private
    //则不应在接口中对此属性进行类型声明限定。直接在类中进行类型的声明确定。
    //_categories:string,
}
class Sim implements Boat{
    //属性的修饰符用于决定属性可以被访问的权限范围,默认值为public,也就是可以通过任意的实例对象或子类进行查看和修改。
    //private仅在类内可以查看,类外需要查看可以调用类中提供的方法,这就为类控制属性提供了主动权。
   public _square: number;
   private _categories:string;
    constructor(square:number,categories:string){
        this._square=square;
        this._categories=categories;
    }
    //通过自定义的方法对私有属性进行访问。
    getCategories(){
        return this._categories;
    }
    //通过自定义的方法对私有属性进行修改
    setCategories(value:string){
        this._categories=value;
    }

    //上述每次读取或修改私有属性都需要通过调用对象实例方法进行,那么有没有更加简便的操作方法呢
    //实际上ts已经帮我们封装好了相应的访问器属性,可以直接使用.属性进行读取或者修改
    get categories(){
        return this._categories;
    }
    set categories(value:string){
        //使用set方法进行部分条件的限定
        if(value==="cd4")
        this._categories=value;
        else return ;
    }
}

let si=new Sim(120,"cd3");
//console.log(si);

//直接对si的属性值进行修改,可以看到控制台中的si对象发生了变化
si._square=199;
//通过方法的调用实现对私有属性的修改
console.log(si.categories);
si.categories="cd5";
//si._categories="cd4";
console.log(si);

         TypeScript需要使用类型声明来保证其静态属性,而有时候我们在进行属性和方法的声明的时候,对应的类型声明暂时不确定或者存在的可能性较多,需要根据具体的使用场景进行类型声明。此时我们就可以使用泛型,将类型声明以参数的形式赋值给对应的属性或方法,也可以称之为类型声明的参数化。

           备注:泛型中的设置参数变量可以继承接口。

//泛型:意思就是说函数或者类的类型声明暂时不太确定的时候,使用一个变量进行代替
//等到具体使用的时候再具体的传入,泛型同样不会出现在编译好的js文件中。
function sum(a:T,b:K):T{
    console.log(b);
    return a;
}
//调用函数进行具体的类型声明
sum(12,23); //不进行具体的传参T,而是自动检测,一般比较容易出错。
sum('12','23') //进行类型声明传入,泛型可以声明多个,使用时一一传入。

interface Man{
    name:string
}

//T extends Man表示必须实现接口中的类型结构
function People(address:T):string{
   return address.name;
}

class Person{
    name:T;
    constructor(name:T){
        this.name=name;
    }
}

let one=new Person('nihao') //隐式自动检测推断
let two=new Person('heelo')  //显示的主动声明传入,不易出错

五、总结归纳

         本篇博客简单的介绍了TypeScript中所采用的面向对象的编程思想,以及简单的类,构造函数、继承、抽象类以及抽象方法,接口,属性的封装和泛型进行了简单的语法介绍和代码示例展示。TypeScript中的接口,泛型,属性的封装等在编译完成后的js文件中并不会出现,它们的存在是为了我们更好的使用面向对象的编程思想进行程序开发。初学TypeScript的时候难免有些不太习惯,要尽量多写代码去尝试,重要的是要理解面向对象的编程开发思想。本篇博客只是对TS中最为基础的概念进行了简单的介绍,详细的内容还是要参考官方的文档和一些好的内容文章。下面是官方文档和部分好的内容文章的链接,供需要者采用。

TypeScript官方文档链接icon-default.png?t=M4ADhttps://www.tslang.cn/samples/index.html    万字长文详解TypeScripticon-default.png?t=M4ADhttps://www.jianshu.com/p/c20ee6299280

去尝试感觉难的事情才会让自己有所成长。

你可能感兴趣的:(TypeScript,typescript,javascript,前端)