TypeScript学习(上)

TypeScript的介绍

1.ts是由微软开发的开源变成语言(vscode和ts都是微软提供的,vs里面很多ts的适配,里面有很多ts的插件,让写法更佳舒适)

2.ts是js的超集(超集的理解:ts在js之上,包含js所有最新的语法特性,包含es6、es7所对应的写法)

【 es 3一个版本,es5一个版本 ,es 6一个版本,es2017,2018,2019都是es7版本】

3.ts是开发大型应用的基石(为什么要学ts?很多大型项目都是基于ts来开发的,比如angular,vscode,vue 3.0,react;越来越多的特性都偏向ts,大型应用基本都用ts语法做支持)

4.ts提供了更丰富的语法提示

5.ts在编译阶段能检查语法错误

安装

1.先去官网安装node

2.npm i typescript

错误检查&ts的编译

编译:

(为什么需要编译?因为浏览器无法识别ts,所以需要将ts编译成js)

var a:string='123';
console.log(a);

 执行tsc index.ts,将index.ts文件编译为index.js文件。

 创建配置文件:tsc --init    , 配置完rootDir和outDir后,直接输入tsc编译

错误检查:

 但是这段代码在js中却可以正确运行

js是动态类型,可以动态更改变量的类型

var a = '123';
a=123;
console.log(a);

ts是静态类型的(一旦定义完类型后就无法更改它的类型):

ts中的数据类型&&ts新增的语法特性

数据类型

js:基本类型:boolean 、string、number、null、undefined、symbol

      引用类型:object

ts:数据类型:

  1.  number
    let num: number = 6;
  2. string
    let str: string = '123';
  3. boolean
    let flag: boolean = false;
  4. any
    let obj:{ // 对象类型的注解
        str: string,
        toString: () => void
    } = {
        str: '124',
        toString: function () {
    
        }
    }
    console.log(obj);
    
    // 或这样写1
    // interface Obj {
    //    str: string;
    //    toString: () => void
    // }
    // let obj: Obj = {
    //     str: '123',
    //     toString:function () {
    
    //     }
    // }
    
    // 或这样写2
    // type Obj = {
    //    str: string;
    //    toString: () => void
    // }
    // let obj: Obj = {
    //     str: '123',
    //     toString:function () {
    
    //     }
    // }

    let obj:any = {
        str: '124',
        toString: function () {
    
        }
    }
    console.log(obj);

     

     表示任意数据类型。

    1)如果是对象的话,any不能提示原有的属性和方法 

    2)如果是不同变量的话,可以是任意的数据类型

    3)未给初始值的变量类型为any (比如let a; 这里的a就是any类型,所以可以a='123'; a=123;不会报错)

  5. undefined ,
    let num: number = undefined;
    配置项中--strictNullChecks会严格检查null和undefined类型,赋值会赋不成功                      (想赋的话把此参数改成true,但是官网不建议这么赋值,想这样赋的话就用联合项)                                                       推荐写法:
    let num: number | undefined = undefined;
                                                                     
  6. null  ,null和undefined是所有类型的子类型。
  7. never,表示永不存在值的类型。当报错或死循环的时候用它。
    function error(message) {
        throw new Error(message);
    }
    function fail() {
        return error('something failed');
    }
    function infiniteLoop() {
        while (true) {
        }
    }
  8. void  :表示没有类型,用在函数没有返回值的时候。
    function test(): void {
        console.log(1111);
    }
  9. 枚举
  10. 数组 :
    // 数组的注解;有3种表示方法
    // 1.类型[]
    let arr1: number[] = [1, 2, 3];
    
    // 2.Array<类型> Array<number>
    let arr2: Array<number> = [1, 2, 3];
    
    // 3.interface
    interface NumberArray {
      [index: number]: number;
    }
    let list: NumberArray = [1, 2, 3, 4];
    
    
    // 类数组
    function test() {
      let args: IArguments = arguments;
    }

  11. 元祖

  Object:非原始类型。

// 全局声明一个函数,规定这个函数的参数是对象或null,没有返回值
declare function create(o: object | null): void;

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

create([]); // OK
create(function(){}); // OK
create({}); // OK
// 由此看出,这里的object就是非原始类型

      进一步的区分:interface、number[]、string[]、boolean[]

      数组中泛型的写法:Array<number>、Array<string>、Array<boolean>

      函数注解:

let test=function(a:number,b:number):number{
    return a+b;
}

类型注解

开发者自己定义的ts变量类型就是类型注解

类型推断

当我们没有指定类型的时候,ts会自动帮我们分析当前的变量类型。

