TypeScript 数据类型与内置对象

注意,数据类型大小写不同。如Boolean是构造函数而boolean是基本类型,如Object包含数组,而object不包含。

类型兼容

在TS符合鸭子类型,只要两个类型内部结构相同即兼容

类型注解

声明变量时若没有设置类型和初始值,则类型为any,初始值为undefined
声明变量若指定了初始值但没有指定类型,则触发类型推论,自动附加类型。

基本类型
  • boolean
  • number
  • string
  • undefinednull
    两者各自有自己的类型分别叫做undefinednull
    未开启--strictNullChecks时,nullundefined是所有类型的子类型,例如可以把null赋值给number类型的变量。
  • void
    方法无返回,或return无值,或return undefined

object

所有非基本类型的父集

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create(undefined); // Error
数组
  • 类型+括号表示法
let a: number[] = [1, 2, 3];
let c: (number | string)[] = [1, 2, "3"];
let d: number[][] = [[1,2],[3,4]];
  • 数组泛型Array<>
let b: Array<number | string> = [1, 2, "3"]; 
  • 用接口表示数组
    通常仅用于类数组对象,如以下IArguments是内置接口
// interface IArguments {
//     [index: number]: any;
//     length: number;
//     callee: Function;
// }

function sum() {
    let args: IArguments = arguments;
}
元组(元素数量和类型固定的数组)

当访问一个越界的元素,会使用联合类型替代

let x: [string, number];
x = ['hello', 10];
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
any

声明变量时若没有设置类型和初始值,则类型为any,初始值为undefined
any类型会跳过编译阶段的类型检查,可以任意赋值或调用属性,不建议使用。

let something: any;
something = 'seven';
something = 7;

something.setName('Tom');
unknown

安全版的any,任何类型都能分配给unknown,而unknown只能分配给unknownany或者未声明类型
unknow类型必须类型断言转化为其他类型后才能操作,弥补了any的缺点。

let c: unknown = 100;
c+=10;//Object is of type 'unknown'
(c as number) += 10;
(<number>c) += 10;
let d = c as number;
never

方法不会执行完(抛出异常或无限循环)
never可以被赋值给任何类型,但任何类型都不能赋值给never

// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
    while (true) {}
}
let x: never;
// 运行错误,数字类型不能转为 never 类型
x = 123;
// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();

联合类型

满足其中一种类型即可赋值
但后续只能访问此联合类型中所有类型共有的属性或方法:

function fn(x: boolean | string):void{
    console.log(x.length);//error boolean上不存在属性length
}
字面量类型

直接将字面量作为一个类型,通常配合联合类型使用可起到类似枚举的作用

let num: 1 | 2 | 3 = 1;
let str: "a" | "b" | "c" = c;
类型推论

如声明变量时,指定了初始值但没有指定类型,则触发类型推论,自动附加类型。
如既没有初始值又没有指定类型,则推断为any

let num =  'seven';
num = "7";
num = 7;//error TS2322: Type 'number' is not assignable to type 'string'.

let num2;
num2 = 'seven';
num2 = 7;
类型断言

可通过值 as 类型<类型>值进行断言,不建议使用后者,因容易与泛型混淆。
类型断言只会影响 TypeScript 编译时的类型,类型断言语句在编译结果中会被删除。

通常用于为anyunknow、联合类型、外部传入内容、父类对象等,指定更精确的数据类型。当断言为更模糊的数据类型时要慎用,因等同于绕过了编译器的类型检查,可能会造成运行时错误

let a:number = 666;
let aLength1 = a.length;//报错 类型number不存在属性length
let aLength2 = (a as any).length;//不报错

通过双重断言甚至可以将一个类型先断言为any,再断言为任一其他类型。

let a:string = "hello";
(a as any as number) = 100;

类型声明比类型断言更严谨,因类型不会变更模糊:

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

const animal: Animal = {
    name: 'tom'
};

