Typescript的初探

528 阅读12分钟

Typescript的学习

why tpyescript?

  • 程序更容易理解
  • 效率更高
    • 在不同的代码块和定义中跳转
    • 代码自动补全以及丰富的就接口提示
  • 更少的错误
  • 非常好的包容性,完美兼容javascript

1.typescript的安装:

1.安装:

//安装
sudo npm install -g typescript

//检查
tsc -v  //正常会弹出安装的TS版本号

2.卸载重装:

//卸载typescript
sudo npm uninstall -g typescript

//找到tsc目录,删除它

where tsc # /usr/local/bin/tsc
cd /usr/local/bin
rm -rf tsc

//重新安装
sudo npm install -g typescript

2.类型:

1.基础类型:

ECMAScript最新定义了8种数据类型:

  • 7种原始数据类型
    • Boolean
    • Null
    • Undefined
    • Number
    • String
    • Symbol
    • BigInt
// 布尔类型
let isDone:boolean =true;

// 数字类型
let age:number =24;

// 字符串类型
let PlayerName:string = "艾弗森";

// undefined
let u:undefined =undefined;

// null
let n:null =null;

//number类型可以赋值undefined
let num:number = undefined;

  • undefinednull是所有类型的子类型,也就是说他俩可以赋值给任何类型的变量

2.any类型和联合类型:

any:可以赋值任意类型

// any类型:可以赋值任意类型

let notSure:any="任意类型any";

notSure=24;

notSure=true;

notSure.myname;

notSure.getName();



联合类型:a类型或者b类型...

// 联合类型:如下例子🌰可以是数字类型或者字符串类型

let NumorString:number | string =24;

NumorString="科比"

3.数组Array和元组tuple:

  • 数组

    //数字数组
    let arrofNumber:number[] = [1,2,3,4,5];
    
    arrofNumber.push(520);
    
  • 类数组-IArguments:

    //IArguments
    function test(){
        console.log(arguments);
        console.log(arguments.length); //有length属性
        console.log(arguments[0]);  //可以使用[]
    
        let igr:IArguments = arguments;
    }
    
    
  • 元组元组可以简单的理解为对数组的每项进行类型的规范的数组

    //元组:限定了数组每一项类型的数组
    
    let tuple:[string,number]=["韦德",3];   //✅
    
    tuple=["韦德",3,"迈阿密热火队"];    //❎
    

3.Interface初探:

interface的主要作用:ts里的interface(接口)可以理解为定义一些参数,规定变量里面有什么参数,参数是什么类型,使用时就必须有这些对应类型的参数,少或者多参数、参数类型不对都会报错。

  • 对象的形状进行描述
  • 进行抽象
  • Duck Typing鸭子类型(类型推测):如果一个东西能像鸭子🦆一样游泳,吃饭,遛弯,那么就可以称之为鸭子

例子代码:

  • 表示可选属性
  • readonly表示只读,不可修改
interface Person{
    name:string,
    age?:number     //?表示可选属性
   readonly id:number //只能读,不能修改重写;再有readonly是用在属性上的
}

let kirk:Person={
    name:"kirk",
    age:18,
    id:9527
}

let xiaogang:Person={
    name:"小刚",
    id:9275
}

kirk.id=1111;  // ❎ readonly后不可修改

4.函数类型和类型推断:

1.函数类型:

  • 冒号":"后面基本都是关于类型的解释
  • 冒号前 + ?:表示可选
  • ts中,
//函数类型
function add(x:number,y:number,z?:number):number{
    if(typeof z ==="number"){
        return x+y+z; 
    }else{
        return x+y;
    }
}

let all=add(2,99,1);
console.log(all)

// 函数表达式
const add2:(x:number,y:number,z?:number)=>number = add;

使用interface描述函数:

interface ISum {
	(x:number,y:number,z?:number):number
}
let add2:ISum = add;

2.类型推断:

虽然这里没有对str的类型进行约束,但是ts会自行推断str的类型为字符串,所以以后就不可以将他命名为其他类型的值

//ts的类型推断

let str="琳琅满目";

