TypeScript+Vue3.0笔记二

TypeScript+Vue3.0笔记二,第1张

TS第一篇文章请看:TypeScript+Vue3.0笔记_浅夏旧梦的博客-CSDN博客

二四、类01 类的概念

虽然JavaScript中有类的概念,但是可能大多数JavaScript程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。

类(Class):定义一件事物的抽象特点,包含它的属性和方法

对象(Object):类的实例,通过 new生成

面向对象编程(Object Oriented Programming,简称 OOP)三大特性:继承、封装、多态

继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特征。

封装(Encapsulation):将对数据的 *** 作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据。

多态(Polymorphism):由继承而产生相关的不同的类,对同一个方法可以有不同的响应。比如Cat和Fish都继承自Animal,但是分别实现了自己的eat方法。此时针对某一个实例,我们无须了解他是Cat还是Dog,就可以直接调用eat方法,程序会自动判断出来应该如何执行eat方法。

存取器(Getter & Setter):用于改变属性的读取和赋值行为

修饰器(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如public 表示共有的属性或方法。

抽象类(Abstract Class):抽象类是提供给其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现。

接口(Interface):不同类之间共有的属性或方法,可以抽象成一个接口,接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口。

构造函数(Constructor):构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中

使用class关键字定义类,使用 constructor关键字定义构造函数。

通过new生成新的实例的时候,会自动调用构造函数:

class Animal{
	public _name;
	constructor(name:string){
		this._name = name;
	}
	sayHello(){
		return `My name is ${this._name}`;
	}
}

let tom = new Animal('Tom');
console.log(tom.sayHello); //My name is tom
二五、类02

类的存取器 :getset

使用getter 和 setter 可以获取和改变类的属性:

class Animal{
	// private name:string;
	constructor(name:string){
		this.name = name;
	}
	get name(){
		return 'Jack';
	}
	set name(value){
		console.log('This name:'+value);
	}
}

let a = new Animal('Kitty');//setter Kitty
a.name = 'Tom';//setter Tom
console.log(a.name);//getter Jack
二六、类03

类的静态方法

使用static修饰符修饰的方法成为静态方法,他们不需要实例化,而直接通过类来调用:

class Animal{
	public _name;
	constructor(name:string){
		this._name = name;
	}
	sayHi(){//这是实例方法
		return `My name is ${ this._name }`;
	}
	static sayHello(){//这是类方法
		return "I'm Animal class";
	}
}

let a = new Animal('Jack');
console.log(a.sayHi());//My name is Jack
console.log(Animal.sayHello());//I'm Animal class
二七、类04

类的三种访问修饰符:public、private、protected

访问权限大小由大到小:

public

全局的、公共的,当前所涉及到的地方都可以使用

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

let a  = new Animal('Jack');
console.log(a._name);// Jack
a._name = 'Tom';
console.log(a._name);// Tom

protected

受保护的,允许子类访问,不允许公共访问:

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

class Cat extends Animal{
    public constructor(name:string){
        super(name);
        console.log(this.name);
    }
} 

private

私有的,只能在类的内部使用,子类也无法访问,无法在实例后通过类的实例属性访问:

class Animal{
	private _name;
    public constructor(name:string){
        this._name = name;
    }
}

let a  = new Animal('Jack');
console.log(a._name);// 报错:属性“_name”为私有属性,只能在类“Animal”中访问。ts(2341)
a._name = 'Tom'; //报错:属性“_name”为私有属性,只能在类“Animal”中访问。ts(2341)
console.log(a._name);// 报错:属性“_name”为私有属性,只能在类“Animal”中访问。ts(2341)

默认是public,但是 TSLint 可能会要求必须用限定符来表明这个属性或方法是什么类型。

二八、类05

参数属性和只读属性关键字

修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更加简洁:

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

只读属性

class Animal{
    readonly name:string;
    public constructor(myname:string){
        this.name = myname;
    }
}

let a  = new Animal('Tom');
console.log(a.name);//Tom
a.name = 'Jack';//报错:无法分配到 "name" ,因为它是只读属性。ts(2540)
二九、类06

抽象类

abstract关键字用来定义抽象类和其中的抽象方法。

什么是抽象类?

首先,抽象类是不允许被实例化的:

abstract class Animal{
	//public name:string;//这报错:属性“name”没有初始化表达式,且未在构造函数中明确赋值。
    public name:any;
	public constrcutor(name:string){
		this.name = name;
	}
	public abstract sayHi():void;
}

let a = new Animal('Tom');
//报错:无法创建抽象类的实例。ts(2511)

上面例子中,我们定义了一个抽象类类Animal,并且定义了一个抽象方法 sayHi,在实例化抽象类是报错了。

其次,抽象类中的抽象方法必须被子类实现:

class Cat extends Animal{
	/**
	 * eat
	 */
	public eat() {
		console.log('Im eating');
	}
}
//报错:非抽象类“Cat”不会实现继承自“Animal”类的抽象成员“sayHi”。ts(2515)

正确的抽象类例子:

abstract class Animal{
	public name: any;
	public constrcutor(name:string): void{
		this.name = name;
	}
	public abstract sayHi():void;
}

class Cat extends Animal{
	public sayHi(): void {
		console.log(`This is Cat ${ this.name}`);
	}
}
let a = new Cat('Tom');

上面例子为原视频的例子,却报错:应有 0 个参数,但获得 1 个。ts(2554)

找了一些资料还没找到原因,后续再来更

三十、类与接口 类继承接口

实现(implements)是面向对象的一个重要概念。一般来说,一个类只能继承自另一个类,有时候不用类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用implements关键字来实现,这个特性大大提高了面向对象的灵活性。

举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法,这时候如果有另一个类:车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门,和车都去实现它:

interface Alert{
	alert():void;
}
class Door{
}

class SecurityDoor extends Door implements Alert{
	alert(){
		console.log('SecurityDoor alert');
	}
}
class car implements Alert{
    alert(){
        console.log('Car alert');
    }
}    

一个类可以实现多个接口:

interface Alert{
	alert():void;
}
interface Light{
    lightOn():void;
    lightOff():void;
}

class Car implements Alert, Light{
    alert(){
        console.log('Car alert');
    }
    lightOff(){
         console.log('Light Off');
    }
    lightOn(){
         console.log('Light On');
    }
}

上述例子中,Car 实现了 AlertLight接口,既能报警,也能开关灯。

接口继承接口

接口和接口之间可以是继承关系:

interface Alert{
	alert():void;
}
interface LightableAlert extends Alert{
    lightOn():void;
    lightOff():void;
}

这很好理解,LightableAlert 继承了 Alert ,除了拥有alert方法之外,还可以拥有自己定义的两个新方法 lightonlightoff

接口继承类

常见的面向对象语言中,接口是不能继承类的,但是在TypeScript中是可以的:

class Point{
	x:number;
	y:number;
	constructor(x:number,y:number){
		this.x = x;
		this.y = y;
	}
}

interface Point3d extends Point{
	z:number;
}

let point3d:Point3d = {x:1,y:2,z:3};

但在这里不推荐这样使用,我们在定义接口的时候只做定义,具体实现交给实现接口的类去完成。

三一、泛型01

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候在指定类型的一种特性。

首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值。:

function creatArray(length:number,value:any): Array<any>{
    let result = [];
    for(let i = 0; i < length; i++){
        result[i] = value;
    }
    return result;
}

creatArray(3,'x');//['x','x','x']

上例中,我们使用了之前提到过的数组泛型来定义返回值的类型。

这段代码编译不会报错,但是一个显而易见的缺陷是,他并没有准确的定义返回值的类型;

Array允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的value的类型。

这时候,泛型就派上用场:

function createArray<T>(length: number, value: T):Array<T>{
    let result: T[] = [];
    for(let i = 0; i<length; i++){
        result[i] = value;
    }
    return result
}
creatArray<string>(3,'x');//['x','x','x']

上例中,我们在函数名后添加了,其中T 用来指代任意输入的类型,在后面的输入value:T 和输出 Array 中即可使用了。接着在调用的时候,可以指定他的具体的类型为string 型。当然,也可以不手动指定,而让类型推论自动推算出来:

function createArray<T>(length: number, value: T):Array<T>{
    let result: T[] = [];
    for(let i = 0; i<length; i++){
        result[i] = value;
    }
    return result
}
//不指定类型,通过类型推断来自动推断出类型 
creatArray(3,'x');//['x','x','x']
三二、泛型02

多个类型参数

定义泛型的时候,可以一次定义多个类型参数:

function swap<T, U>(tuple:[T, U]): [U, T]{
	return [tuple[1],tuple[0]];
}
swap([7,'seven']);//['seven', 7]

上例中,我们定义了一个 swap 函数,用来交换输入的元组

三三、泛型03

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道他是哪种类型,所以不能随意的 *** 作它的属性或方法:

function loggingIdentity<T>(arg: T): T{
    console.log(arg.length);
    return arg;
}
//报错:类型“T”上不存在属性“length”。ts(2339)

上例中,泛型 T 不一定包含属性 length ,所以编译的时候报错了。

这时,我们可以对泛型进行约束,致允熙这个函数传入那些包含 length 属性的变量,这就是泛型约束:

interface LengthWise{
	length: number;
}
function loggingIdentity<T extends LengthWise>(arg: T): T{
    console.log(arg.length);
    return arg;
}

上例中,我们使用了 extends 约束了泛型 T 必须符合接口 LengthWise 的形状,也就是必须包含 length 属性。

此时如果调用 loggingIdentity 的时候,传入的 arg 不包含 length ,则会在编译时报错:

interface LengthWise{
	length: number;
}
function loggingIdentity<T extends LengthWise>(arg: T): T{
    console.log(arg.length);
    return arg;
}
loggingIdentity('1111');
// 4
loggingIdentity(8);
//报错:类型“number”的参数不能赋给类型“LengthWise”的参数。ts(2345)
三四、泛型04

泛型接口

之前学习过,可以使用接口的方式来定义一个函数需要符合的形状:

interface SearchFunc{
	(source: string, subString: string): boolean;	
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string){
    return source.search(subString) != -1;
}

当然也可以使用含有泛型的接口来定义函数的形状:

interface CreateArrayFunc{
    <T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T>{
    let result:T[] = [];
    for(let i = 0; i < length; i++){
        result[i] = value;
    }
    return result;
}

createArray(3,'x');
//['x','x','x']

进一步,我们可以把泛型参数提前到接口名上:

interface CreateArrayFunc<T>{
    (length: number, value: T): Array<T>;
}

let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T>{
    let result:T[] = [];
    for(let i = 0; i < length; i++){
        result[i] = value;
    }
    return result;
}

createArray(3,'x');
//['x','x','x']
createArray(3,true);
//[true,true,true]
createArray(3,{isExist:true});
//[{isExist:true},{isExist:true},{isExist:true}]
三五、泛型05

泛型类

与泛型接口类似,泛型也可以用于类的类型定义中:

class GenericNumber<T>{
	zeroValue!: T; //!为非空断言, 否则报错:属性“XXX”没有初始化表达式,且未在构造函数中明确赋值。
	add!: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x,y){ return x+y; };

泛型参数的默认类型

在TypeScript2.3以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

function createArray<T = string>(length: number, value: T):Array<T>{
	let result:T[] = [];
    for(let i = 0; i < length; i++){
        result[i] = value;
    }
    return result;
}
三六、声明合并

同名函数、接口、类的合并

如果定义了两个相同的名字的函数、接口或类,那么他们会合并成一个类型:

函数的合并

之前我们学习过,我们可以使用重载定义多个函数类型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string{
	if (typeof x === 'number'){
		return Number(x.toString().split('').reverse.join(''));
	} else if( typeof x === 'string'){
		return x.split('').reverse.join('');
	}
}
接口的合并

接口中的属性在合并时会简单地合并到一个接口中:

interface Alarm{
	price: number;
}
interface Alarm{
	weight: number;
}

相当于:

interface Alarm{
	price: number;
	weight: number;
}

注意,合并的属性的类型必须是唯一的:

interface Alarm{
	price: number;
}
interface Alarm{
	price: number;
    //虽然重复了,但是类型还是number,所以不会报错
	weight: number;
}
interface Alarm{
	price: number;
}
interface Alarm{
	price: string;
    //报错:后续属性声明必须属于同一类型。属性“price”的类型必须为“number”,但此处却为类型“string”。ts(2717)
	weight: number;
}

接口中的方法合并,和函数的合并一样:

interface Alarm{
	price: number;
	alert(s: string): string;
}
interface Alarm{
	weight: number;
	alert(s: string, n: number): string;
}

相当于:

interface Alarm{
	price: number;
	weight: number;
	alert(s: string): string;
	alert(s: string, n: number): string;
}
类的合并

类的合并与借口的合并一样

PS: 但是一般情况下,不建议创建多个同名接口或类,虽然可以自动合并,但是可能会发生意想不到的问题。代码不要写在两个地方,不然不好维护。

三七、写在结尾

TypeScript 应用非常广泛,最新的 Vue 和 React 均集成了 TypeScript ,这里推荐大家使用 Vue3 ,Vue3 天然支持 TypeScript。

另一方面,TS 中有很多支持 ES 的语法,关系图:

最后,多看文档

TypeScript英文文档

TypeScript中文文档

Vue3.2笔记后续更新————2022.5.15

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/940679.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-17
下一篇 2022-05-17

发表评论

登录后才能评论

评论列表(0条)

保存