typescript个人学习笔记

https://ts.xcatliu.com/basics/primitive-data-types.html
深受启发

1.剑谱第一页,初始化ts

typescript个人学习笔记_第1张图片
outDir表示把ts编译成js文件,文件编译后存放的位置

2.类型

2.1原始数据类型

  • 基础数据五种
    在这里插入图片描述
    在这里插入图片描述
    undefined可以赋值给其他类型
  • 引用类型数组对象
//定义数组一
let arr:[]=[]
let arr:number[]=[1,2,3]
//定义数组二:泛型
let arr:Array<number>=[1,2,3]

object表示非原始类型,除了数字、字符、布尔,都包含于object,包括null和undefined

let obj:object={}
object=null//不会报错
object=undefined//不会报错
object=[]//不会报错
object=new String()//不会报错
object=String//不会报错

2.2any

不知道类型用any,比方说后台可能会返回1或者true,你不知道,但是两种类型

let newArr:any[]=[100,2,4,"",true]
console.log(newArr[0].split(''))//会报错,any有优点也有缺点,无法判定是哪种类型,导致用不了

2.3void

表示空值类型,表示没有任何返回值的函数

function fun1():void{//函数定义什么类型就得返回什么类型

}
console.log(fun1())//undefined

let v:void=undefined

2.4类型推断

以下代码虽然没有指定类型,但是会在编译的时候报错:

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:

let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

2.5联合类型

表示取值可以为多种类型中的一种

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
console.log(myFavoriteNumber.split(''))//再次复制会走类型判断,类型不对就会报错

2.6对象的类型——接口

推荐接口名称带I大写,如IPerson

interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25
};

上面的例子中,我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。

  • 可选属性
interface Person {
    name: string;
    age?: number;//可选属性
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
  • 任意属性
    使用 [propName: string] 定义了任意属性取 string 类型的值。
interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

interface Person {
    name: string;
    age?: number;//这个写法必须是string,不然报错
    [propName: string]: string;//或者这里写联合类型
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.
  • 只读属性
interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

2.7数组的类型——接口(不常用)

内置对象

interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

2.8函数的类型——接口

//函数声明

interface SearchFunc {
    (source: string, subString: string): boolean;//boolean是返回值
}

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

输入多余的(或者少于要求的)参数,是不被允许的

//函数表达式

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {//=> number是返回值的意思,后面跟函数声明一样
    return x + y;
};

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

  • 可选属性
function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

可选参数后面不允许再出现必需参数了

  • 默认值
    此时就不受「可选参数必须接在必需参数后面」的限制了:
function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
  • 剩余参数
    items 是一个数组。所以我们可以用数组的类型来定义它,注意,rest 参数只能是最后一个参数
function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}

let a = [];
push(a, 1, 2, 3);
  • 重载
    函数名相同,形参不同的多个函数
    比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 ‘hello’ 的时候,输出反转的字符串 ‘olleh’。
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): number;//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('');
    }
}

注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

2.9类型断言

手动指定类型

2.9.1将一个联合类型断言为其中一个类型

typescript个人学习笔记_第2张图片
typescript个人学习笔记_第3张图片

  • 变量 as 类型(更推荐)
  • <变量>变量
    typescript个人学习笔记_第4张图片
    一个标准的错误示例
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function swim(animal: Cat | Fish) {
    (animal as Fish).swim();
}

