TypeScript学习笔记(二)

面向对象简介

1、面向对象是程序中一个非常重要的思想,简而言之就是程序之中所有的操作都需要通过对象来完成。

2、举例来说:
(1)操作浏览器要使用window对象
(2)操作网页要使用document对象
(3)操作控制台要使用console对象

3、一切操作都要通过对象,也就是所谓的面向对象,那么对象到底是什么呢?

这就要先说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的抽象等等。

程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物。一个事物到了程序中就变成了一个对象。

4、在程序中所有的对象都被分成了两个部分数据和功能

以人为例,人的姓名、性别、年龄、身高、体重等属于数据,人可以说话、走路、吃饭、睡觉这些属于人的功能。

数据在对象中被成为属性,而功能就被称为方法。所以简而言之,在程序中一切皆是对象。

类(class)

要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person类来创建人的对象,通过Dog类创建狗的对象,通过Car类来创建汽车的对象,不同的类可以用来创建不同的对象。

1、类的简介

	// 使用class关键字来定义一个类
	// 对象中主要包含两部分:属性 和 方法
	class Person{
	     /* 
	     直接定义的是实例属性,需要通过对象的实例去访问:
	        const per = new Person()
	        per.name 
	
	     使用static开头的属性是静态属性(类属性),可以直接通过类访问
	        Person.age
	        
	     readonly 开头的属性表示一个只读的属性,无法修改
	     */
	
	    // 定义实例属性(需要通过new来实现访问)
	    readonly name: string = '孙悟空';
	    // 在属性前使用static关键字可以定义类属性(静态属性)。不创建对象就可以直接用的属性
	    static readonly age: number = 18;
	
	    // 定义方法
	    // 如果方法以static开头则方法是类方法,可以直接通过类调用
	    sayHello(){
	        console.log('hi');
	    }
	}
	
	const per = new Person()
	// console.log(per);
	
	// console.log(Person.age);
	
	per.sayHello()
	// Person.sayHello()

2、构造函数与this

上述方式,只能创建多个相同的对象。使用构造函数可以传入参数:

class Dog{
    name : string;
    age : number;

    // 构造函数constructor,会在对象创建时调用
    constructor(name:string, age: number){
        // 在实例方法中,this表示当前的实例
        // 在构造函数中当前对象就是当前新建的那个对象
        // 可以通过this向新建的对象中添加属性
        // console.log(this);
        this.name = name;
        this.age = age
    }
    bark(){
        alert('汪汪汪')
        // 在方法中可以通过this来表示当前调用方法的对象
        console.log(this);  //谁调用this就是谁
    }
}

const dog1 = new Dog('小黑',4)
const dog2 = new Dog('小白',3)

继承

1、extends

通过继承可以将其他类中的属性和方法引入到当前类中。使用 extends 关键字

	// 定义一个Animal类
    class Animal{
        name : string;
        age : number;
        constructor(name:string, age: number){
            this.name = name;
            this.age = age
        }
        bark(){
            console.log('动物叫');
        }
    }    
    // 定义一个Dog类,并使其继承Animal类
    class Dog extends Animal{
        run(){
            console.log(`${this.name}在跑`);
        }
    }
    const dog = new Dog('旺财', 3)
    console.log(dog);
    dog.bark()
    dog.run()

    // 定义一个Cat类,并使其继承Animal类
    class Cat extends Animal{  
    }
    const cat = new Cat('咪咪', 5)
    console.log(cat);
    cat.bark()

备注:

 Animal称为父类,Dog 和 Cat 称为子类
 使用继承后子类将自动拥有父类的所有方法和属性。
 通过继承可以将多个类共有的代码写在父类中,这样只需要写一次就可让所有的子类都同时拥有父类中的属性和方法
 可以在子类中直接添加父类中没有的方法.
 方法的重写:如果在子类中添加了与父类相同的方法,则子类方法会覆盖掉父类方法

2、super

在子类中可以使用super来完成对父类的引用

// 定义一个Animal类
    class Animal{
        name : string;

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

        bark(){
            console.log('动物叫');
        }
    }

    class Dog extends Animal{
        age: number

        constructor(name: string, age: number){
            // 如果子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用
            super(name);
            this.age = age
        }

        bark(){
            // super表示 当前类的父类
            super.bark()      // 动物叫
        }
    }

    const dog = new Dog('旺财', 3)
    console.log(dog);
    dog.bark()

如果子类中写了构造函数,在子类构造函数中必须对父类的构造函数进行调用

3、抽象类

  • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
  • 使用abstract开头的方法叫做抽象方法,抽象方法没有方法体只能定义在抽象类中,继承抽象类时抽象方法必须要重写
