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
类的存取器 :get
、set
使用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
实现了 Alert
和 Light
接口,既能报警,也能开关灯。
接口和接口之间可以是继承关系:
interface Alert{
alert():void;
}
interface LightableAlert extends Alert{
lightOn():void;
lightOff():void;
}
这很好理解,LightableAlert
继承了 Alert
,除了拥有alert
方法之外,还可以拥有自己定义的两个新方法 lighton
和lightoff
。
常见的面向对象语言中,接口是不能继承类的,但是在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
函数,用来交换输入的元组
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道他是哪种类型,所以不能随意的 *** 作它的属性或方法:
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
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)