(当类型推断能推断出正确的类型时,就不用类型注解,比如函数这种情况下

function add(a, b) {
  return a + b;
}
add(1, 2);

联合类型

注意:

1.联合类型的公有属性是不会报错的;比如toString(),number和string类型都有这个方法就不会报错。

2.在赋值的时候就已经确定了数据的类型。不能更改类型了。

 

接口

对对象的形状进行描述;

对类的一部分的行为的抽象;

interface Person {
  name: string;
  age: number;
}
let person: Person = {
  name: "lily",
  age: 18,
};

【interface定死了以后,它的属性不可多,也不可少

 

如果想要多或少属性怎么办呢?

;可以用可选属性?:

多;用任意属性[propName:string]:any

 

如果遇到id这种唯一不可变的属性,可以用readonly属性

 改造:

ReadonlyArray<T>类型

let ro: ReadonlyArray<number> = [1,2,3,4];

readonly 还是 const?

当做变量用const,当做属性用readonly(主要判断属性是否可写)

函数类型接口

(之前是按这样的方式来注解函数,有1个问题:在注解函数的同时实现了这个函数,如果我只想要定义这个函数类型,没办法定义)

function mySearch1(source: string, subString: string): boolean {
  let result = source.search(subString);
  return result > -1;
}

函数表达式的方式太冗长

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

我们希望抽象出来一个参数是source和subString,返回值是boolean的函数类型

第一种(不常见):

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

第二种(常见):

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

 可索引类型接口(就是数组类型接口)

当索引是number时候,就可以描述一个数组:

interface numberArr {
  [index: number]: number;
}
let test: numberArr = {
  0: 1,
  1: 2,
};
let arr: numberArr = [1, 2, 3];

 注意:索引签名支持2种形式:字符串和数字。因为所有的number最终会转为字符串,当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。

 官网例子:

(1)为什么第一个索引签名是number会报错? 因为Animal父类的name是string类型

(2)那如果把第一个改成string又为什么会报错?因为两者重复。

(3)如果把第二个索引签名改成number类型为什么不报错?因为所有number最后都被会js转成string类型。

(4)如果number都会被转为string,那么回到第一个问题,为什么不能给第一个索引签名设置为number?因为string类型范围比number范围大,Animal是父类, 父类必须比子类范围要大,所以第一个必须是string,第二个必须是number

索引签名有点类似老大的意思,索引签名是个大范围,接口里面的属性必须得在这个范围内。

interface NumberDictionary {
  [index: string]: number | string;
  length: number; 
  name: string; 
}

索引签名也可以设置为只读:

类 类型接口

可以用接口来描述类吗?不完全可以,因为类有构造器。

对类的一部分的行为的抽象”的理解:

把类上 抽象的公共属性和方法,抽成一个接口。

想要解决这个报错问题,可以改配置项"noImplicitAny": false

interface Alarm {
  alert(): void;
}
interface Light {
  color: string;
  lightOn(): void;
  lightOff(): void;
}
class Door {}
class SecurityDoor extends Door implements Alarm {
  alert() {
    console.log("hi");
  }
}
class Car implements Alarm, Light {
  color = "red";
  lightOn() {}
  lightOff() {}
  alert() {}
}

类和接口对比:

(1)抽象类中定义抽象方法要实现;
         接口中定义的属性方法都要实现;

(2)抽象类中的方法属性可以通过继承方式来拿到;
         接口的抽象方法需要实现;实现接口的话,接口所有方法都要实现;

(3)1个类可以实现多个接口;多个子类可以继承1个抽象的类

 用类 分别实现类的静态部分与实例部分

(类的静态部分和实例部分需要单独写)

通过接口直接实现类的构造函数是会报错的,因为constructor存在于类的静态部分,所以不在检查的范围内。

但是也有办法:

单独抽离1个构造函数的interface,用函数检查的方式单独检查函数的interface,然后返回一个new的实例

interface ClockInterface {
  currentTime: Date;
  getTime(h: number, m: number): void;
}
interface ClockConstructor {
  new (h: number, m: number): any;
  getTime1():void; // 相当于一个静态方法
}

class Clock implements ClockInterface {
  currentTime = new Date();
  getTime() {}
  constructor(h: number, m: number) {}
  static getTime1() { }
}
function createClock(Clock: ClockConstructor, h: number, m: number) {
  return new Clock(h, m);
}
let clock = createClock(Clock, 12, 12);

继承接口

就是接口可以继承接口 或 类可以实现接口。

“接口可以继承接口”的理解:

接口是对象形状的描述,接口继承接口就相当于在原本基础上进行进一步的约束。

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = <Square>{};  // 这里接口继承接口,然后用断言的方式赋初始值
square.color = "blue";
square.sideLength = 10;

官方这里没有解释为什么这里要用类型断言:

如果let square:Square;这样写,报错提示:“在赋值前就使用了变量”;定义了square,但是没有初始值,但是一旦给初始值,就回去检查它类型,就会类型不符,所以用类型推断方式。

接口的混合类型

函数类型的interface,可以通过添加属性的方式来实现对象的interface

相当于函数类型interface+对象类型interface

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

接口继承类

1.类可以实现接口:类可以抽象一部分功能,让接口实现

2.接口可以继承接口:接口相当于一个对象,继承一个对象,就是能在这个对象上面添加更对属性

3.接口可以继承类:类也是接口,接口可以继承接口,所以接口能继承类。

报错:

1.image重复,改成image1。

2.提示错误实现接口SelectableControl,因为接口继承Control接口:在Image里定义state,但是因为是私有成员,所以没法直接实现state

 接口和类的关系:

// 2种写法效果是一样的
class Point {
  x: number;
  y: number;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

interface PointerInterface {
  x: number;
  y: number;
}

interface Point3D extends Point {
  z: number;
}

interface Point3D extends PointerInterface {
  z: number;
}

虽然这种写法方式没有问题,但容易引起大家歧义,不建议大家用接口继承类。

 函数

函数有2种写法:函数声明和函数表达式。

函数声明的注解方式:

function test(a: number, b: number): number {
  return a + b;
}

函数表达式的注解方式:

let test1: (a: number, b: number) => number = (a, b) => {
  return a + b;
};

let test2: (a: number, b: number) => {} = (a, b) => {
  return { a, b };
};
console.log(test2(1, 2));

当参数少了的时候同interface可以设置可选参数

【可选参数注意事项】

(1)默认把可选参数放在后面,必选参数不能位于可选参数后。

function test(a: string, b?: string): string {
  return "" + a + b;
}
console.log(test("1", "2"));

(2)默认值和可选参数不能放一起。

当参数多了可以用剩余参数 ,...args,...args是个数组,必须放到最后面

 

// 语法
function test1(
  { first, second }: { first: number; second: number } = { first: 1, second: 2 }
) {
  return first + second;
}
function test2({ first = 2 }: { first: number }) {
  return first;
}


// 解构赋值
// 变量和属性名一致时,对象可简写
// let {a:a,b:b,c:c}={a:1,b:2,c:3};
// 等价于
// let {a,b,c}={a:1,b:2,c:3};
// 不完全结构是值(右边)多了;结构失败是变量(左边)多了

关于this

this有4个规则:

(1)默认情况下this指向window。(函数的独立调用、立即执行函数、闭包中的this都指向window)

(2)隐式绑定,谁调用就指向谁。

(3)显示绑定,call、apply、bind绑定this指向

(4)new的this指向实例化后的对象

官网例子

报错的this指向window,我们希望这里的this指向deck。

就可以这么写

noImplicitThis配置项

 控制当源文件中存在this的值是any的时候是否报错,noImplicitThis默认为false,即当源文件中存在this为any的情况也不报错,如果设置为true则会报错

函数的重载

ts的重载解决的只是表意问题,能够表意更清楚

function reverse(x: string): string;
function reverse(x: number): number;
function reverse(x: number | string) {
  if (typeof x === "string") {
    return x.split("").reverse().join("");
  } else {
    return Number(x.toString().split("").reverse().join(""));
  }
}
console.log(reverse(123));

编译后

类的注解方式

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

继承

继承中的super在constructor内部默认是构造函数;在这以外super指的是父类。

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();

类成员的修饰符

public:

(1)自身调用:

(2)子类调用:

(3)实例调用:

 private:

(1)只能自身调用

protected:(控制可访问否,上面都是)

(1)自身可以调用(2)子类可以调用

readonly:(控制可写否)

顺序:public readonly

不能修饰成员方法

参数属性

参数属性可以方便地让我们在一个地方定义并初始化一个成员。

class Octopus1 { // 简写
  constructor(private theName: string) {}
}

class Octopus2 {
  private theName: string;
  constructor(theName: string) {
    this.theName = theName;
  }
}

存取器(改变赋值和读取的行为)

1.成对出现,定义了getter一定也有setter,并且修饰同一属性

2.这里的赋值操作相当于调用了set方法,判断里用到了fullName属性,相当于调用了get方法。

静态属性

静态属性用static来定义,通过类/构造函数来访问。如果是实例属性用this.来访问的。

抽象类

abstract关键字来定义抽象类,抽象类能够作为其它派生类的基类使用。

特点:(1)无法创建实例

           (2)抽象类中的抽象方法一定要实现

abstract class Animal {
  abstract makeSound(): void; // 发声方式
  move(): void {
    console.log("roaming the earch...");
  }
}
class Snack extends Animal {
  makeSound() {
    console.log("zzzzzzzz");
  }
  move(): void {
    console.log("roaming the earch...");
  }
}
class Cow extends Animal {
  makeSound() {
    console.log("mmmmmmmmmm");
  }
  move(): void {
    console.log("roaming the earch...");
  }
}

高阶技巧

1.定义了一个类的时候,相当于定义了一个类型/构造函数

2.接口可以继承

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值