typescript学习笔记

ts的存在,是为了解决js是动态类型语言的缺陷。

​ 何为弱类型,简单理解就是定义的一个变量,它的类型没有被限定死,可以随意地变更为其他类型,这样一来,等代码复杂了,使用者就无法获知它的具体类型或者其值,从而使得bug易发并且效率降低。

​ 所以说,ts的出现,就是将js的动态类型(运行时报错)转变为静态类型(编译时报错)。

​ 这一点,体现在ts这门语言的方方面面。

一,声明变量

let arr:number[]=[1,2,3,4]
let arr2: Array<number> = [1, 1, 2, 3, 5];  //数组泛型
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];  //any在数组定义中的应用

这样写,就是限定arr为number类型的数组。

二,定义函数

function fn1(name:string,age:number):string{
    return `名字为${name},年龄为${age}`
}
let result=fn1('小华',18)
console.log(result)

也是约束和限定了函数的参数和函数的返回类型

function fn1(name:string,age?:number):void{
    console.log(`名字为${name},年龄为${age}`)
}
fn1('小华',18)

函数参数的可选 和函数的无返回的限定

这样写,都是某个参数是否是某种类型,并且是否传。

那如果是或的关系,一个参数可以是某些类型都可以呢?

这就引入了联合类型

三,联合类型

let arr:string[]|number[];
arr=[1,2,3]
arr=['1','2','3']

围绕ts是为了约束限定的中心点来看,联合类型的出现,就是为了解决某个参数可以是多种类型的情况。

但是呢,之前的约束,都是利用基本数据类型如number,string等进行的,有时候我们需要自定义类型,来约束某些复杂的参数,那怎么办?这就引入了接口。

四,接口

接口的出现,就是给使用者自定义用来限定的类型。在 TypeScript中,我们使用接口(Interfaces)来定义对象的类型。

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)

TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

interface MyType{
    name:string,
    age:number,
    eat:()=>void
}
let customer :MyType={
    name:'小明',
    age:18,
    eat:()=>{console.log("吃东西")},
}
customer.eat()

上面的例子中,我们定义了一个接口 Mytype,接着定义了一个变量 customer,它的类型是 Mytype。这样,我们就约束了 customer 的形状必须和接口 Mytype 一致。也就是说:赋值的时候,变量的形状必须和接口的形状保持一致

接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。

1,接口还能继承,使用extends关键字
interface MyType{
    name:string,
    age:number,
    eat:()=>void
}
interface OtherType extends MyType{
    heigh:number,
}
let customer :OtherType={
    name:'小明',
    age:18,
    heigh:178,
    eat:()=>{console.log("吃东西")},
}
customer.eat()
2,接口中可以综合使用readonly,联合类型等增加约束能力
interface MyType{
   readonly name:string,  //只读属性
    age:number|string,
    eat:()=>void
}
interface OtherType extends MyType{
    heigh?:number,   //可选属性,此时heigh若是赋值了,就是number类型,不赋值就是underfine类型
    [propName: string]: any;  //任意属性:(1,其他属性值类型必须是它的子集,2,一个接口中只允许出现一个任意类型,若有多种,则使用联合类型)
}
let customer :OtherType={
    name:'小明',
    age:'18',
    eat:()=>{console.log("吃东西")},
}
customer.eat()
3,接口还可以对可索引类型进行限定
interface User{
    [index:number]:string,
}
let arr:User=['1','2']

这样一来,使用User这个接口限定的元素,索引值必须是number,对应值必须是string。

4,当有其他不确定数量的参数时
interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;  //可以有其他任意数量的属性及值
}
5,给函数使用的接口定义-利用接口约束函数

从上文可以看到,接口都是在描述对象拥有的各种各样的外形。

此外,接口也可以描述函数类型。

//1,普通函数定义
function sum(x: number, y: number): number {
    return x + y;
}
//2,表达式定义的函数--注意这里不是箭头函数,而是对函数输入和输出的限定
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};
//3,函数也有可选参数这些的限定,且可选参数必须接在必需参数后面
//4,函数也有默认参数,且放置的位置不受约束
function buildName(firstName: string, lastName: string = 'Cat'):string {
    return firstName + ' ' + lastName;
}
//5,剩余参数使用...items来限定
function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
    });
}

//使用接口来约束函数的定义
interface SearchFunc {
  (source: string, subString: string): boolean; 
   //括号中就是对函数参数的限定,:外是对返回值的限定
}
let mySearch: SearchFunc = function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
}
6,类类型接口-利用接口约束类-implement