str=123   // ❎ 上面ts已经推断出str为字符串类型,不能再赋值为数字类型的

5.类class:

1.类class

  • 类(class):定义了一切事物的抽象特点
  • 对象(object):类的实例
  • 面向对象oop三大特性:封装、继承、多态
    • 封装:将对数据的操作细节隐藏起来,只暴露对外的接口;外接不需要知道对外的接口就能访问这个对象。
    • 继承:子类继承父类,除了有自己的一些具体的特性外,还有父类的
    • 多态:是由继承产生的相关的不同的类,对同一个方法有一个不同的响应。例如:猫和狗都继承动物,但是他们都分别实现了自己的吃的方法。
//创建一个动物的类(class.ts文件里的代码)
class Animal{
    name:string;
    // 构造函数
    constructor(name:string){
        this.name=name;
    }
    run(){
        return `${this.name} is running~!`
    }
}

const snake=new Animal("lisin");
console.log(snake.run());   //lisin is running~!

2.安装ts-node以及对代码运行:

//安装
sudo npm install -g ts-node 

运行class.ts文件
ts-node class.ts

3.class.ts的代码:

class Animal{
    name:string;
    // 构造函数
    constructor(name:string){
        this.name=name;
    }
    run(){
        return `${this.name} is running~!`
    }
}

const snake=new Animal("lisin");
console.log(snake.run()); // lisin is running~!

// 继承
class Dog extends Animal{
    bark(){
        return `${this.name}is 汪汪汪~!`
    }
}

const ahuang=new Dog("阿黄");
console.log(ahuang.bark()); //阿黄is 汪汪汪~!

//🌵多态:ahuang有自己的run方法了
console.log(ahuang.run()); //阿黄 is running~!


class Cat extends Animal{
    constructor(name){
        super(name);
        console.log(this.name);
        this.name=name;
    }
    run(){
        return `一只叫 ${this.name} 的猫,正在和毛线球乱跑...`
    }
}

const maomao=new Cat("毛毛");
console.log(maomao.run()); //一只叫 毛毛 的猫,正在和毛线球乱跑...

6.修饰符:

三总修饰符:

  • public:被public修饰的是公有的,可以在类的外部被访问到
  • private:被private修饰的是私有的,只可以在自己的类下访问
  • protected:被protected修饰的,只可以在子类下和自己的类中访问到。(相当于把被修饰的部分变成了遗产,只有我和我的子女可以访问,其他人不行的。)

1.public修饰符:允许在类的外部访问

class Person {
    public name:string;
    constructor(name:string) {
        this.name = name;
    }
}
const kirk = new Person("krik");
console.log(kirk.name);//ts-node打印出 --- kirk
  • 实例可以获得内部的name属性,如果是public的话,那么name将不可以被访问到。

关于super

class Person {
    public name:string;
    constructor(name:string) {
        this.name = name;
    }
}
const kirk = new Person("kirk");
console.log(kirk.name);

class Teacher extends Person {
    constructor(public age:number) {
        super("测试子类向父类传递的值")  //这里的super会将值传递给Person类的constructor中去
    }
}
const testName = new Teacher(99);
console.log(testName.age); // 99
console.log(testName.name);//测试子类向父类传递的值
  • 另外这里有个地方强调的,class继承时候,不传值,constructor中的super也是要写的哈

2.private修饰符:只能自己的类内访问

//private修饰符

class Animal{
  private name:string;
    // 构造函数
    constructor(name:string){
        this.name=name;
    }
    run(){
        return `${this.name} is running~!`
    }
}

class Cat extends Animal{
    constructor(name){
        super(name);
        console.log(this.name);   //由于上面的private修饰符,这里面会报错
        this.name=name;   //由于上面的private修饰符,这里面会报错+1
    }
    run(){
        return `一只叫 ${this.name} 的猫,正在和毛线球乱跑...`//由于上面的private修饰符,这里面会报错+1
    }
}

3.protected修饰符:只能自己自己的孩子访问

//proteted修饰符
class Animal{
  protected name:string;      //这个地方是protected修饰符修饰
    // 构造函数
    constructor(name:string){
        this.name=name;
    }
    run(){
        return `${this.name} is running~!`
    }
}