不报错 鸭子类型,虽然没有使用 interface Cat extends Animal,依然视为兼容
let fake_tom = animal as Cat;

报错
let real_tom: Cat = animal;

类型别名 type

使用type关键字定义数据类型,方便对数据类型进行管理和复用

//字面量类型
let num_1:1|2|3 = 1;
type Num_123 = 1 | 2 | 3;
let num_2: Num_123 = 1;
//元组
let tuple_1: [string, number] = ["1", 1];
type Tuple = [string, number];
let tuple_2: Tuple = ["1", 1];

interface IContact {
    name: string; // 姓名
    phone?: number; // 手机号
    email: string; // 邮箱
    avatar: string; // 头像
}
type Contact = IContact
  • Omit 用于去除类型中某些字段
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

type OmitContact = Omit<Contact, 'email' | 'avatar'>;
const obj_omit: OmitContact = {
    name: "vv"
};
  • Pick 用于只保留类型中某些字段
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

type PickContact = Pick<Contact, 'name' | 'phone'>;
const obj_pick: PickContact = {
    name: "vv"
};
  • Partial 用于把类型中所有字段变为可选
type Partial<T> = {
    [P in keyof T]?: T[P];
};

type PartialContact = Partial<Contact>;
const obj_partial: PartialContact = {};
  • Required 用于把类型中所有字段变为必选
type Required<T> = {
    [P in keyof T]-?: T[P];
};

type RequiredContact = Required<Contact>;
const obj_required: RequiredContact = {
    name: "vv",
    phone: 111,
    email: "123@qq.com",
    avatar: "XXX"
};
  • 也可以自行创建一个类型,如添加字段:
type WithId<T, P = number> = T & { id: P; };
type ContactWithId = WithId<PickContact>;
const obj_with_id: ContactWithId = {
    name: "vv",
    id: 1
};

枚举 enum

  • 数字枚举
    默认从0依次递增,也可以自己设置
    可以用key取value,也可以用value取key
enum Color {Red = 1, Green, Blue};
var c: Color = Color.Green;//2
var c2: string= Color[2];//"Green"
//事实上此时对应的js为:
//Color[Color["Green"] = 2] = "Green";
//即Color.Green为2,且Color[2]为'Green'
  • 字符串枚举
    只能用key取value
enum Color {Red = "red", Sat = <any>"S"};
var c:Color = Color["Red"];

接口 interface

接口是对行为的抽象,而具体如何行动需要由类去实现(implement)。此外,接口也可用于对象形状的描述。在TS中,其类型检查符合“鸭式辨型法”也称做“结构性子类型化”。
因接口是类型而非真正的值,在编译结果中会被删除,无法通过instanceof等判断。
当一个接口为空对象{}时,其相关的类型校验总是通过
当重复声明同名接口时,会合并声明为一个接口(前提是两次声明的内容无冲突)

  • 可选属性
    接口中定义的属性必须被实现,若可不实现,则应使用可选属性:key?: value;
  • 只读属性
    仅能在对象创建时赋值,之后不能修改:readonly key:value;
  • 任意属性(可索引类型)
    接口中未定义的属性不可被实现,若要实现,则应使用可索引类型
    一个接口中只能有一个任意属性,且其他属性的类型必须是任意属性的子集(若存在可选属性,则任意属性的属性应包含undefinedany
interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number | undefined | number[];
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male',
    params: [1,2,3]
};
对象接口
interface ILabel {
  label: string;
  name?: string;//可选参数
  readonly age: number;//只读参数,仅能在对象刚创建时赋值,之后不能修改
  todo?():void;//方法属性
  sayHello?:() => string;//方法属性
}

function printLabel(labelledObj: ILabel ) {
  ...
}
printLabel( {size: 10, label: "Size 10 Object",age: 18} );//会报错
函数接口

函数参数名不需要与接口里定义的名字相匹配