先是用接口定义一个数据类型结构,然后使用implements关键字,来实现这个接口,也就是说,新建的这个类,必须满足这个接口的数据结构形式。也就是被这个接口约束限定了,所以说,这里的接口依旧是起约束作用。

interface  Animal{
    name:string;
    eat(s:string):string;
    age:number;
}
 
//实现接口使用implements关键字
//狗类
class Dog implements Animal{
    name:string;
    age:number=12;
    constructor(name:string){
        this.name=name;
    }
    //实现接口中抽象方法
    eat(s:string):string{
       return  this.name+"吃肉:"+s;
    }
}
 
var dog=new Dog("tom");
console.log(dog.eat("五花肉"));
7,接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。

五,类型断言

值 as 类型
//将某个值断言为某种类型

既然说是断言,就要有程序员比ts更准确地知道该值是某种类型地觉悟。也就是说,ts是在编译时帮助我们检查错误的。而我们断言之后。编译器或者说ts就会无条件地信任我们的断言。从而通过编译阶段,这时如果有错误,则会在运行阶段爆出来。

1,将一个联合类型断言为其中一个类型
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === 'function') {
        return true;
    }
    return false;
}
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`

这时候就是,tom本来时cat类型,被我们断言成Fish类型,而fish有swim方法,编译器相信了我们的断言,所以就不会报错了。但是运行时,发现它是animal类型,没有swim方法,就报错了。

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

接口是一个类型,不是一个真正的值,它在编译结果中会被删除,当然就无法使用 instanceof 来做运行时判断了:

interface ApiError extends Error {
    code: number;
}
function isApiError(error: Error) {
    if (typeof (error as ApiError).code === 'number') {
        return true;
    }
    return false;
}
const aaa:Error={
    name: '测试',
    message: '只是测试一下哈哈哈'
}
console.log(isApiError(aaa))   //false
3,将 any 断言为一个具体的类型

举例来说,历史遗留的代码中有个 getCacheData,它的返回值是 any

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

那么我们在使用它时,最好能够将调用了它之后的返回值断言成一个精确的类型,这样就方便了后续的操作:

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

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

const tom = getCacheData('tom') as Cat;
tom.run();

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

4,类型断言的限制

若 A 兼容 B,那么 A 能够被断言为 B,B也能被断言为A

任何类型都可以被断言为 any

any 可以被断言为任何类型

六,类型别名

类型别名用来给一个类型起个新名字。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {   //NameOrResolver就成了一个新的类型
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

七,字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串中的一个。

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'

// index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'.

上例中,我们使用 type 定了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。

注意,类型别名与字符串字面量类型都是使用 type 进行定义。

八,元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;

tom[0].slice(1);
tom[1].toFixed(2);
//或者只赋值一个
let tom: [string, number];
tom[0] = 'Tom';

但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。

let tom: [string, number];
tom = ['Tom'];

// Property '1' is missing in type '[string]' but required in type '[string, number]'.

九,枚举

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

枚举使用 enum 关键字来定义:

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
//枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

手动赋值

enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
//未手动赋值的枚举项会接着上一个枚举项递增。
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的,但是会导致覆盖,最好不要这样做。

十,类

1,类的概念
类(Class):定义了一件事物的抽象特点,包含它的属性和方法
对象(Object):类的实例,通过 new 生成
面向对象(OOP)的三大特性:封装、继承、多态
封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
存取器(getter & setter):用以改变属性的读取和赋值行为
修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
2,类的创建

类是用来实现对象的创建和继承的。里面可以定义具体的值和方法,而接口,它是类型的定义,不能设置具体的值和方法。

即:接口用来定义类型,而类是用来创建对象的

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }  //构造函数,只在new实例对象的时候执行一次,其中的this指向这个新建的对象
    name:string='"孙悟空"          //这是实例属性的写法,只有new出实例对象之后才可以访问,只有在类实例化时才执行创建。
    static age:number=19     //这是静态属性的写法,只有直接通过Greeter.age才能访问,因为它定义在Greeter上
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

每次实例化一个类,就会执行构造器函数,从而给这个对象初始化赋值,并且构造器函数需要接收的参数,就是这个类要被实例化需要接收的参数。

3,类的继承
class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");

sam.move();

Snake类创建了 move方法,它重写了从 Animal继承来的 move方法,使得 move方法根据不同的类而具有不同的功能(多态)。

派生类包含了一个构造函数,它必须调用super(),它会执行基类的构造函数。 而且,在构造函数里访问this的属性之前,我们一定要调用super()。 这个是TypeScript强制执行的一条重要规则。

class Animal {
 name:string;
 age:number;
 constructor(name:string,age:number){
     this.name=name
     this.age=age
 }
 sayHello(){
     console.log("动物在叫~~~")
 }
}
//子类继承父类的所有属性和方法
class Dog extends Animal{
    sayHello(){
        console.log("汪汪汪!!!!")
    }
}
//子类继承父类的所有属性和方法,
//如果子类中添加了和父类相同的方法,则子类方法会覆盖父类方法(方法的重写)
class Cat extends Animal{
    constructor(name:string,age:number){
        super(name,age)
    }
    sayHello(){
        console.log("喵喵喵")
    }
    run(){
        console.log(`${this.name}在跑~~~`)
    }
}
const dog1 = new Dog('大黄',22)
console.log(dog1)
dog1.sayHello()
const cat1 = new Cat('嘻嘻',18)
console.log(cat1)
cat1.sayHello()
cat1.run()
//继承的好处在于,可以在不修改原有类的基础上,继承原有类,从而修改类。

super调用父类

class Animal {
 name:string;
 age:number;
 constructor(name:string,age:number){
     this.name=name
     this.age=age
 }
 sayHello(){
     console.log("动物在叫~~~")
 }
}
class Dog extends Animal{
    heigh:number
    constructor(name:string,age:number,heigh:number){
        super(name,age)
        //因为子类写了同名函数,就会覆盖父类的constructor,所以这里需要用super()来调用执行父类的构造函数
        this.heigh=heigh
    }
    sayHello(){
        console.log("汪汪汪!!!!")
    }
}
const dog1 = new Dog('大黄',22,178)
console.log(dog1)
dog1.sayHello()	
4,抽象类
//抽象类,只能被继承,不能创建实例
abstract class Animal {
 name:string;
 age:number;
 constructor(name:string,age:number){
     this.name=name
     this.age=age
 }
abstract sayHello():void
 //抽象类中的抽象方法,子类必须重写
}
class Dog extends Animal{
    heigh:number
    constructor(name:string,age:number,heigh:number){
        super(name,age)
        this.heigh=heigh
    }
    sayHello(){
        console.log("汪汪汪!!!!")
    }
}
const dog1 = new Dog('大黄',22,178)
console.log(dog1)
dog1.sayHello()
5,公共,私有与受保护的修饰符
默认为 public
private私有,只有类的内部可以访问
protected,受保护,类的内部和子类中可以访问
readonly关键字将属性设置为只读的。
6,静态属性

到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。

class Grid {
    static origin = {x: 0, y: 0};
    name:string='名字';
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale
console.log(grid1.origin)   //报错了,没有这个属性
console.log(Grid.origin)  //satic搞出来的,只在自己对象上
console.log(grid1.name) //有这个属性,说明正常初始化的属性是在原型对象上的
7,把类当做接口使用

类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}
//定义一个新的接口Point3d,它的内容继承自Point
let point3d: Point3d = {x: 1, y: 2, z: 3};
8,存取器

get有点类似于vue中的计算属性,可以直接使用

class Food{
    element:HTMLElement;
    constructor(){
        //获取食物的dom元素节点,getElementById方法返回值可能为element或者null,这里肯定是element,所以加了!
        this.element=document.getElementById('food')!;
    }
    //定义一个获取食物x坐标的方法
    get X(){
        return this.element.offsetLeft;
    }
    get Y(){
        return this.element.offsetTop;
    }
}
const food=new Food()
console.log(food.X,food.Y)

十一,泛型

可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

可以说,接口的出现是为了自定义数据的类型,那么泛型的出现,则是为了让可以定义的类型变得更加灵活。适用范围更广。

//传入啥类型,就会返回啥类型,这里就是利用<T>来先定义一个类型T,然后后续直接使用这个T
function identity<T>(arg: T): T {
    return arg;
}
let output = identity<string>("myString");  // type of output will be 'string'
//此时就是通过<string>来手动指定,必须是string类型
let output = identity("myString");  // type of output will be 'string'使用类型推论
1,泛型变量

这时候,是把T作为一个类型变量,参数arg必须是一个数组,且参数的类型为T。返回值亦然。

function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

或者:

function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

这种用法,实际上就是把泛型当作一个指代符,用来暂时性地指代它是某一种类型。

2,泛型接口
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
3,泛型类
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;
};
//同样是作为泛型变量,来约束这个类的一些参数类型统一。
4,泛型约束
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;
}
//定义一个函数,这个函数使用T来作为类型约束,而T继承了Lengthwise,所以说T必须具备length属性

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