class Dog extends Animal{
    // 构造函数
       constructor(name:string){
          super(name);
          this.name=name;
      }
    run(){
        return `${this.name} is swiming~!`
    }
}	

const dog=new Dog("kirk");
console.log(dog.run());  //支持自己和自己孩子访问的,结果如下👇
//kirk is swiming~!

const snake=new Animal("lisin");
snake.name="gogoing"  //会报错,因为上面的protected修饰符的关系,现在的snake.name只能被在"Animal"以及子类中访问到。
console.log(snake.run()); // lisin is running~!

4.readonly修饰符:只读

//readonly修饰符
class Animal{
  readonly name:string;
    // 构造函数
    constructor(name:string){
        this.name=name;
    }
    run(){
        return `${this.name} is running~!`
    }
}

const snake=new Animal("lisin");
snake.name="gogoing"  //会报错,因为上面使用了readonly修饰符,所以只能"读取",不能对其修改。
console.log(snake.run()); // lisin is running~!

5.static修饰符:(当类的状态和实例没有关系的时候,可以使用静态方法)

class Animal{
  name:string;
  static categories:string[]=["哺乳动物","鸟类"]
    // 构造函数
    constructor(name:string){
        this.name=name;
    }
    run(){
        return `${this.name} is running~!`
    }
    isAnimal(a){
        return a instanceof Animal;
    }
}


console.log(Animal.categories); //[ '哺乳动物', '鸟类' ]

const snake=new Animal("lisin");
console.log(snake.isAnimal(snake)); //true

7.interfaceimplement

将类中共有的逻辑提取出来,放到interface中,然后通过implement来完成对interface的实现。

  • 通过interface去告诉CarcellPhone,你们都要去实现这个Radio的方法。
  • interface接口之间是可以通过extends继承拓展的。
  • class类是通过implements来对interface完成使用的。
//接口①
interface Radio{
    switchRadio():void
};
//接口②
interface Battery{
    checkBattery():void
};
  
//接口之间的继承
	// 实现了Radio和Barrery方法,inerface也可以使用extends继承
interface RaidowithBattery extends Radio{
    checkBattery():void
};

//class类对接口的使用
class Car implements Radio{
    switchRadio(){
        console.log("我是Car的SwithRadio方法");
    }
}
// 对多个接口的implements
class CellPhone implements Radio,Battery{
    switchRadio(){
        console.log("我是cellPhone的SwithRadio方法");
    }
    checkBattery(){
        console.log("我是检查电池🔋的")
    }
}

class CellPhone1 implements RaidowithBattery{
    switchRadio(){
        console.log("我是cellPhone的SwithRadio方法");
    }
    checkBattery(){
        console.log("我是检查电池🔋的")
    }
}
 const pokmen = new CellPhone();
 console.log(pokmen.switchRadio());  //我是cellPhone的SwithRadio方法

8.枚举:

1.enums:

  • 枚举是一个双向的映射
enum Direction{
    Up,
    Down,
    Left,
    Right
};

//🤖这种双向访问有点牛皮
console.log(Direction.Down); // 1
console.log(Direction[3]);  //Right

2.编译后文件的理解

例子1:
//enmus.ts文件
enum Direction{
    Up=10,   //赋值up为10后,后面的down、left、right依次递增
    Down,
    Left,
    Right
};
console.log(Direction.Down);  //11
console.log(Direction[13]);  //right

接着执行命令行进行编译:

tsc enums.ts

编译后的文件:

var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 10] = "Up";
    Direction[Direction["Down"] = 11] = "Down";
    Direction[Direction["Left"] = 12] = "Left";
    Direction[Direction["Right"] = 13] = "Right";
})(Direction || (Direction = {}));
;
console.log(Direction.Down); //11
console.log(Direction[13]); //Right
  • 自执行函数,封装了一个自己的独特的作用域
  • []内的Direction["Up"]=0是用于自我解释的,会将Direction对象里面的Up设置为0
  • javascript赋值操作返回的是被赋予的这个值,所以
    • Direction[Direction["Up"] = 10] = "Up",相当于Direction[10] = "Up";