abstract class Animal{
    abstract run(): void;
    bark(){
        console.log('动物在叫~');
    }
}

class Dog extends Animals{
    run(){
        console.log('狗在跑~');
    }
}

接口

  • 接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的,换句话说接口中的所有方法都是抽象方法。

  • 接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。

  • 接口用来定义一个类的结构,用来定义类中应该包含哪些属性和方法。

(1)接口可以当做类型声明使用

    interface myInterface{
        name:string;
        age:number
    }

    interface myInterface{
        gender: string
    }

    const obj: myInterface = {
        name:'sss',
        age: 12,
        gender:'女'
    }

注:接口可以重复声明,最终结果是将内部的代码合并。

(2)接口可以在定义类时限制类的结构

接口中的所有属性都不能有实际的值
接口只定义对象的结构而不考虑实际值,在接口中所有的方法都是抽象方法

	interface myInter{
	        name:string;
	        sayHello():void;
	    }
    // 定义类时,可以使类去实现一个接口(实现一个接口就是使类满足接口的要求)
    class myClass implements myInter{
        name: string;
        constructor(name:string){
            this.name = name;
        }
        sayHello(): void {
            console.log('hi');
        }
    }

属性的封装

1、封装:

  • 对象实质上就是属性和方法的容器,它的主要作用就是存储属性和方法,这就是所谓的封装

  • 默认情况下,对象的属性是可以任意的修改的,为了确保数据的安全性,在TS中可以对属性的权限进行设置

  • 只读属性(readonly):

    • 如果在声明属性时添加一个readonly,则属性便成了只读属性无法修改

2、TS中属性具有三种修饰符:

  • public(默认值),可以在类、子类和对象中修改。修饰的属性可以在任意位置访问(修改)
  • protected ,可以在类、子类中修改。受保护的属性,只能在当前类和子类中访问(修改)
  • private ,可以在类中修改。私有属性只能在类内部进行访问(修改)
    可以通过在类中添加方法使得私有属性可以被外部访问(getter方法)
	class A{
	        // num: string;  默认public,B中可以访问
	        // private num: string;   B中无法访问,会报错
	        protected num: string;   //B是子类,可以访问
	        constructor(num:string){
	            this.num = num
	        }
	    }

    class B extends A {
        test(){
            console.log(this.num);
        }
    }

3、属性存取器

  • 对于一些不希望被任意修改的属性,可以将其设置为private

  • 直接将其设置为private将导致无法再通过对象修改其中的属性

  • 我们可以在类中定义一组读取、设置属性的方法,这种对属性读取或设置的属性被称为属性的存取器

  • 读取属性的方法叫做setter方法,设置属性的方法叫做getter方法

	class Person{
	    private _name: string;
	    constructor(name: string){
	        this._name = name;
	    }
	    get name(){                 
	        return this._name; 
	    }
	    set name(name: string){
	        this._name = name;
	    }
	}
	const p1 = new Person('孙悟空');
	console.log(p1.name); // 通过getter读取name(私有)属性
	p1.name = '猪八戒'; // 通过setter修改name属性

4、补充
上面的写法,在一个类中,要先定义属性,然后再写构造函数。

可以直接将属性定义在构造函数中,简化写法:

	class C{
        // 可以直接将属性定义在构造函数中
        constructor(public name:string, public age: number){
        }
    }
    const c = new C('ni', 89)
    console.log(c);

泛型

1、定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

举个例子:
(1)

	function fn(a: any): any{
	    return a
	}

上例中,test函数有一个参数类型不确定,但是能确定的时其返回值的类型和参数的类型是相同的,由于类型不确定所以参数和返回值均使用了any,但是很明显这样做是不合适的,首先使用any会关闭TS的类型检查,其次这样设置也不能体现出参数和返回值是相同的类型。

(2)使用泛型

	function fn<T>(a: T): T{
		return a;
	}

这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。

2、如何使用上述泛型函数

(1)直接调用具有泛型的函数: fn(10)

  • 不指定泛型,TS可以自动对类型进行推断
  • 但有时编译器无法自动推断时还需要使用下面的方式:

(2)指定泛型: fn<string>('hello')

3、可以同时指定多个泛型,泛型间使用逗号隔开:

function test<T, K>(a: T, b: K): K{
    return b;
}

test<number, string>(10, "hello");

4、类中同样可以使用泛型:

class myClass<T>{
    name:T;
    constructor(name:T){
        this.name = name
    }
}
const mc = new myClass<string>('haha')

5、也可以对泛型的范围进行约束

interface Inter{
    length:number;
}

function fn3<T extends Inter>(a:T): number{
    return a.length
}
fn3('123')

T extends Inter 表示泛型T必须是 Inter 实现类(子类)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值