interface SearchFunc {
  (source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
  let result = src.search(sub);
  if (result == -1) {
    return false;
  }else {
    return true;
  }
}
console.log(mySearch('hello','h'));
类接口

类使用implements实现接口,详见“类”一章

混合接口

因JS灵活的特点,一个变量可能同时可以作为函数被调用,又具有对象的属性,此时其接口为一个混合接口

interface Counter {
  (source: string, subString: string): boolean;
  label: string;
}
接口继承(扩展)
interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {//一个接口可以继承多个
    sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;//必须同时满足两个接口

TS中声明一个类时,其实同时为该类声明了一个同名接口。因此和其他面向对象语言不同,TS的接口可以继承类,其本质上即继承该类的接口

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};
案例:用接口表示类数组
function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。

强化了静态属性

采用了ES7提案,废除了原型成员。
static不仅可修饰方法,也能修饰属性。类内直接定义的无static属性,均为实例属性而非原型属性。

实例属性必须先在类中声明,然后再通过this赋值

可为构造函数中的形参添加修饰符(访问修饰符 或 readonly)来简写该过程,自动为类声明该实例属性并赋值。

class Box {
    name: string; # js中可以省略该行,但TS中会报错
    constructor(name: string) {
        this.name = name;
    }
}

# 或简写为以下形式
class Box {
    constructor(public name: string) {}
}
修饰符
  • 只读修饰符 readonly
    被设置为只读的属性,只能在声明时或构造函数里被初始化,之后无法修改。

  • 访问修饰符

    • public
      默认值,公有
    • private
      私有,不能在声明它的类的外部访问(包括实例)
      构造函数为私有时,该类不能被继承和实例化
      两个类即使有同名private成员也无法兼容,除非该成员有同一来源
    • protected
      受保护,类似private,但在子类中也能访问
      构造函数为受保护时,该类只能被继承,不能被实例化
继承(extends)

不同于python,JS、TS、C#都只能继承单个类
包含constructor函数的派生类必须调用super(),它会执行基类的构造方法。

class Animal{
    name:string;
    age:number;
    constructor(theName:string='my name'){
        this.name=theName;
    }
    move(distance:number=100){
        console.log(this.name + 'move' + distance)
    }
    show(){
        console.log('this is show')
    }

}
let cat=new Animal('puppy');
cat.move(666)

class Dog extends Animal{
    name:string;//子类只能让基类的属性更具体,而不能违反;不声明时即和基类相同
    weight:number;
    constructor(ddd){
        super(ddd);//constructor内super必须在this之前
        this.weight=20;
    }
    move(){//子类方法可以覆盖基类
        console.log(this.name+this.weight);
        super.show()
    }

}
let dog=new Dog('ddd');
dog.move()

实现接口(implements)

和C#一样,TS中的类可以同时实现多个接口。

  • 接口仅描述类的部分成员
    类可自行声明更多成员,不违反接口。
  • 接口仅描述类的公有成员
    接口内描述的成员都是public。
  • 接口仅描述类的实例成员
    如静态成员、constructor等,不在描述范围内。
interface IBox {
    name: string;
}
interface IAnimal {
    age: number;
}
class BoxAnimal implements IBox, IAnimal {
    static currentTime(): Date { return new Date() };
    name: string = "TK";
    age: number;
    sex: number = 0;