例子2:

编译前:

//enums.ts
enum Direction{
    Up="UP",
    Down="DOWN",
    Left="LEFT",
    Right="RIGHT"
};
console.log(Direction.Down);  //1
console.log(Direction[3]);  //Right

编译后:

var Direction;
(function (Direction) {
    Direction["Up"] = "UP";
    Direction["Down"] = "DOWN";
    Direction["Left"] = "LEFT";
    Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));
;
console.log(Direction.Down); //1
console.log(Direction[3]); //Right

例子3:

enum Direction{
    Up="UP",
    Down="DOWN",
    Left="LEFT",
    Right="RIGHT"
};
// console.log(Direction.Down);  // 1
// console.log(Direction[3]);  // Right

const value="DOWN";
if(value === Direction.Down){
    console.log("CrossOver~!")
}
//打印结果
//CrossOver

9.泛型:

1.泛型:

  • 在函数名称后面声明泛型变量 <T>,它用于捕获开发者传入的参数类型(比如说string),然后我们就可以使用T(也就是string)做参数类型返回值类型
//这个"<>"东西就是泛型

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

//通过类型推断,T为字符串类型了
const result=echo("123");

//泛型在元组中的使用:
function swap<T,U>(tuple:[T,U]):[U,T]
{
    return [tuple[1],tuple[0]]
}

const result2=swap(["字符串",123]);
//鼠标放到result2,看到result2的类型第一个是number,第二个是string

console.log(result2[1]); //  字符串

2.约束泛型:

//1.在T后面添加[]达到约束泛型的效果
function echoWithArr<T>(arg:T[]):T[]{        
    console.log(arg.length);
    return arg;
}
const arrs=echoWithArr([23,34,45]);
//控制台打印结果:3 
//2.使用interface来进行限制
interface IWithLength{
    length:number
};
//🌵尖括号内写extends,约束函数内的传入值必须是拥有length属性的才可以
function echoWithLength <T extends IWithLength>(arg:T):T
{
    console.log(arg.length);
    return arg;
}
const o = echoWithLength("sdsfs");
const obj = echoWithLength({length:1005});

console.log(o); // 5

consple.log(obj);// 1005 {length:1005}
  • 就是对输入和输出进行限制---只有拥有length属性的,才可以,否则就报错
    • T后面添加[]达到约束泛型的效果
    • 通过extends interface来进行限制

3.索引泛型:

我们要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,我们通过这两个参数返回这个属性的值,比如:

function getValue(obj: object, key: string) {
  return obj[key] // error
}

参数 obj 实际上是 {},因此后面的 key 是无法在上面取到任何值的。

下面这里引入索引类型

function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
  return obj[key] // ok
}
  • keyof把传入的对象属性类型取出生成一个联合类型

10.泛型的应用:

1.下面的例子会有一个问题,无法判断是否能使用toFixed方法:

// 泛型在类中的应用例子1:
class Queue{
    private data=[];
    push(item){
      return  this.data.push(item);
    };
    pop(){
      return  this.data.shift();
    }
}

const queue=new Queue();    //💡

queue.push(1);
queue.push("this Love")
console.log(queue);  // Queue { data: [ 1, 'this Love' ] }

console.log(queue.pop().toFixed());// 1

console.log(queue.pop().toFixed());//this Love 是个字符串类型,无法使用toFixed方法,会报错;

2.最好的解决办法:

通过泛型来约束类的形状👇:

class Queue<T>{
    private data=[];
    push(item:T){
      return  this.data.push(item);
    };
    pop(){
      return  this.data.shift();
    }
}

const queue=new Queue<number>();   //注意这个地方和上面例子的差别,多了<number>
queue.push(1);
queue.push("this Love")//这时候这里面就有提示了----类型“this Love”的参数不能赋值给number参数

const queue2=new Queue<string>();
queue2.push("pokmeon go!");
console.log(queue2.pop().length);  //11

3.泛型在interface中的使用:

