TypeScript学习笔记

80 阅读9分钟

TypeScript(尚硅谷学习笔记)

一、概念

  • 什么是 TS?

    • 是添加了类型系统的 JS,适用于任何规模的项目
    • 是一门静态类型、弱类型的语言
    • 是完全兼容 JS 的,它不会修改 JS 运行时的特性
  • TS 特点:

    • TS 需要先编译为 JS,然后运行在浏览器、Node.js 等任何能运行 JS 的环境中
    • 拥有很多编译选项,可自由决定类型检查的严格程度
    • 可以和 JS 共存
    • 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力
    • 拥有活跃的社区,大多数常用的第三方库都提供了类型声明
    • 与标准同步发展,符合最新的 ES 标准
  • 核心设计理念:在完整保留 JS 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug

  • TS 与 JS 的不同点:

    • TS 是静态类型,它在运行前需要先编译为 JS,而在编译阶段就会进行类型检查;TS 坚持与 ES 标准同步发展;TS 非常适用于大型项目
    • JS 是动态类型,它在运行时才会进行类型检查
  • TS 与 JS 的相同点:

    • 它们都是弱类型,允许隐式类型转换

基础内容

  1. 类型声明 使用 : 声明变量的类型,: 的前后有没有空格都可以
// 给变量指定类型
let a : number = 10;
// 给函数的参数指定类型
function sum(a : number, b : number){
	return a + b;
}
// 给函数的返回值指定类型
function sum(a: number, b: number): number{
    return a + b;
}
let result = sum(123, 456);

如果变量的声明和赋值是同时进行的,ts 可以自动对变量进行类型检测 即使指定不符合的变量类型,执行 ts 文件时依然会生成 js 文件;如果要在报错的时候终止 js 文件的生成,可以在 tsconfig.json 中配置 noEmitOnError 即可

  1. 数据类型
  2. number 表示数值类型 let a: number = 19;
  3. string 表示字符串类型 1. let a: string = "abc";
  4. Boolean 表示布尔类型 let a: boolean = true;
  • 注意,使用构造函数 Boolean 创造的对象不是布尔值,其返回的是一个 Boolean 对象 let createdByNewBoolean: Boolean = new Boolean(1);
  1. 联合类型表示取值可以为多种类型中的一种

当使用字面量进行类型声明时,是无法修改其值的

let a: 10;
//可以使用 | 来连接多个类型
let a: "male" | "female";
let a: string | number;
//当使用联合类型时,只能访问此联合类型的所有类型里共有的属性或方法
  1. any 表示任意类型,当一个变量设置为 any 相当于关闭了对该变量的 ts 类型检测 声明变量如果不指定类型,则 ts 解析器会自动判断变量的类型为 any(隐式any) 当把一个 any 类型的值赋值给其他变量时不会报错,不建议使用

  2. unknown 表示未知类型的值,是类型安全的 any 当把一个 unknown 类型的值赋值给其他变量时会报错,建议使用

解决报错1:进行类型判断

let a: unknown;
a = "hello";
let s: string = "world";
if(typeof a === "string"){
    s = a;
}

解决报错2:类型断言

let a: unknown;
a = "hello";
let s: string = "world";
s = a as string;
s = <string>a;
  1. void 表示空,在函数中表示没有返回值
function fn(): void{xxx}

声明 void 类型的变量只能赋值给 undefined 和 null,而不能赋值给 number 类型

  1. undefined & null 可以使用其来定义这两个原始数据类型 undefined 和 null 是所有类型的子类型,所以其可以赋值给 number 类型的变量
let u: number = undefined;
  1. never 表示永远不会返回结果,用于进行报错
function fn2(): never{
    throw new Error("error!");
}
  1. object 表示对象,使用 {} 指定对象中可以包含哪些属性 属性名后加 ? 表示属性是可选的
let b: {name: string, age?: number};
b = {name: "孙悟空"};
//使用 [propName: string]: any 表示任意类型的属性
let c: {name: string, [propName: string]: any};
c = {name: "孙悟空", age: 18}
  1. function 使用箭头函数来设置函数的类型声明
let d: (a: number, b: number) => number;
d = function(a, b){
    return a + b;
}
  1. array 表示数组,数组表示法主要有以下几个:
  • 类型+方括号:
let e: string[];
e = ["a", "b"];
  • 数组泛型
let f: Array<number>;
f = [1, 2];
  • 接口:不常用,主要用来表示类数组
    // 只要索引的类型是数字时,那么值的类型必须是数字
interface NumberArray{
	[index: number]: number
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5]
  1. tuple 表示元组,元组就是固定长度的数组
let t: [string, string];
t = ["a", "b"];
  1. enum表示枚举
enum Gender{
male = 0,
female = 1
}
let h: {
    name: string,
    gender: Gender
}
h = {
    name: "孙悟空",
    gender: Gender.male
}
  1. 类型推论 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型 例如:
let a = "a";
// 等价于
let a: string = "a";
  1. 类型别名 当我们需要很多类型时,可能会因为太长而增加书写量,所以可以给类型设置别名
type myType = string | number | boolean;
let a1: myType;
let a2: myType;
let a3: myType;
a1 = "a";
a2 = 1;
a3 = true;
  1. 编译选项
  • 自动编译 可以监视 ts 文件的变化,自动编译,在命令行中执行命令即可 tsc app.ts -w
  • 缺点:只可以监视当前文件,且无法关闭监视窗口