    constructor(age: number) {
        this.age = age;
    }
}
let tk: BoxAnimal = new BoxAnimal(18);
console.log(tk);

抽象类与抽象方法 abstract
  • 带有abstract关键字的类称为抽象类,是供其它类继承的基类,介于类和接口之间,可以包含成员的实现细节,但不能被实例化。
  • 带有abstract关键字的方法称为抽象方法,只能在抽象类中定义。抽象方法不包含具体实现并且必须在派生类中实现。
abstract class Department {
   constructor(public name: string) {
   }
   printName(): void {
       console.log('Department name: ' + this.name);
   }
   abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {
   constructor() {
       super('Accounting and Auditing'); // constructors in derived classes must call super()
   }
   printMeeting(): void {
       console.log('The Accounting Department meets each Monday at 10am.');
   }
   generateReports(): void {
       console.log('Generating accounting reports...');
   }
}

let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type
把类当做接口使用

在TS中,类型(interface)和类(class)的界限并不清晰,本质上类也可充当一个仅包含其实例public成员的类型使用。
同理,当重复声明同名类时,会合并声明为一个类(前提是两次声明的内容无冲突)
因此可以用一个类实现另一个类,或者一个接口继承一个类。
此外,TS也符合鸭子类型。注意在鸭子类型中,a:A并不代表a是A或其子类的实例,只是形状相符而已。

interface IBox {
    name: string;
}
interface IAnimal {
    age: number;
}
class BoxAnimal implements IBox, IAnimal {
    static currentTime(): Date { return new Date() };
    name: string = "TK";
    age: number;
    sex: number = 0;

    constructor(age: number) {
        this.age = age;
    }
}
let tk: BoxAnimal = new BoxAnimal(18);
type TBoxAnimal = BoxAnimal;
interface IBoxAnimal extends BoxAnimal { };
let tk_2: TBoxAnimal = { name: "11", age: 18, sex: 0 };
let tk_3: IBoxAnimal = { name: "11", age: 18, sex: 0 };

对象

Typescript 中的对象不能随意增删属性字段:

let my_custom_date =20;
(window as any).my_custom_date = 30;
var obj = {
    name: 'ming',
};
// 事实上触发了如下类型推论:
// var obj: { name: string; } = {
//     name: 'ming',
// };
// obj.age = 19; //报错
obj["age"] = 19; //通过字典形式可以绕开类型检查
(obj as { name: string, age?: number; }).age = 19;//通过类型断言为obj添加可用属性

函数

函数声明方式定义函数:

function sum(x: number, y: number): number {
    return x + y;
}

函数表达式方式定义函数:

let myAdd: (baseValue:number, increment:number) => number =
    function(x: number, y: number): number { return x + y; };

注意在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

参数

传递给一个函数的参数个数必须与函数期望的参数个数一致

  • 可选参数
    必须写在必选参数之后 :
function fn(a:number,b?:any){ }
fn(100)
  • 参数默认值
    带默认值的参数也可以不传。
    不传或传入undefined时,会采用默认值
function fn(a:number,b='hello'){ }
fn(100)
  • 剩余参数
function(a,b,...c: any[]){ }
  • 函数重载
    ts的函数重载比较特殊,重载和具体实现是分开书写的。
    根据传入参数的不同,依次查找并尝试重载列表。 因此定义重载时,要把最精确的定义放在最前面。
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

注意,这里只有两个重载:一个是接收对象另一个接收数字。
function reverse(x: number | string): number | string | void并不是重载列表的一部分,需包含重载列表里所有的出入参情况,也可以写成function reverse(x: any): any


泛型(Generics)

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

泛型函数

保证返回值的类型与传入参数的类型相同。
通过重载也可以实现该功能,但使用泛型更方便。

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
泛型接口
interface GenericIdentityFn {
    <T>(arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;

也可以把泛型参数提前到接口名上,则使用泛型接口时,需要定义泛型的类型。

interface GenericIdentityFn<T> {
    (arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
泛型类
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
泛型约束

泛型因未知其实际类型,无法在函数内调用属性或方法。通过继承接口可告知编译器泛型的形状,称之为泛型约束

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

泛型之间也可以互相约束

function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = (<T>source)[id];
    }
    return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };

copyFields(x, { b: 10, d: 20 });
泛型参数默认类型

当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用

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;
}

内置对象

详见TypeScript核心库定义文件
注意,NodeJS不是内置对象,如需使用,则应引入@types文件:

npm install @types/node --save-dev
  • ECMA 内置对象
    Boolean、Error、Date、RegExp 等。
  • DOM 和 BOM 的内置对象
    Document、HTMLElement、Event、NodeList 等。

可以将变量定义为这些类型:

let b: Boolean = new Boolean(1);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容