typescript 讲解 object 新增特性

625 阅读9分钟

前言:根据资料类型来学习

  1. (已讲完) 原始资料型别(Primitive types) : string (字串)、number (数值)、boolean (布林值)、null、undefined
  • string (字串)、number (数值)、boolean (布林值) 没什么好讲的,跟 JavaScript 一样~~
  • null、undefined typescript 配置与设定
  1. (已讲完) TypeScript 才有的型别: any、unknown、void、 never、 union types (联合型别) 、intersection types(交集型别)、 literal types (字面值型别)、 tuple (元组)、 enums (列举)
  1. (这期讲) 物件型别(Object types): object (物件) 、 arrays (阵列) 、function (函式)

上期讲 function TypeScript 才有的型别讲解

本期讲 object (物件)

interfaces---定义物件的型别

?:可选属性

readonly:防止属性被修改


interface IPerson {
    name?: string;
    readonly age: number;
}

const iris: IPerson = {
    name: 'Jack',
    age: 18
};

interface 还可以定义阵列及函式

//阵列
interface INumberArray {
    [index: number]: number;
}
const list: INumberArray = [1, 1, 2, 3, 5];

//函式
interface IPersonFunc {
  (name: string, age: number): void;
}

interface 可以 function overload

interface IPoint {
    getDist(): number;
    getDist(x?: number): number;
}

const point:IPoint = {
    getDist(x?: number) {
         if (x && typeof x == "number") {
             return x;
         } else {
             return 0;
         }
    }
}

console.log(point.getDist(20)); //20
console.log(point.getDist()); //0

Index Signatures----新增任意属性

语法:[propName: string]

所有成员属性格式都必须符合 Index Signatures

// error example
interface NumberDictionary {
  [index: string]: number;
  length: number; // ok
  name: string; //error: Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}

// ok example
interface NumberOrStringDictionary {
  [index: string]: number | string;
  length: number; // ok, length is a number
  name: string; // ok, name is a string
}

泛型(Generics)---- 不预先指定具体的型别而在使用的时候再指定型别的一种特性

Generics实现了类型自由但是又不会出现any,undefined和null的缺点

除了使用「型别 + 方括号」来表示 array, 也可以使用泛型方式来表示

在函式后面加上 <Type> 表示动态型别,<Type>命名也是可以自行定义的,如<List>。只是 <T> 及 <Type> 比较通用。

//「型别 + 方括号」
const list1: number[] = [1, 2, 3];
//阵列泛型 
const list2: Array<number> = [1, 2, 3]; 

//「型别 + 方括号」
function identity2(arg: number[]): number[] {
  console.log(arg);
  return arg;
}

identity2([1, 2, 3]); // [1, 2, 3]

//阵列泛型 
function identity(arg: Array<number>): Array<number> {
  console.log(arg);
  return arg;
}

identity([1, 2, 3]); // [1, 2, 3]

//arrow function
const makeTuple2 = <T, U>(tuple: [T, U]): [T, U] => {
  return [tuple[0], tuple[1]];
}

const tuple2 = makeTuple2([1, "a"]); 
console.log(tuple2); //[ 1, 'a' ]

Generic Constraints 泛型约束

在函式内部使用泛型变数的时候,由于事先不知道它是哪种型别,所以不能随意的操作它的属性或方法。 可以用 extends解决

interface Lengthwise {
    length: number;
}

function loggingIdentity2<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

loggingIdentity2(3); //error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
loggingIdentity2({ length: 10, value: 3 });

Generic Interface

interface GenericIdentityFn {
  <Type>(arg: Type): Type;
}
 
function identity<Type>(arg: Type): Type {
  return arg;
}
 
let myIdentity: GenericIdentityFn = identity;

Generic Classes

class GenericNumber<NumType> {
  zeroValue: NumType;
  add: (x: NumType, y: NumType) => NumType;
}

//限制为 number 型別
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
};

Generic Function 带入不同的值对应的型别

以下例子想要取得带入参数 array 第 0 个的值, 我们可以看到 s 回传值的型别会是any, 但我想要带入不同的值对应的型别,该怎么做呢。

function firstElement(arr: any[]) {
  return arr[0];
}

const s = firstElement(["a", "b", "c"]);
const n = firstElement([1, 2, 3]);