继承

  1. 使用继承后,子类会拥有父类所有的属性和方法;
  2. 如果希望在子类中添加一些父类没有的东西,直接加就行;
  3. 如果子类中添加了和父类相同的方法,子类方法会覆盖父类,这种方式称为方法重写。
//继承
(function () {
    //把重复代码抽离出来 重新写一个类
    class Animal{
        name:string;
        age:number;

        constructor(name:string, age:number) {
            this.name = name;
            this.age = age;
        }
        sayhello(){
            console.log("动物在叫")
        }
    }
    //继承Animal类 Animal称为父类,Dog为子类,使用继承后 子类会有弗雷所有属性
    class Dog extends Animal{
     run(){
         console.log("狗在跑")
     }
    }
    class Cat extends Animal{
        sayhello(){
            console.log("喵喵喵喵喵喵")
        }
    }


    const dog = new Dog('wancai', 1);
    console.log(dog);
    dog.run();

    const cat = new Cat('咪咪', 1);
    console.log(cat);
    cat.sayhello();
})();

注意:如果在子类中写了构造函数,则必须对父类的构造函数进行调用,即使用 super()。

class Cat extends Animal{
    age:number;
    //如果在子类中写了构造函数,则必须对父类的构造函数进行调用,即使用 super()
    constructor(name:string,age:number){
        super(name,age)
        this.age = age+2
    }
   sayhello() {
       //
       super.sayhello();//super表示当前类的父类
   }
}

抽象类:abstract class Animal,以abstract开头的类抽象类,只能被用来继承,不能用来创建实例,即不能new Animal。 抽象方法只能定义在抽象类中,而且必须是没有方法体。子类必须对抽象方法进行重写。

//父类
abstract class Animal{ 
    name:string;
    age:number;

    constructor(name:string, age:number) {
        this.name = name;
        this.age = age;
    }
    //抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
   abstract sayhello():void

}
//子类
class Cat extends Animal{
   sayhello() {
       console.log('喵喵喵喵喵')
   }
}

接口

接口就是用来定义一个类的结构,定义包含哪些属性和方法 和定义myType不同的是,myinterface是可以重复定义的,可以限制类的结构。接口中的所有属性和方法都不能有实际值 只定义结构,不考虑实际值 ,都是抽象方法。

//描述一个对象的类型
type myType = {
    name:string;
    age:number
}
const obj1:myType = {
    name:'xixi',
    age:19
}
// 和定义myType不同的是,myinterface是可以重复定义的,定义的结果就是两者的合并,可以限制类的结构, 用接口表示 接口就是用来定义一个类的结构,定义包含哪些属性和方法
 //接口中的所有属性和方法都不能有实际值  只定义结构,不考虑实际值  都是抽象方法
interface myInterface{
   name:string;
   age:number;
}
interface myInterface{
   gender:string,
   sayhello():void
}

const obj2:myInterface = {
   name:'xixi',
   age:19,
   gender:'male',
   sayhello() {
       console.log('hello')
   }
}
//    类实现接口
    interface myInterface{
        name:string;
        sayhello():void
    }
    class Myclass implements myInterface{
        name:string;
        //构造函数用来进行初始化
        constructor(name:string) {
            this.name = name
        }
        sayhello() {
            console.log('hello')
        }
    }

属性封装

  1. 类的修饰符:
  •   public:公有属性,可以在任意位置访问与修改
    
  •   private:私有属性,只能在类内部进行访问修改
    
  •   protected:受保护的属性,只能在当前类和当前类的子类中使用
    
  • 类中定义的属性与方法,属性可以任意被修改会导致对象中的数据变得非常不安全。

  • getter方法用来读取属性,setter方法设置属性,它们被称为属性的存储器。

class Person{
    private name: string;
    private age: number;
    constructor(name: string, age: number){
        this.name = name;
        this.age = age;
    }
    //定义方法,用来获取name属性。
    getName(){
        return this.name;
    }
    //定义方法,设置name属性
    setName(value:string){
        this.name = value;
    }
}
const p = new Person("孙悟空", 18);
p.setName("猪八戒");
console.log(p.getName());
  • 在 ts 中我们需要设置属性的修饰符为 private
  • ts 中简化了操作,设置 getter 与 setter 的方法如下:
class Person{
    private _name: string;
    private _age: number;
    constructor(name: string, age: number){
        this._name = name;
        this._age = age;
    }

    get name(){
        return this._name;
    }

   set name (value:string){
        this._name = value;
    }
}
const p = new Person("孙悟空", 18);
p.name = 'xixi'
console.log(p);
console.log(p.name);

泛型

//这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。
function fn<T>(arg: T): T{
    return arg;
}
//方式一(直接使用):
fn(10)

//使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式

//方式二(指定类型):
fn<number>(10)
//也可以在函数后手动指定泛型
//可以同时指定多个泛型,泛型间使用逗号隔开:
function fn1<T, K>(a: T, b: K): K{
    return b;
}
fn1<number, string>(10, "hello");

//使用泛型时,完全可以将泛型当成是一个普通的类去使用
//类中同样可以使用泛型:

class MyClass<T>{
    name: T;
    constructor(prop: T){
        this.name = prop;
    }
}
//除此之外,也可以对泛型的范围进行约束
interface MyInter{
    length: number;
    name:string
}
//表示泛型T必须是Inter子类
function fn3<T extends MyInter>(arg: T): number{
    return arg.length;
}
fn3({length:12,name:''})
const ma = new MyClass<string>('xiix')                                                                    
//使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。