TypeScript学习笔记(三)

开启ts学习之路,本篇ts的ES6面向对象、接口类型和泛型。

一、ES6面向对象

ES5的面向对象是由函数实现,加上TS的话按照之前内容即可。而ES6的面向对象是使用class来定义类,因此需要特殊讲一下:

1、类

以下描述一个Animal类:

class Animal {
  name: string;   // 声明了Animal类中的name属性必须为string类型
  constructor(name: string){
    this.name = name;
  }
  sayName():void{	// 加void表示没有返回值
      console.log(this.name);
  }
}

let animal = new Animal("小瑶");
animal.sayName();   // 小瑶
    

2、继承

定义一个Cat类,继承自Animal类:

class Animal {
  name: string;   // 声明了Animal类中的name属性必须为string类型
  constructor(name: string){
    this.name = name;
  }
  sayName():void{
    console.log(this.name);
  }
}

class Dog extends Animal {
  constructor(name: string){
    super(name);
  }
  shout(): void{
      console.log(`${this.name}正在“汪汪”`);
  }
}

let dog = new Dog("小瑶");
dog.shout();   // 小瑶正在喊“汪汪”
    

3、公共、私有与受保护的修饰符

类属性和方法除了可以通过 extends 被继承之外,还可以通过修饰符控制可访问性。

在 TypeScript 中就支持 3 种访问修饰符,分别是 public、private、protected。

a. 公共修饰符public

public 修饰的是在任何地方可见、公有的属性或方法。上述代码中,凡是没有加修饰符的,都是默认已经自带public:

class Animal {
   name: string;
   constructor(name: string){
      this.name = name;
   }
   sayName():void{
      console.log(this.name);
   }
}

let animal = new Animal("张三");
animal.sayName();

// 以上代码相当于:

class Animal {
   public name: string;
   constructor(name: string){
      this.name = name;
   }
   public sayName():void{
      console.log(this.name);
   }
}

let animal = new Animal("张三");
animal.sayName();

b. 私有修饰符private

private 修饰的是仅在当前类中可见、私有的属性或方法。具体如下:

class Animal {
    public name: string;
    private age: number;	// 只能在当前类中使用,实例和子类都不能用
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
    public sayName():void{
        console.log(`${this.name}今年${this.age}`);
    }
}

let animal = new Animal("张三", 12);
animal.sayName();           // 张三今年12岁
console.log(animal.age);    // 属性“age”为私有属性,只能在类“Animal”中访问 ✖
    

c. 受保护修饰符protected

protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法。

class Animal {
    public name: string;
    protected age: number;  // 只能在当前类和子类中使用,实例不能用
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
}

let animal = new Animal("张三", 11);
console.log(animal.age);    // 属性“age”受保护,只能在类“Animal”及其子类中访问 ✖

class Cat extends Animal {
    constructor(name: string, age: number){
        super(name, age);
    }
    shout(): void{
        console.log(`${this.name}正在喊“喵喵”,它今年${this.age}`);	 // 可以使用 ✔
    }
}

let cat = new Cat("嘟嘟", 11);
cat.shout();   // 嘟嘟
    

4、只读属性与静态属性

a. 只读属性readonly

加上readonly的属性只能读不能写:

TypeScript学习笔记(三)_第1张图片

b. 静态属性static

加上static的属性与方法,无需实例化即可调用:

class Animal {
    // 直接给静态属性赋值
    static username: string = "张三";
}

Animal.username; // 张三
    

5、抽象类abstract

简单一句话:抽象类无法被实例化。

// 定义一个抽象类
abstract class Animal {
    name: string;
    constructor(name:string){
        this.name = name;
    }
}

let animal = new Animal("张三");    // 无法创建抽象类的实例。
    

十、接口类型

请先看这段js代码:

function fn({name, age}){
    console.log(`${name}的年龄为:${age}`)
}

fn({name: "张三", age: 18});	// 张三的年龄为:18
    

它的作用是给函数传了个对象,并且把对象中两个字段解构出来使用。但如果你要指定两个字段的类型,就会报错了:

TypeScript学习笔记(三)_第2张图片

此时如果你通过以下代码书写,效果就不一样了:

function fn({name, age}: {name: string; age: number}){
    console.log(`${name}的年龄为:${age}`)
}

fn({name: "张三", age: 18});	// 张三的年龄为:18
  

1、接口类型

其实这段代码是接口类型的演变,原来的表现形式应该如此:

// 使用接口定义字段类型
// 接口一般首字母大写,经常使用I开头
interface IObj {
    name: string;
    age: number;
}

function fn(obj: IObj): void{
    console.log(`${obj.name}今年${obj.age}`)
}

fn({name: "张三", age: 18});	// 张三今年18岁
    

以上接口中,name和age的顺序是可以打乱的,不会影响代码执行。

2、可缺省属性

接口中定义的字段,参数必须传,但有些参数我们希望可传可不传,可以如下:

interface IObj {
    name: string;
    age?: number;	// ?表示该参数可传可不传
}

function fn(obj: IObj): void{
    console.log(`${obj.name}今年${obj.age || 0}`)
}

fn({name: "张三"})	// 张三今年0岁

3、任意属性

其实属性名称有时也不确定,因此可以使用以下方式定义,但值必须为any。

interface IObj {
    name: string;
    // [propName: string]: string | number;
    // 这个参数名称可能也是不确定的,也不清楚它的类型,可以any,也可指定若干数据类型,建议直接any
    [propName: string]: any; 
}

function fn(obj: IObj): void{
    console.log(`${obj.name}${obj.sex || '男'}`)
    
}

fn({name: "张三"})				// 张三是男性
fn({name: "张三", sex: "女"})	   // 张三是女性
    

注意:

任意属性和可选属性最好不能共存,即便共存最好类型一致。

十一、泛型

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

现在有一个需求:有一个函数可以创建指定长度的数组,每一项都要填充一个指定值。

function createArr(length: number, value: any): Array<any>{
// 或:function createArr(length: number, value: any): any[]{
    let arr = [];
    for(var i=0;i<length;i++){
        arr[i] = value;
    }
    return arr;
}

console.log( createArr(3, "猴子的救兵") );  // [ '猴子的救兵', '猴子的救兵', '猴子的救兵' ]
   

以上代码在函数调用前,是不清楚返回的数据类型的,因为value的数据类型为any。但我们说尽量不使用any,毕竟它绕过了类型检查,因此,我们需要借助泛型来解决。

1、泛型简单使用

一般我们用 T 来代表输入和返回的数据类型,直到调用了才明确 T 是什么数据类型:

function createArr<T>(length: number, value: T): Array<T>{
// 或:function createArr(length: number, value: T): T[]{
    let arr: T[] = [];
    for(var i=0;i<length;i++){
        arr[i] = value;
    }
    return arr;
}

console.log( createArr<string>(3, "猴子的救兵") ); // [ '猴子的救兵', '猴子的救兵', '猴子的救兵' ]
    

2、多个类型参数

如果有多个参数都是类型未知的,那么泛型应该这么写:

// 情况一:多个参数传入
function fn<U, T>(flag: U, value: T): Array<U | T>{
    let arr: Array<U | T> = [flag, value];
    return arr;
}

console.log(fn(true, "我是孙悟空"))		// [ true, '我是孙悟空' ]

// 情况二:单个参数传入,且为数组
function fn<U, T>(arr: [U, T]): Array<U | T>{
    let newArr: Array<U | T> = [...arr];
    return newArr;
}

console.log(fn([true, "我是孙悟空"]))  // [ true, '我是孙悟空' ]

你可能感兴趣的:(typescript学习,typescript,学习,javascript)