console.log(s); //a
console.log(n); //1

我们可以使用泛型,我们在函式名后添加了,其中 Type 用来指代任意输入的型别,让型别推论自动推算出来。可以看到变数 s 会是 string 型别。而 n 会是 number 型别。

function firstElement<Type>(arr: Type[]): Type {
  return arr[0];
}

// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);

console.log(s); //a
console.log(n); //1

Inference ---泛型有了类型推断再也不用担心不够灵活了

除了使用 <Type> 的方式,可以传入多组参数如<Input, Output>,让 TypeScript 自动 inferred 型别。

如下,n被 inferred 为string, 而透过 parseInt 字串转数字,最后 parsed 被 inferred 的型别是number[]Input及 Output 命名可以自行定义。

function map<Input, Output>(
  arr: Input[],
  func: (arg: Input) => Output
): Output[] {
  return arr.map(func);
}

// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n, 10));

console.log(parsed); //[1,2,3]

指定参数型别

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

const arr = combine([1, 2, 3], ["hello"]);
//❌ error: Type 'string' is not assignable to type 'number'.


const arr = combine<string | number>([1, 2, 3], ["hello"]);
console.log(arr); //[1, 2, 3, "hello"]
// ⭕ 指定他为string | number 联合型别。

Class

Class简单来说就是物件的模板,定义了一件事物的抽象特点,包含它的属性和方法,提供一个更简洁的语法来建立物件和处理继承

一些 Class 要知道的事

  1. constructor: 用来初始建构一个类别 (class) 的物件。一个 class 只能有一个 constructor。(是可以不指定 constructor 的,如果不指定 constructor, 就会就会使用预设的 constructor,长这样constructor() {})
  2. extends: 使用 extends 继承类别, 建立子类别,子类别可以使用父层的东西。
  3. super: 可以使用 super 于通过函式存取父层,super 关键字必须出现在 this 关键字之前使用
  4. prototype methods : 建立 class 的方法
  5. getter : 使用关键字 get,用以改变属性的读取
  6. setter : 使用关键字 set,用以改变属性的赋值(- getter & setter 的好处就是,不会改变原本 constructor 的东西,也可以直接像使用 class 的属性一样去使用。)
  7. static method: 使用关键字 static 在 class 建立静态方法,在 class 中的其他方法都不能使用他,静态方法常被用来建立给应用程式使用的工具函数。
  8. 物件(Object):类别的实例,透过 new 产生
  9. 抽象类别(Abstract Class):抽象类别是供其他类别继承的基底类别,抽象类别不允许被实例化。抽象类别中的抽象方法必须在子类别中被实现
  10. 介面(Interfaces):不同类别之间公有的属性或方法,可以抽象成一个介面。介面可以被类别实现(implements)。一个类别只能继承自另一个类别,但是可以实现多个介面。
  • Fields: 在 ES6 中, 是无法直接在 class 中定义属性,必须定义在 constructor 里面并使用this.,而 ES7 这可以直接在 calss 中定义, 而 TypeSript 也跟上脚步,可以直接在 class 里面去定义属性,且 public 公用的
  • readonly : 可以使用 readonly 关键字来唯读属性, 防止进行赋值
class Point {
  x: number; //可以使用实例属性
  y: number;
  readonly name: string = "show point"; //readonly唯读

  constructor(x = 0, y = 0) {  //预设给0
    this.x = x;
    this.y = y;
  }
  
  printPoint() {
    console.log(`x: ${this.x}, y: ${this.y}`);
  }
}

let p = new Point(2, 4);
p.printPoint();  //x: 2, y: 4 
p.name = "hihi"; //若对readonly属性赋值会报错 error: Cannot assign to 'name' because it is a read-only property.

  • --strictPropertyInitialization(tsconfig.json) : class 实例属性的赋值检查。如下面例子,已宣告 name 属性型别,却没给予赋值, 就会报错提醒。
class GoodGreeter {
  name: string; //error: Property 'name' has no initializer and is not definitely assigned in the 
  word: string;

  constructor() {
    this.word = "hello";
  }
}

Constructors型别定义

constructors: 他跟 function 的使用很像, 你可以使用型别注记,预设参数

Index Signatures

在 JavaScript 中使用 [] 来操作 object 属性,那些属性 JavaScript 都会要 toString 去读取, 如下方例子, 使用 obj["name0"] 没问题,使用 obj[name1] 则会报错。