const tom: Cat = {
    name: 'Tom',
    run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`

原因是 (animal as Fish).swim() 这段代码隐藏了 animal 可能为 Cat 的情况,将 animal 直接断言为 Fish 了,而 TypeScript 编译器信任了我们的断言,故在调用 swim() 时没有编译错误。

可是 swim 函数接受的参数是 Cat | Fish,一旦传入的参数是 Cat 类型的变量,由于 Cat 上没有 swim 方法,就会导致运行时错误了。

2.9.2将一个父类断言为更加具体的子类

继承,暂时放一放

2.9.3将任何一个类型断言为 any

window对象

window.a=10
//报错
// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.
(window as any).foo = 1;

需要注意的是,将一个变量断言为 any 可以说是解决 TypeScript 中类型问题的最后一个手段。

2.9.4将 any 断言为一个具体的类型

function getCacheData(key: string): any {
    return (window as any).cache[key];
}

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

const tom = getCacheData('tom') as Cat;//最后返回Cat类型
tom.run();




tom:{name:'',run(){},}

上面的例子中,我们调用完 getCacheData 之后,立即将它断言为 Cat 类型。这样的话明确了 tom 的类型,后续对 tom 的访问时就有了代码补全,提高了代码的可维护性。

3.进阶

3.1类型别名

常用于联合类型
在这里插入图片描述
typescript个人学习笔记_第5张图片

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

3.2字符串字面量类型

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dblclick'); // 报错,event 不能为 'dblclick'

在这里插入图片描述

3.3元祖

定义一个只有字符串和数字的数组

let tom: [string, number] = ['Tom', 25];//个数和位置全匹配

假如越界的话,越界的元素,它的类型会被限制为元组中每个类型的联合类型

let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);

// Argument of type 'true' is not assignable to parameter of type 'string | number'.

3.4枚举

我们可以使用枚举为一组数值去赋予名字

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

枚举成员会被赋值为从 0 开始递增的数字

var Days;
(function (Days) {
    Days[Days["Sun"] = 0] = "Sun";
    Days[Days["Mon"] = 1] = "Mon";
    Days[Days["Tue"] = 2] = "Tue";
    Days[Days["Wed"] = 3] = "Wed";
    Days[Days["Thu"] = 4] = "Thu";
    Days[Days["Fri"] = 5] = "Fri";
    Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

里面Days[“Sun”] = 0,外面Days[0] = “Sun”;互相映射
未手动赋值的枚举项会接着上一个枚举项递增
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的
所以使用的时候需要注意,最好不要出现这种覆盖的情况。

手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的):

enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};

当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 1

3.4.1常数项和计算所得项

枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。
计算所得项

enum Color {Red, Green, Blue = "blue".length};

上面的例子不会报错,但是如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错,就是说计算后的值要放最后,不然报错,前面还没计算出来,后面却要初始化

enum Color {Red = "red".length, Green=11, Blue=12};//这样是合理的

3.4.2常数枚举

const enum Directions {
    Up,
    Down,
    Left="blue".length,//报错
    Right=10+10//可以
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。

var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

3.4.3外部枚举

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

declare 定义的类型只会用于编译时的检查,编译结果中会被删除

var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

主要用在声明文件

3.5类

3.5.1属性和方法

class Animal {
    name:string;//ts一定要写
    constructor(name:string) {//要写,不写是any
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
}

let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

3.5.2类的继承

class Cat extends Animal {
  constructor(name:string) {
    super(name); // 调用父类的 constructor(name)
    console.log(this.name);
  }
  sayHi(str:string) {//重写父类方法
  	super.sayHi()//执行父类方法
    return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
  }
}

let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom

3.5.3存取器

使用 getter 和 setter 可以改变属性的赋值和读取行为:

class Name{
  firstName:string,
  lastName:string,
  constructor(firstName:string,lastName:string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  get fullName() {//读取器
    return this.firstName+this.lastName;
  }
  set fullName(value) {//设置器
    console.log('setter: ' + value);
    let names=value.split('-');
    this.firstName = names[0];
    this.lastName = names[1];
  }
}

let a = new Name('张','三'); // setter: Kitty
a.fullName= '张三'; // setter: 张三
console.log(a.fullName); // 张三

3.5.4静态方法

只属于类自己的实例和方法

class Animal {
  static isAnimal(a) {
    return a instanceof Animal;
  }
}

let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function,报错

3.5.5public private 和 protected

  • public
    修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
  • private
    修饰的属性或方法是私有的,不能在声明它的类的外部访问,包括其子类,但是这个属性和方法是可以被继承的
  • protected
    修饰的方法是受保护的,和private 区别是在子类允许被访问
class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
}

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

上面的例子中,name 被设置为了 public,所以直接访问实例的 name 属性是允许的。

很多时候,我们希望有的属性是无法直接存取的,这时候就可以用 private 了:

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

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

// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.

在子类也是不允许访问的

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

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

// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.

而如果是用 protected 修饰,则允许在子类中访问:

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

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

当构造函数修饰为 private 时,该类不允许被继承或者实例化:

class Animal {
  public name;
  private constructor(name) {
    this.name = name;
  }
}
class Cat extends Animal {
  constructor(name) {
    super(name);
  }
}

let a = new Animal('Jack');

// index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private.
// index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.

当构造函数修饰为 protected 时,该类只允许被继承:

class Animal {
  public name;
  protected constructor(name) {
    this.name = name;
  }
}
class Cat extends Animal {
  constructor(name) {
    super(name);
  }
}

let a = new Animal('Jack');

// index.ts(13,9): TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration.

3.5.6参数属性

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

3.5.7readonly

class Animal {
  readonly name:string;
  public constructor(name:string) {//只读属性,在构造函数里可以修改。这里构造函数加readonly(以及三个修饰符)指的是,创建并且初始化name参数
    this.name = name;
  }
  update(){
    this.name = '1'//报错
  }
}

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

// index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.

注意如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。

class Animal {
  // public readonly name;
  public constructor(public readonly name) {
    // this.name = name;
  }
}

3.5.8抽象类

abstract 用于定义抽象类和其中的抽象方法。
首先,抽象类是不允许被实例化的:

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

let a = new Animal('Jack');

// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.

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

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

class Cat extends Animal {
  public eat() {
    console.log(`${this.name} is eating.`);
  }
}

let cat = new Cat('Tom');

// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.

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