//定义接口
interface KeyValue<T,U>{
    key:T;
    value:U;
}; 
//使用接口,并用泛型对接口进行约束
let k1:KeyValue<number,string>={
  key:920,
  value:"小镇姑娘"
};
console.log(k1);     //{ key: 920, value: '小镇姑娘' 

let k2:KeyValue<string,number>={
  key:"去吧,皮卡丘~!",
  value:999
};

console.log(k2);     //{ key: '去吧,皮卡丘~!', value: 999 }
  • 数字数组的2种写法:
let arr:number[]=[1,2,3,4,5,6];
//使用泛型方式定义数组
let arr1:Array<number>=[6,5,4,3,2,1];
console.log("我是arr",arr);        //我是arr [ 1, 2, 3, 4, 5, 6 ]
console.log("我是arr1",arr1);      //我是arr1 [ 6, 5, 4, 3, 2, 1 ]
//传入不同的泛型值可以适配不同的类型🌵
//定义一个用于约束函数的泛型接口
interface IPlus<T>{
    (a:T,b:T):T;
}

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

function connect(a:string,b:string):string{
    return a+b;
}

const a:IPlus<number>=plus;
const b:IPlus<string>=connect;

11.类型别名和类型断言

1.类型别名:

//类型别名使用1:

type PlusType = (x:number,y:number)=>number;

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

const sum2:PlusType = sum;

//类型别名使用2:
type NameReslover =  () => string;
type NameOrReslover = string | NameReslover;

function getName(n:NameOrReslover):string
{
    if(typeof n === "string"){
        return n
    }else{
        return n();
    }
}
案例2解析:
  • 首先通过type NameReslover =() => string; 制定了一个叫type NameOrReslover=string | NameReslover;规则,就是一个函数,返回的是一个字符串

  • 然后通过type NameOrReslover=string | NameReslover; 制定了一个叫NameOrReslover的规则,他可以是一个字符串或者之前上面定义的那个NameReslover

  • 创建一个getName的函数,参数规定为NameOrReslover,返回值规定为string

  • 这个叫getName的函数,函数体内部判断参数的类型是否为"string",如果是stringreturn返回,如果不是就调用函数。

  • 注意理解上面的类型别名

// 类型别名使用3:
type Directions = "Up" | "Down" | "Left" | "Right"
let toWhere:Directions = "Left";// 这里将toWhere限定在上下左右中的一个

2.交叉类型:

// 交叉类型例子:
interface IName {
  name:string
}
type IPerson = IName & {age:number}

let person:IPerson = {name:"123",age:123}

3.类型断言:

类型断言是用来告诉编译器,自己比它更了解这个类型,并且让它不要发出错误,这个就是类型断言。

function getLength(input:string | number):number{
    
  	//常规操作
    // const str = input as String;
    // if(str.length){
    //     return str.length;
    // }else{
    //     const number =input as Number;
    //     return number.toString().length;
    // }
  
    //骚气的断言操作
    if((<string>input).length){ // (<string>input)这里就是断言操作了,注意要打括号    
        return (<string>input).length
    }else{
        return input.toString().length;
    }
}
console.log(getLength(1231231));// 7

使用type guard改写上面方法:

type guard就是使用instanceof或者typeof等去缩小类型的范围

function getLength(input:string | number):number{
	if(typeof input === "string"){
    return input.length;
  }else{
    return input.toString().length
  }
}

可以看出,使用断言操作后,相比较上面的常规操作要简洁很多。

12.声明文件和内置类型:

1.声明文件:

例如使用Jquery:

  • 通常会把下面的声明放到一个单独的文件中去-jQuery.d.ts

  • 声明文件一般用文件名+.d.ts结尾

declare var jQuery:(selector:string) => any;

或者通过安装官方声明的文件:

npm install --save @types/jQuery

更多的一些第三方库的安装,以及声明文件可以查询一下地址:

www.typescriptlang.org/dt/search?s…

2.内置类型:

const a:Array<number> = [1,2,3];
const data = new Date();

cosnt reg = /abc/
reg.test("abc")

Math.pow(2,2)

let body = document.body;
let allLis  = document.querySelectorAll("li");