let obj = {
  name0: "iris",
  name1: "iris",
  2: "iris"
};

console.log(obj["name0"]);  //iris
console.log(obj[2]); //iris
console.log(obj[name1]); //error: name1 is not defined

在 TypeScript 中, 会强制提醒使用 toString,减少我们的错误。
TypeScript 的 Index Signatures 必须是 string 或者 number。

let obj2 = {
  toString() {
    console.log('toString called');
    return 'Hello';
  }
};

class Foo {
  constructor(public message: string) {}
  log() {
    console.log(this.message);
  }
}

let foo: any = {};
foo['Hello'].log(); // World
foo[obj2] = new Foo('World'); //error: Type '{ toString(): string; }' cannot be used as an index type.

公共 & 私人 & 保护

  • 都没写的话,预设值为 public
  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,预设所有的属性和方法都是 public 的
  • private 修饰的属性或方法是私有的,不能在宣告它的类别的外部访问
  • protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类别中也是允许被访问的

私有构造函数

该类别不允许被继承或者实例化:

class Person2 {
    public name:string;
    private constructor (name:string) {
        this.name = name;
  }
}
class Child2 extends Person2 {  //无法被继承 error: Cannot extend a class 'Person2'. Class constructor is marked as private.
    constructor (name) {
        super(name);
    }
}

let a2 = new Child2('iris');

Abstract 抽象

抽象类别中的抽象方法必须被子类别实现:

abstract class Animal2 {
  public name;
  public constructor(name:string) {
      this.name = name;
  }
  public abstract sayHi():void;
}

class Cat extends Animal {
  public sayHi() {
      console.log(`Meow, My name is ${this.name}`);
  }
}

let cat = new Cat('Tom');

static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接透过类别来呼叫:

class Animal3 {
    public name;
    public constructor(name:string) {
        this.name = name;
    }
    static isAnimal(c:object) {
        return c instanceof Animal;
    }
}

let c = new Animal3('Tom');
let d = Animal3.isAnimal(c); 
console.log(d); //true
c.isAnimal(c); //c.isAnimal is not a function

static 也能够继承


class Base2 {
  static getGreeting() {
    return "Hello world";
  }
}
class Derived2 extends Base2 {
  myGreeting = Derived2.getGreeting();
}

let derived2 = new Derived2();
console.log(derived2)

Class 与 Interface

我们在前面的时候有提过 interface 是用来定义物件的型别,对物件的形状进行描述。在物件导向程式语言中,介面(Interfaces)是一个很重要的概念,它是对行为的一种抽象,而具体如何行动则需要由类别(class)去实现(implement)

我们可以使用 implements 来检查是否有满足介面的形状

interface Alarm {
    alert();
}

//门是一个类别
class Door {
}

//防盗门是门的子类别, 有一个报警器的功能, 新增报警方法
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}

//车的类别也有报警器的功能,新增报警方法
class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

const car = new Car();
car.alert();//Car alert

一个类别可以实现多个介面

可以使用 , 来实现多个介面:

interface Alarm2 {
    alert():void;
}

interface Light2 {
    lightOn():void;
    lightOff():void;
}

//Car 实现了 Alarm 和 Light 介面,既能报警,也能开关车灯。
class Car2 implements Alarm2, Light2 {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}


const car2 = new Car2();
car2.lightOn(); //Car light on

介面继承介面

介面与介面之间可以是继承关系:

interface Alarm3 {
    alert():void;
}

interface LightableAlarm extends Alarm3 {
    lightOn():void;
    lightOff():void;
}

class Car3 implements LightableAlarm {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

const car3 = new Car3();
car3.alert(); //Car alert

介面继承类别

介面也可以继承 class:

class Point {
    x: number;
    y: number;

    constructor(x = 0,y = 0){
        this.x = x;
        this.y = y;
    }
}

interface Point3d extends Point {
    z: number;
}

const point3d: Point3d = {x: 1, y: 2, z: 3};
console.log(point3d); //{ x: 1, y: 2, z: 3 }

小声说

基础篇先到这喔~~ 乖乖打好地基,我们下次见啦~

typescript 配置与设定

TypeScript 才有的型别讲解

typescript 讲解 Function 新增特性